Merge remote-tracking branch 'origin/master' into worktree/session-reference

# Conflicts:
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md
#	docs/config-catalog.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	packages/compact/compact-basic/src/region.ts
#	packages/compact/compact/README.md
#	packages/compact/compact/tests/compact.spec.ts
#	packages/examples/acp-demo/package.json
#	packages/ui/tui/README.md
#	packages/ui/tui/package.json
#	packages/ui/tui/src/index.ts
#	packages/ui/tui/tests/harness.ts
#	packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
Yichen Jiang
2026-07-21 21:10:42 +08:00
629 changed files with 21135 additions and 3315 deletions

View File

@@ -6,7 +6,7 @@ This package is the interface tier of the compaction capability, split so each c
| Package | Role |
|---|---|
| `@deepseek-ai/dsh-compact` (this) | the interface: abstract service + `compact/*` events + `CompactionResult` + canonical checkpoint source + tool-pairing boundary helpers + the shared transcript renderer (`renderTranscript`/`renderContentBlocks`) |
| `@deepseek-ai/dsh-compact` (this) | the interface: abstract service + `compact/*` events + `CompactionResult` + canonical checkpoint source + tool-pairing boundary helpers |
| `@deepseek-ai/dsh-compact-basic` | a backend: `ctx.tokenMeter` pressure + token-budget retention + `llm.stream()` summarization |
| `@deepseek-ai/dsh-tool-compact` (deferred) | the model-facing `/compact` tool over `ctx.compact` |
@@ -63,7 +63,7 @@ Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and
#### What the model sees
A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite.
A successful implementation replaces an older surface range with one user-role summary checkpoint — a `user/message` carrying `surfaceOp: { op: 'replace', start, end }`; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite.
#### Token effect
@@ -73,20 +73,6 @@ Zero direct tokens from this interface. A backend trades many retained history t
A successful backend replacement invalidates reuse from the first shadowed history token; the seam itself does not alter a request.
### Transcript supplied to a compaction consumer
#### What the model sees
`renderTranscript()` joins entries with one blank line and renders them exactly as `User: <content>`, `Assistant: <content>`, `Tool result (call <callId>): <content>`, `Tool error (call <callId>): <content>`, `[Context: <content>]`, or `[Steering: <content>]`. Non-text blocks render exactly as `[reasoning: <text>]`, `[tool-call: <name>(<arguments>)]`, `[tool-result: <content>]`, `[tool-result]`, or `[<block-type>]`.
#### Token effect
Data-dependent input tokens are paid only by the auxiliary model or consumer that requests this transcript; the conversation model does not receive a duplicate transcript.
#### KV Cache effect
No conversation-cache invalidation. A consumer's auxiliary request can reuse only the exact prefix produced by this rendering; changed or compacted entries invalidate reuse from their first difference.
## Known Limitations and Deferred Work
- **No model-facing consumer tier yet** — `@deepseek-ai/dsh-tool-compact` (the `/compact` tool) is deferred; compaction is reachable only via direct `ctx.compact` calls or a backend's auto listener.

View File

@@ -11,22 +11,29 @@
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.d.ts",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"cordis": "^4.0.0-rc.7"

View File

@@ -13,7 +13,6 @@ import type { Session } from '@deepseek-ai/dsh-session'
import type { CompactionResult } from './types.ts'
export type { CompactionResult } from './types.ts'
export { renderContentBlocks, renderTranscript } from './render.ts'
export { toolPairingBalancedAfter, toolPairingBalancedBefore } from './tool-pairing.ts'
/** Canonical source for the replacement user message produced by every compaction backend. */

View File

@@ -0,0 +1,111 @@
/** Package-owned compaction log-stream invariants. @module @deepseek-ai/dsh-compact/invariant */
import type { Context } from 'cordis'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
import type { InvariantFailure, InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import type {} from './types.ts'
const PACKAGE_NAME = '@deepseek-ai/dsh-compact'
/** Cordis companion plugin name. */
export const name = 'compact-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
interface CompactionTrace {
turn: number
summarized: boolean
}
type CompactionTransition =
| { kind: 'start'; turn: number }
| { kind: 'summary'; turn: number }
| { kind: 'end' }
/** Validate one compaction event without advancing committed trace state. */
function validateCompactionEvent(
open: CompactionTrace | undefined,
event: SessionEvent,
fail: InvariantFailure,
): CompactionTransition | undefined {
if (event.type === 'compact/start') {
if (open !== undefined) fail(`compact/start for turn ${event.data.turn} while turn ${open.turn} is still compacting`)
return { kind: 'start', turn: event.data.turn }
}
if (event.type === 'compact/summary') {
if (open === undefined) fail('compact/summary has no matching compact/start')
if (open.summarized) fail('compact/summary repeated within one compaction')
const seqs = event.data.shadowedSeqs
if (seqs.length === 0) fail('compact/summary shadowedSeqs must be non-empty')
if (seqs[0] !== event.data.shadowedRange.start || seqs.at(-1) !== event.data.shadowedRange.end) {
fail('compact/summary shadowedRange must match the first and last shadowedSeqs')
}
if (!Number.isSafeInteger(event.data.shadowedTokenCount) || event.data.shadowedTokenCount < 0) {
fail('compact/summary shadowedTokenCount must be a non-negative safe integer')
}
return { kind: 'summary', turn: open.turn }
}
if (event.type !== 'compact/end') return undefined
if (open === undefined) fail('compact/end has no matching compact/start')
if (event.data.turn !== open.turn) {
fail(`compact/end turn ${event.data.turn} does not match compact/start turn ${open.turn}`)
}
if (event.data.error === undefined && !open.summarized) {
fail('successful compact/end requires one compact/summary')
}
return { kind: 'end' }
}
/** Apply one committed compaction transition. */
function applyCompactionTransition(
transition: CompactionTransition,
): CompactionTrace | undefined {
if (transition.kind === 'start') return { turn: transition.turn, summarized: false }
if (transition.kind === 'summary') return { turn: transition.turn, summarized: true }
return undefined
}
/** Install compaction start/summary/end checks. */
// Event owners keep precommit staging local so their vocabularies never move into a central helper.
/* jscpd:ignore-start */
const install: InvariantInstaller = Object.assign((ctx: Context, fail: InvariantFailure) => {
const traces = new WeakMap<Session, CompactionTrace>()
const staged = new WeakMap<SessionEvent, { session: Session; transition: CompactionTransition }>()
const seed = (session: Session): void => {
let open: CompactionTrace | undefined
for (const event of session.events) {
const transition = validateCompactionEvent(open, event, fail)
if (transition !== undefined) open = applyCompactionTransition(transition)
}
if (open !== undefined) traces.set(session, open)
}
const traceFor = (session: Session): CompactionTrace | undefined => traces.get(session)
for (const session of ctx.sessions.list()) seed(session)
ctx.on('session/created', (session) => { seed(session) }, { global: true })
ctx.on('session/event', (session, event) => {
if (event.type !== 'compact/start' && event.type !== 'compact/summary' && event.type !== 'compact/end') return
const candidate = staged.get(event)
/* v8 ignore next -- internal/dispatch stages every compaction event */
if (candidate === undefined || candidate.session !== session) return fail('compaction event published without pre-commit validation')
staged.delete(event)
const next = applyCompactionTransition(candidate.transition)
if (next === undefined) traces.delete(session)
else traces.set(session, next)
}, { global: true })
ctx.on('internal/dispatch', (_mode, eventName, args) => {
if (eventName !== 'session/event') return
const [session, event] = args as [Session, SessionEvent]
const transition = validateCompactionEvent(traceFor(session), event, fail)
if (transition !== undefined) staged.set(event, { session, transition })
}, { global: true })
}, { inject: ['sessions'] })
/* jscpd:ignore-end */
/**
* Register the compact invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))

View File

@@ -1,95 +0,0 @@
/**
* Pure shared transcript projection for summarization and recall, so both
* render the same log span byte-for-byte under replay.
* @module @deepseek-ai/dsh-compact/render
*/
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
/**
* Render text directly, reasoning as a tagged span, and every other block as a
* type-tagged placeholder. Tool results recurse into nested content; empty
* blocks contribute nothing and rendered blocks join with newlines.
*
* @param blocks - the content blocks to render.
* @returns the newline-joined plain-text rendering; empty string when nothing renders.
*/
export function renderContentBlocks(blocks: readonly ContentBlock[]): string {
const parts: string[] = []
for (const block of blocks) {
switch (block.type) {
case 'text':
if (block.text) parts.push(block.text)
break
case 'reasoning':
if (block.text) parts.push(`[reasoning: ${block.text}]`)
break
case 'tool-call':
parts.push(`[tool-call: ${block.name}(${block.arguments})]`)
break
case 'tool-result': {
const inner = renderContentBlocks(block.content)
parts.push(inner ? `[tool-result: ${inner}]` : '[tool-result]')
break
}
// ContentBlockMap is merge-extensible — render an unknown block as a
// bare type-tagged placeholder so a plugin-added block type is still
// signalled to the reader rather than dropped.
default:
parts.push(`[${(block as ContentBlock).type}]`)
}
}
return parts.join('\n')
}
/**
* Render message-producing events as a role-labeled transcript. `seqs` are
* walked in caller-supplied surface order, which may differ from numeric log
* order after replacement; non-surface and unknown merged events are skipped.
*
* @param events - the session log the seqs index into (`session.events`).
* @param seqs - the surface-node seqs to render, in surface order.
* @returns the transcript, entries joined by blank lines; empty string when nothing renders.
*/
export function renderTranscript(events: readonly SessionEvent[], seqs: readonly number[]): string {
const lines: string[] = []
for (const seq of seqs) {
const event = events[seq]
if (!event) continue
switch (event.type) {
case 'user/message': {
const text = renderContentBlocks(event.data.content)
if (text) lines.push(`User: ${text}`)
break
}
case 'assistant/message': {
const text = renderContentBlocks(event.data.content)
if (text) lines.push(`Assistant: ${text}`)
break
}
case 'tool/result': {
const text = renderContentBlocks(event.data.content)
const label = event.data.isError ? 'Tool error' : 'Tool result'
if (text) lines.push(`${label} (call ${event.data.callId}): ${text}`)
break
}
case 'context/message': {
const text = renderContentBlocks(event.data.content)
if (text) lines.push(`[Context: ${text}]`)
break
}
case 'steering/message': {
const text = renderContentBlocks(event.data.content)
if (text) lines.push(`[Steering: ${text}]`)
break
}
default:
break
}
}
return lines.join('\n\n')
}

View File

@@ -0,0 +1,90 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import SessionStore from '@deepseek-ai/dsh-session'
import * as CompactInvariant from '@deepseek-ai/dsh-compact/invariant'
import InvariantService from '@deepseek-ai/dsh-invariants'
async function setup(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(SessionStore)
await ctx.plugin(InvariantService)
await ctx.plugin(CompactInvariant)
return ctx
}
const summary = (overrides: Record<string, unknown> = {}) => ({
summary: [{ type: 'text' as const, text: 'short' }],
shadowedRange: { start: 2, end: 4 },
shadowedSeqs: [2, 3, 4],
shadowedTokenCount: 12,
provider: 'mock',
model: 'mock',
...overrides,
})
describe('compaction invariants', () => {
it('accepts successful and failed compaction lifecycles', async () => {
const ctx = await setup()
const success = ctx.sessions.create()
success.append('compact/start', { turn: 1 })
success.append('compact/summary', summary())
success.append('compact/end', { turn: 1 })
const failed = ctx.sessions.create()
failed.append('compact/start', { turn: 2 })
failed.append('compact/end', { turn: 2, error: 'provider failed' })
})
it('rebuilds an open trace when the companion loads after the session', async () => {
const ctx = new Context()
await ctx.plugin(SessionStore)
const session = ctx.sessions.create()
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('compact/start', { turn: 3 })
await ctx.plugin(InvariantService)
await ctx.plugin(CompactInvariant)
expect(() => session.append('compact/end', { turn: 3, error: 'resume failed' })).not.toThrow()
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
})
it.each([
['summary without start', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/summary', summary())
}, /no matching compact\/start/],
['nested start', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/start', { turn: 2 })
}, /still compacting/],
['repeated summary', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/summary', summary())
session.append('compact/summary', summary())
}, /repeated within one compaction/],
['empty shadow set', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/summary', summary({ shadowedSeqs: [] }))
}, /shadowedSeqs must be non-empty/],
['wrong endpoints', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/summary', summary({ shadowedRange: { start: 1, end: 4 } }))
}, /shadowedRange must match/],
['invalid token count', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/summary', summary({ shadowedTokenCount: -1 }))
}, /non-negative safe integer/],
['end without start', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/end', { turn: 1, error: 'failed' })
}, /no matching compact\/start/],
['wrong end turn', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/end', { turn: 2, error: 'failed' })
}, /does not match/],
['success without summary', (session: ReturnType<Context['sessions']['create']>) => {
session.append('compact/start', { turn: 1 })
session.append('compact/end', { turn: 1 })
}, /requires one compact\/summary/],
])('rejects %s', async (_name, action, message) => {
const ctx = await setup()
expect(() => { action(ctx.sessions.create()) }).toThrow(message)
})
})

View File

@@ -1,138 +0,0 @@
import { describe, expect, it } from 'vitest'
import { renderContentBlocks, renderTranscript } from '@deepseek-ai/dsh-compact'
import { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { ContentBlock } from '@deepseek-ai/dsh-llm'
import { CallId } from '@deepseek-ai/dsh-llm'
function session(): Session {
return new Session(SessionId('render-spec'))
}
describe('renderContentBlocks', () => {
it('renders text blocks verbatim and skips empty ones', () => {
expect(renderContentBlocks([
{ type: 'text', text: 'hello' },
{ type: 'text', text: '' },
{ type: 'text', text: 'world' },
])).toBe('hello\nworld')
})
it('wraps reasoning, skipping empty reasoning', () => {
expect(renderContentBlocks([
{ type: 'reasoning', text: 'think' },
{ type: 'reasoning', text: '' },
])).toBe('[reasoning: think]')
})
it('renders tool-call as a name(args) placeholder', () => {
expect(renderContentBlocks([
{ type: 'tool-call', id: CallId('c1'), name: 'read', arguments: '{"filePath":"a"}' },
])).toBe('[tool-call: read({"filePath":"a"})]')
})
it('renders tool-result with nested content, and bare when empty', () => {
expect(renderContentBlocks([
{ type: 'tool-result', toolCallId: CallId('c1'), content: [{ type: 'text', text: 'ok' }] },
{ type: 'tool-result', toolCallId: CallId('c2'), content: [] },
])).toBe('[tool-result: ok]\n[tool-result]')
})
it('renders an unknown (merge-extended) block type as a bare type tag', () => {
const unknown = { type: 'image', data: 'zzz' } as unknown as ContentBlock
expect(renderContentBlocks([unknown])).toBe('[image]')
})
it('returns the empty string for no blocks', () => {
expect(renderContentBlocks([])).toBe('')
})
})
describe('renderTranscript', () => {
it('renders each surface event type with its label, in the seq order given', () => {
const s = session()
const user = s.append('user/message', {
content: [{ type: 'text', text: 'fix the bug' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
const assistant = s.append('assistant/message', { provenance: { provider: 'mock', model: 'mock' },
turn: 0, step: 0,
content: [{ type: 'text', text: 'looking' }],
}, { surfaceOp: 'append' })
const result = s.append('tool/result', {
turn: 0, step: 0, callId: CallId('c1'),
content: [{ type: 'text', text: 'exit 0' }],
isError: false,
}, { surfaceOp: 'append' })
const context = s.append('context/message', {
content: [{ type: 'text', text: 'file changed' }],
source: { kind: 'plugin', plugin: 'fs' },
}, { surfaceOp: 'append' })
const steering = s.append('steering/message', {
turn: 0,
content: [{ type: 'text', text: 'stop that' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
expect(renderTranscript(s.events, [user.seq, assistant.seq, result.seq, context.seq, steering.seq])).toBe([
'User: fix the bug',
'Assistant: looking',
'Tool result (call c1): exit 0',
'[Context: file changed]',
'[Steering: stop that]',
].join('\n\n'))
})
it('labels an error tool result "Tool error"', () => {
const s = session()
const result = s.append('tool/result', {
turn: 0, step: 0, callId: CallId('c9'),
content: [{ type: 'text', text: 'boom' }],
isError: true,
}, { surfaceOp: 'append' })
expect(renderTranscript(s.events, [result.seq])).toBe('Tool error (call c9): boom')
})
it('renders NON-log-order seqs in the order given (surface order after a replace)', () => {
const s = session()
const first = s.append('user/message', {
content: [{ type: 'text', text: 'first' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
const second = s.append('user/message', {
content: [{ type: 'text', text: 'second' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
expect(renderTranscript(s.events, [second.seq, first.seq])).toBe('User: second\n\nUser: first')
})
it('skips events that render to nothing, non-message events, and seqs with no event', () => {
const s = session()
const empty = s.append('user/message', {
content: [{ type: 'text', text: '' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
const emptyAssistant = s.append('assistant/message', { provenance: { provider: 'mock', model: 'mock' },
turn: 0, step: 0,
content: [{ type: 'text', text: '' }],
}, { surfaceOp: 'append' })
const emptyResult = s.append('tool/result', {
turn: 0, step: 0, callId: CallId('c3'),
content: [{ type: 'text', text: '' }],
isError: false,
}, { surfaceOp: 'append' })
const emptyContext = s.append('context/message', {
content: [{ type: 'text', text: '' }],
source: { kind: 'plugin', plugin: 'fs' },
}, { surfaceOp: 'append' })
const emptySteering = s.append('steering/message', {
turn: 0,
content: [{ type: 'text', text: '' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
// A log-only (non-surface) event type: contributes nothing to a transcript.
const lock = s.append('compact/start', { turn: 0 })
expect(renderTranscript(s.events, [
empty.seq, emptyAssistant.seq, emptyResult.seq, emptyContext.seq, emptySteering.seq, lock.seq, 9999,
])).toBe('')
})
})

View File

@@ -19,6 +19,9 @@
},
{
"path": "../../core/session"
},
{
"path": "../../support/invariants"
}
]
}