Merge remote-tracking branch 'origin/master' into worktree/web-file-session-references

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.md
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	packages/client/connection/src/client/api.ts
#	packages/client/connection/src/client/index.ts
#	packages/client/runtime/src/client/sessions/conversation.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/MessageItem.module.css
#	packages/client/ui-conversation/src/client/chat/MessageItem.tsx
#	packages/client/ui-conversation/tests/chat-branch-tails.spec.tsx
#	packages/context/file-reference-local/src/search.ts
#	packages/context/file-reference-local/tests/search.spec.ts
#	packages/core/session/src/index.ts
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/fetch/handler.ts
#	packages/ui/tui/src/chat/file-autocomplete.ts
#	packages/ui/tui/src/file-autocomplete.ts
#	packages/ui/tui/src/index.ts
#	packages/ui/tui/tests/tui.spec.ts
#	scripts/verify-package-readme-model-experience.ts
#	tsconfig.client.json
This commit is contained in:
Yichen Jiang
2026-07-28 11:33:33 +08:00
1503 changed files with 48718 additions and 22002 deletions
@@ -241,11 +241,9 @@ describe('session reference discovery and preparation', () => {
[{ sessionId: source.id, label: 'source' }],
)
expect(prepared.content).toEqual([{ type: 'text', text: 'use @source' }])
expect(prepared.contexts).toHaveLength(1)
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context?.content[0]?.type !== 'text') throw new Error('expected text context')
expect(context.source).toEqual({ kind: 'plugin', plugin: 'session-reference' })
expect(context.placement).toBe('prompt-prefix')
expect(context.source).toMatchObject({ kind: 'session-reference' })
expect(context.content[0].text).toContain('untrusted, read-only snapshot')
expect(promptData(context.content[0].text)).toEqual([{
sessionId: 'source',
@@ -259,7 +257,7 @@ describe('session reference discovery and preparation', () => {
{ role: 'assistant', text: 'visible answer' },
],
}])
expect(context.meta).toMatchObject({
expect(context.source).toMatchObject({
kind: 'session-reference',
version: 1,
references: [{
@@ -279,29 +277,29 @@ describe('session reference discovery and preparation', () => {
expect(context.content[0].text).not.toContain('later source mutation')
})
it('projects only the direct prompt when a source message contains baked prefix context', async () => {
it('excludes injected context when projecting a referenced session', async () => {
const ctx = await harness()
const target = ctx.sessions.create(SessionId('target'))
const source = ctx.sessions.create(SessionId('source'))
source.append('user/message', {
content: [
{ type: 'text', text: 'nested referenced snapshot must not propagate' },
{ type: 'text', text: '\n\n## My request:\n' },
{ type: 'text', text: 'direct source question' },
],
source: { kind: 'user' },
envelope: {
displayContent: [{ type: 'text', text: 'direct source question' }],
prefixContexts: [{ source: { kind: 'plugin', plugin: 'session-reference' } }],
content: [{ type: 'text', text: 'nested referenced snapshot must not propagate' }],
source: {
kind: 'session-reference',
version: 1,
references: [],
},
}, { surfaceOp: 'append' })
source.append('user/message', {
content: [{ type: 'text', text: 'direct source question' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
const prepared = await ctx.sessionReferences.prepare(
fakeAgent(target),
[{ type: 'text', text: 'inspect source' }],
[{ sessionId: source.id }],
)
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context?.content[0]?.type !== 'text') throw new Error('expected text context')
expect(promptData(context.content[0].text)).toMatchObject([{
conversation: [{ role: 'user', text: 'direct source question' }],
@@ -325,7 +323,7 @@ describe('session reference discovery and preparation', () => {
[{ type: 'text', text: 'use @source' }],
[{ sessionId: source.id }],
)
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context?.content[0]?.type !== 'text') throw new Error('expected text context')
const prompt = context.content[0].text
expect(prompt).toMatch(/^## Referenced sessions\n/u)
@@ -350,14 +348,14 @@ describe('session reference discovery and preparation', () => {
const content = [{ type: 'text' as const, text: 'go' }]
const withoutReferences = await ctx.sessionReferences.prepare(agent, content, [])
expect(withoutReferences).toEqual({ content, contexts: [] })
expect(withoutReferences).toEqual({ content })
expect(withoutReferences.content).not.toBe(content)
await expect(ctx.sessionReferences.prepare(agent, content, [
{ sessionId: one.id, label: 'first' },
{ sessionId: one.id, label: 'ignored duplicate' },
{ sessionId: two.id },
])).resolves.toMatchObject({ contexts: [{ meta: { references: [{ label: 'first' }, { label: 'two' }] } }] })
])).resolves.toMatchObject({ additionalContext: { source: { references: [{ label: 'first' }, { label: 'two' }] } } })
await expect(ctx.sessionReferences.prepare(agent, content, [{ sessionId: target.id }]))
.rejects.toThrow(expectCode('SESSION_REFERENCE_SELF_REFERENCE'))
await expect(ctx.sessionReferences.prepare(agent, content, [null as never]))
@@ -428,14 +426,14 @@ describe('session reference discovery and preparation', () => {
)
const prepared = await ctx.sessionReferences.prepare(fakeAgent(target), [{ type: 'text', text: 'go' }], [{ sessionId: source.id }])
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context?.content[0]?.type !== 'text') throw new Error('expected text context')
const data = promptData(context.content[0].text) as unknown[]
expect(Buffer.byteLength(stringifyTagSafeJson(data[0]), 'utf8')).toBeLessThanOrEqual(360)
expect(context.content[0].text).toContain('checkpoint')
expect(context.content[0].text).toContain('latest-')
expect(context.content[0].text).toContain('omitted')
expect(context.meta).toMatchObject({ references: [{ truncated: true, compacted: true }] })
expect(context.source).toMatchObject({ references: [{ truncated: true, compacted: true }] })
})
it('applies the full byte limit independently to each of three references', async () => {
@@ -462,7 +460,7 @@ describe('session reference discovery and preparation', () => {
[{ type: 'text', text: 'go' }],
sources.map(source => ({ sessionId: source.id })),
)
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context?.content[0]?.type !== 'text') throw new Error('expected text context')
const data = promptData(context.content[0].text) as unknown[]
const sizes = data.map(source => Buffer.byteLength(stringifyTagSafeJson(source), 'utf8'))
@@ -495,18 +493,12 @@ describe('session reference discovery and preparation', () => {
[{ type: 'text', text: 'use @source' }],
[{ sessionId: source.id }],
)
const context = prepared.contexts[0]
const context = prepared.additionalContext
if (context === undefined) throw new Error('expected prepared context')
target.append('user/message', context, { surfaceOp: 'append' })
target.append('user/message', {
content: [...context.content, { type: 'text', text: '\n\n## My request:\n' }, ...prepared.content],
content: prepared.content,
source: { kind: 'user' },
envelope: {
displayContent: prepared.content,
prefixContexts: [{
source: context.source,
...context.meta === undefined ? {} : { meta: context.meta },
}],
},
}, { surfaceOp: 'append' })
const before = target.deriveMessages()
@@ -533,7 +525,7 @@ describe('session reference discovery and preparation', () => {
expect(ctx.sessions.get(source.id)).toBeUndefined()
expect(target.deriveMessages()).toEqual(before)
expect(JSON.stringify(before)).toContain('durable referenced fact')
expect(JSON.stringify(before)).toContain('## My request:')
expect(JSON.stringify(before)).toContain('use @source')
expect(JSON.stringify(before)).not.toContain('later source mutation')
expect(new Session(SessionId('replayed-target'), target.events).deriveMessages()).toEqual(before)
})