feat(web): declare the remaining context forms on every shipped producer
Four values complete the vocabulary, so the opaque body is reached only by producers that genuinely promise no shape. `snapshot` — current state a later snapshot supersedes. system-prompt now exposes `renderContextSections()`, the named contributions `renderContextSnapshot()` already joins for the model, so the body attributes each part to the subsystem that produced it instead of re-splitting joined prose. The runtime snapshot, time-context, and tmux-context declare it. `notice` — a one-off account of what just happened, declared by tool-tasks, goal state changes, tool-goal wrap-up, plan-mode switches, and repeat-tool-guard. Its `summary` rides the COLLAPSED row: these five are the majority of shipped producers and none of them needs expanding to be read. The task summary bounds itself because its inputs are unbounded caller text. `relay` — a message another agent addressed to this one; both subagent sources declare it and the body names the sender above what it said. `recall` — material lifted from another session's log. session-reference needed no new field: its references already record retained and omitted counts and the truncation flag, which the body shows first, because recalled context is bounded on the way in. `ContextFormed` is now discriminated by `form`, so a producer cannot declare a shape without the facts that shape is presented from — a notice without its summary, or a snapshot without its sections, fails to compile. Only the two hook bridges stay opaque, by design: their content is whatever an external program printed, so no shape can be promised for it. Unknown kinds and unreadable records land there too.
This commit is contained in:
@@ -200,7 +200,10 @@ export function apply(ctx: Context, config: Config): void {
|
||||
const text = count === thresholds[0]
|
||||
? GENTLE_REMINDER
|
||||
: detailedReminder(exec.name, count, previewArguments(canonical, argumentsPreviewChars))
|
||||
return createUserMessage({ content: [{ type: 'text', text }], source: PLUGIN_SOURCE })
|
||||
return createUserMessage({
|
||||
content: [{ type: 'text', text }],
|
||||
source: { ...PLUGIN_SOURCE, form: 'notice', summary: `${exec.name} × ${count}` },
|
||||
})
|
||||
}
|
||||
|
||||
// Observe-and-enrich, never veto: count first (state advances regardless of
|
||||
|
||||
@@ -45,7 +45,14 @@ function reminders(agent: Agent): { text: string; source: unknown }[] {
|
||||
}))
|
||||
}
|
||||
|
||||
const GUARD_SOURCE = { kind: 'plugin', plugin: 'repeat-tool-guard' }
|
||||
// The reminder is a `notice`-form context; its summary names the repeated
|
||||
// call so a reader sees it without expanding the row.
|
||||
const guardSource = (tool: string, count: number) => ({
|
||||
kind: 'plugin',
|
||||
plugin: 'repeat-tool-guard',
|
||||
form: 'notice',
|
||||
summary: `${tool} × ${count}`,
|
||||
})
|
||||
|
||||
describe('threshold escalation', () => {
|
||||
it('reminds gently at the first default threshold (3) and in detail at the second (5)', async () => {
|
||||
@@ -62,11 +69,11 @@ describe('threshold escalation', () => {
|
||||
const found = reminders(agent)
|
||||
expect(found).toHaveLength(2)
|
||||
expect(found[0]!.text).toContain('repeating the exact same tool call')
|
||||
expect(found[0]!.source).toEqual(GUARD_SOURCE)
|
||||
expect(found[0]!.source).toEqual(guardSource('probe', 3))
|
||||
expect(found[1]!.text).toContain('consecutive_calls: 5')
|
||||
expect(found[1]!.text).toContain('- tool: probe')
|
||||
expect(found[1]!.text).toContain('{"q":"same"}')
|
||||
expect(found[1]!.source).toEqual(GUARD_SOURCE)
|
||||
expect(found[1]!.source).toEqual(guardSource('probe', 5))
|
||||
})
|
||||
|
||||
it('keys the gentle text to thresholds[0], not the literal 3', async () => {
|
||||
@@ -327,7 +334,7 @@ describe('fold onto the downstream decision', () => {
|
||||
expect(found[0]!.text).toBe('downstream-ctx')
|
||||
expect(found[0]!.source).toEqual({ kind: 'plugin', plugin: 'test' })
|
||||
expect(found[1]!.text).toContain('repeating the exact same tool call')
|
||||
expect(found[1]!.source).toEqual(GUARD_SOURCE)
|
||||
expect(found[1]!.source).toEqual(guardSource('probe', 2))
|
||||
expect(found[2]).toEqual({ text: 'downstream-ctx', source: { kind: 'plugin', plugin: 'test' } })
|
||||
// The block's feedback reached the tool result unchanged.
|
||||
const results = [...agent.session.events].filter((e): e is SessionEvent<'tool/result'> => e.type === 'tool/result')
|
||||
|
||||
Reference in New Issue
Block a user