refactor(agent): align delivery method names

This commit is contained in:
Turtle
2026-07-24 15:08:36 +08:00
parent 9848a9542b
commit b02b438667
113 changed files with 462 additions and 405 deletions

View File

@@ -5,7 +5,7 @@
## Public API
- `listCandidates(agent, query?, limit?)` lists sessions other than `agent.id`, filters case-insensitively by id or cwd, and ranks same-cwd, cwd-less, then other-cwd records while preserving `listSessions()` creation order within each group. Each selected candidate uses its latest log-backed title as the mention label and falls back to the session id; titles and message bodies are not searched.
- `prepare(agent, content, references, signal?)` preserves first-mention order, deduplicates ids, rejects self-reference and more than the configured distinct-source limit, reads every source in parallel, and returns detached content plus zero or one aggregated `HookContext`. Any invalid reference, failed read, cancellation, or budget failure rejects before the host calls `send()` or `steer()`.
- `prepare(agent, content, references, signal?)` preserves first-mention order, deduplicates ids, rejects self-reference and more than the configured distinct-source limit, reads every source in parallel, and returns detached content plus zero or one aggregated `HookContext`. Any invalid reference, failed read, cancellation, or budget failure rejects before the host calls `followup()` or `steer()`.
- `encodeSessionReferenceUri()` and `decodeSessionReferenceUri()` implement `dsh-session:<base64url(JSON.stringify(sessionId))>` so every JavaScript string id round-trips exactly. `formatSessionReferenceMention()` emits `@[label](uri)`, and `parseSessionReferenceText()` replaces Markdown mentions or bare canonical URIs with readable `@label` text while returning structured references. Explicit Markdown mentions reject every malformed URI; bare text is considered a reference only when a non-empty base64url-shaped payload follows the scheme, and a matching noncanonical candidate still fails. Empty or punctuation-only scheme mentions remain ordinary discussion text.
## Snapshot semantics

View File

@@ -42,7 +42,7 @@ function sessionAgent(session: Session, id = 'agent'): Agent {
session,
status: 'running',
ctx: new Context(),
send: () => AgentMessageId('stub'),
followup: () => AgentMessageId('stub'),
queue: () => AgentMessageId('stub'),
steer: () => AgentMessageId('stub'),
inject(content, options) {
@@ -52,7 +52,7 @@ function sessionAgent(session: Session, id = 'agent'): Agent {
}, { surfaceOp: 'append' })
return AgentMessageId('stub')
},
acceptInput: () => AgentMessageId('stub'),
send: () => AgentMessageId('stub'),
cancel() {},
whenIdle: () => Promise.resolve(),
}
@@ -374,7 +374,7 @@ describe('real agent-loop request history', () => {
})
const agent = ctx.agentLoop.create(SessionId(`late-${mode}`), { provider: 'mock', model: 'mock' })
agent.send([{ type: 'text', text: 'start' }])
agent.followup([{ type: 'text', text: 'start' }])
await agent.whenIdle()
expect(laterSawReading).toBe(true)
@@ -400,7 +400,7 @@ describe('real agent-loop request history', () => {
}))
const agent = ctx.agentLoop.create(SessionId('loop'), { provider: 'mock', model: 'mock' })
agent.send([{ type: 'text', text: 'start' }])
agent.followup([{ type: 'text', text: 'start' }])
await agent.whenIdle()
expect(adapter.requests).toHaveLength(2)

View File

@@ -78,7 +78,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
it('obeys a probe instruction loaded from the workspace', async () => {
const live = await harness()
live.agent.send([{ type: 'text', text: 'Workspace context handshake?' }])
live.agent.followup([{ type: 'text', text: 'Workspace context handshake?' }])
await waitForIdle(live.ctx, live.agent)
expect(finalText([...live.agent.session.events])).toContain(PROBE)
@@ -90,7 +90,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
await writeFile(join(workdir!, 'pkg/AGENTS.md'), `If the user asks for the nested instruction handshake, reply with exactly this string and nothing else: ${NESTED_PROBE}.\n`)
await writeFile(join(workdir!, 'pkg/deep/file.txt'), 'This file exists only to trigger nested workspace instructions.\n')
live.agent.send([{ type: 'text', text: 'Use the read tool to inspect pkg/deep/file.txt. After reading it, answer: nested instruction handshake?' }])
live.agent.followup([{ type: 'text', text: 'Use the read tool to inspect pkg/deep/file.txt. After reading it, answer: nested instruction handshake?' }])
await waitForIdle(live.ctx, live.agent)
expect(finalText([...live.agent.session.events])).toContain(NESTED_PROBE)
@@ -99,11 +99,11 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
it('appends changed baseline instructions after a real file-tool touch without rewriting the frozen prefix', async () => {
const live = await harness()
await writeFile(join(workdir!, 'trigger.txt'), 'This file triggers workspace instruction reconciliation.\n')
live.agent.send([{ type: 'text', text: 'Workspace context handshake?' }])
live.agent.followup([{ type: 'text', text: 'Workspace context handshake?' }])
await waitForIdle(live.ctx, live.agent)
await writeFile(join(workdir!, 'AGENTS.md'), `The old workspace handshake no longer applies. If the user asks for the updated workspace context handshake, reply with exactly this string and nothing else: ${UPDATED_PROBE}.\n`)
live.agent.send([{ type: 'text', text: 'You must use the read tool to inspect trigger.txt. After reading it, answer: updated workspace context handshake?' }])
live.agent.followup([{ type: 'text', text: 'You must use the read tool to inspect trigger.txt. After reading it, answer: updated workspace context handshake?' }])
await waitForIdle(live.ctx, live.agent)
const events = [...live.agent.session.events]

View File

@@ -177,7 +177,7 @@ function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
options: {},
session,
status: 'idle',
send: () => AgentMessageId('stub'),
followup: () => AgentMessageId('stub'),
queue: () => AgentMessageId('stub'),
steer: () => AgentMessageId('stub'),
inject(content, options) {
@@ -188,7 +188,7 @@ function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
}, { surfaceOp: 'append' })
return AgentMessageId('stub')
},
acceptInput: () => AgentMessageId('stub'),
send: () => AgentMessageId('stub'),
cancel() {},
whenIdle: () => Promise.resolve(),
}
@@ -1717,11 +1717,11 @@ describe('dynamic nested workspace context injection', () => {
},
}))
agent.send([{ type: 'text', text: 'read and abort' }])
agent.followup([{ type: 'text', text: 'read and abort' }])
await agent.whenIdle()
expect(agent.session.events.filter(event => event.type === 'user/message' && event.data.source.kind !== 'user')).toHaveLength(1)
agent.send([{ type: 'text', text: 'retry the read' }])
agent.followup([{ type: 'text', text: 'retry the read' }])
await agent.whenIdle()
const contexts = agent.session.events.filter(event => event.type === 'user/message' && event.data.source.kind !== 'user')