@namzu/sdk 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +253 -0
- package/dist/agents/ReactiveAgent.d.ts.map +1 -1
- package/dist/agents/ReactiveAgent.js +5 -0
- package/dist/agents/ReactiveAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts +22 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +47 -14
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
- package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +2 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +4 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/config/runtime.d.ts +24 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +9 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/builtins/webhook.test.js +17 -0
- package/dist/connector/builtins/webhook.test.js.map +1 -1
- package/dist/connector/index.d.ts +1 -0
- package/dist/connector/index.d.ts.map +1 -1
- package/dist/connector/index.js +1 -0
- package/dist/connector/index.js.map +1 -1
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +4 -0
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/connector/mcp/index.d.ts +1 -0
- package/dist/connector/mcp/index.d.ts.map +1 -1
- package/dist/connector/mcp/index.js +1 -0
- package/dist/connector/mcp/index.js.map +1 -1
- package/dist/connector/mcp/streamable-http.d.ts +22 -0
- package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.js +128 -0
- package/dist/connector/mcp/streamable-http.js.map +1 -0
- package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
- package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
- package/dist/connector/mcp/streamable-http.test.js +132 -0
- package/dist/connector/mcp/streamable-http.test.js.map +1 -0
- package/dist/{runtime/query → constants}/continuation.d.ts +5 -0
- package/dist/constants/continuation.d.ts.map +1 -0
- package/dist/{runtime/query → constants}/continuation.js +5 -0
- package/dist/constants/continuation.js.map +1 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +1 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +11 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +30 -1
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +18 -9
- package/dist/persona/assembler.js.map +1 -1
- package/dist/provider/capabilities.d.ts +27 -0
- package/dist/provider/capabilities.d.ts.map +1 -0
- package/dist/provider/capabilities.js +29 -0
- package/dist/provider/capabilities.js.map +1 -0
- package/dist/provider/collect.d.ts.map +1 -1
- package/dist/provider/collect.js +4 -1
- package/dist/provider/collect.js.map +1 -1
- package/dist/provider/index.d.ts +2 -0
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +1 -0
- package/dist/provider/index.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/registry/tool/execute.d.ts +27 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +124 -21
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/registry/tool/execute.test.d.ts +9 -6
- package/dist/registry/tool/execute.test.d.ts.map +1 -1
- package/dist/registry/tool/execute.test.js +72 -10
- package/dist/registry/tool/execute.test.js.map +1 -1
- package/dist/registry/toolset/catalog.test.js +62 -0
- package/dist/registry/toolset/catalog.test.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +8 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js +184 -0
- package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js +259 -0
- package/dist/runtime/query/__tests__/checkpoint-store.test.js.map +1 -0
- package/dist/runtime/query/__tests__/checkpoint.test.js +9 -3
- package/dist/runtime/query/__tests__/checkpoint.test.js.map +1 -1
- package/dist/runtime/query/__tests__/deferred-tools.test.js +43 -5
- package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
- package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js +110 -0
- package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js.map +1 -0
- package/dist/runtime/query/__tests__/prompt.test.js +15 -11
- package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +16 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +21 -7
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/context.d.ts +7 -0
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +1 -0
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +29 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +98 -8
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +3 -55
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +260 -656
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/checkpoint.js +24 -2
- package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +84 -3
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +13 -2
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +53 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
- package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
- package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
- package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
- package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
- package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +35 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.js +341 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -0
- package/dist/runtime/query/replay/list.d.ts +17 -1
- package/dist/runtime/query/replay/list.d.ts.map +1 -1
- package/dist/runtime/query/replay/list.js +21 -4
- package/dist/runtime/query/replay/list.js.map +1 -1
- package/dist/runtime/query/replay/prepare.d.ts +10 -0
- package/dist/runtime/query/replay/prepare.d.ts.map +1 -1
- package/dist/runtime/query/replay/prepare.js +20 -4
- package/dist/runtime/query/replay/prepare.js.map +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js +1 -1
- package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -1
- package/dist/session/workspace/shared-run.js +1 -0
- package/dist/session/workspace/shared-run.js.map +1 -1
- package/dist/skills/loader.test.js +8 -8
- package/dist/skills/loader.test.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +1 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/run/checkpoint-disk.d.ts +28 -0
- package/dist/store/run/checkpoint-disk.d.ts.map +1 -0
- package/dist/store/run/checkpoint-disk.js +55 -0
- package/dist/store/run/checkpoint-disk.js.map +1 -0
- package/dist/streaming/coalesce.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/payload-budget.test.js +48 -0
- package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
- package/dist/tools/builtins/__tests__/read-file.test.js +9 -0
- package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +50 -2
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/search-tools.d.ts.map +1 -1
- package/dist/tools/builtins/search-tools.js +32 -7
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
- package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
- package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
- package/dist/tools/coordinator/index.d.ts +8 -0
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +195 -15
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/index.d.ts.map +1 -1
- package/dist/types/agent/index.js +1 -0
- package/dist/types/agent/index.js.map +1 -1
- package/dist/types/agent/reactive.d.ts +15 -0
- package/dist/types/agent/reactive.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +18 -0
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/agent/working-memory.d.ts +29 -0
- package/dist/types/agent/working-memory.d.ts.map +1 -0
- package/dist/types/agent/working-memory.js +2 -0
- package/dist/types/agent/working-memory.js.map +1 -0
- package/dist/types/common/index.d.ts +10 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +18 -0
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/common/usage-merge.test.d.ts +2 -0
- package/dist/types/common/usage-merge.test.d.ts.map +1 -0
- package/dist/types/common/usage-merge.test.js +48 -0
- package/dist/types/common/usage-merge.test.js.map +1 -0
- package/dist/types/connector/mcp.d.ts +9 -2
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +42 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js +11 -0
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/provider/chat.d.ts +9 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +18 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +10 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/checkpoint-store.d.ts +64 -0
- package/dist/types/run/checkpoint-store.d.ts.map +1 -0
- package/dist/types/run/checkpoint-store.js +12 -0
- package/dist/types/run/checkpoint-store.js.map +1 -0
- package/dist/types/run/config.d.ts +27 -0
- package/dist/types/run/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +6 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/index.d.ts +1 -0
- package/dist/types/run/index.d.ts.map +1 -1
- package/dist/types/run/index.js +1 -0
- package/dist/types/run/index.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +25 -4
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/ReactiveAgent.ts +5 -0
- package/src/agents/SupervisorAgent.ts +56 -16
- package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
- package/src/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +5 -0
- package/src/config/runtime.ts +9 -0
- package/src/connector/builtins/webhook.test.ts +21 -0
- package/src/connector/index.ts +1 -0
- package/src/connector/mcp/client.ts +4 -0
- package/src/connector/mcp/index.ts +1 -0
- package/src/connector/mcp/streamable-http.test.ts +169 -0
- package/src/connector/mcp/streamable-http.ts +152 -0
- package/src/{runtime/query → constants}/continuation.ts +5 -0
- package/src/constants/index.ts +1 -0
- package/src/manager/run/persistence.ts +34 -1
- package/src/persona/assembler.ts +18 -6
- package/src/provider/capabilities.ts +47 -0
- package/src/provider/collect.ts +4 -1
- package/src/provider/index.ts +5 -0
- package/src/public-runtime.ts +5 -0
- package/src/registry/tool/execute.test.ts +85 -10
- package/src/registry/tool/execute.ts +124 -21
- package/src/registry/toolset/catalog.test.ts +68 -0
- package/src/run/reporter.ts +9 -0
- package/src/runtime/query/__tests__/capability-negotiation.test.ts +248 -0
- package/src/runtime/query/__tests__/checkpoint-store.test.ts +322 -0
- package/src/runtime/query/__tests__/checkpoint.test.ts +15 -6
- package/src/runtime/query/__tests__/deferred-tools.test.ts +52 -5
- package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
- package/src/runtime/query/__tests__/orphan-task-turn-end.test.ts +127 -0
- package/src/runtime/query/__tests__/prompt.test.ts +15 -11
- package/src/runtime/query/checkpoint.ts +23 -9
- package/src/runtime/query/context.ts +9 -0
- package/src/runtime/query/index.ts +148 -29
- package/src/runtime/query/iteration/index.ts +316 -831
- package/src/runtime/query/iteration/phases/checkpoint.ts +32 -2
- package/src/runtime/query/iteration/phases/compaction.ts +89 -3
- package/src/runtime/query/iteration/phases/context.ts +59 -3
- package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
- package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
- package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
- package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
- package/src/runtime/query/iteration/stream-turn.ts +396 -0
- package/src/runtime/query/replay/list.ts +38 -4
- package/src/runtime/query/replay/prepare.ts +37 -4
- package/src/session/workspace/__tests__/shared-run.test.ts +1 -1
- package/src/session/workspace/shared-run.ts +1 -0
- package/src/skills/loader.test.ts +8 -8
- package/src/store/index.ts +1 -0
- package/src/store/run/checkpoint-disk.ts +67 -0
- package/src/streaming/coalesce.test.ts +3 -3
- package/src/tools/builtins/__tests__/payload-budget.test.ts +63 -0
- package/src/tools/builtins/__tests__/read-file.test.ts +12 -0
- package/src/tools/builtins/read-file.ts +50 -2
- package/src/tools/builtins/search-tools.ts +38 -7
- package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
- package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
- package/src/tools/coordinator/index.ts +227 -17
- package/src/types/agent/index.ts +1 -0
- package/src/types/agent/reactive.ts +17 -0
- package/src/types/agent/supervisor.ts +20 -0
- package/src/types/agent/working-memory.ts +29 -0
- package/src/types/common/index.ts +19 -0
- package/src/types/common/usage-merge.test.ts +53 -0
- package/src/types/connector/mcp.ts +14 -2
- package/src/types/hitl/index.ts +52 -1
- package/src/types/provider/chat.ts +10 -0
- package/src/types/provider/config.ts +18 -0
- package/src/types/provider/interface.ts +11 -0
- package/src/types/run/checkpoint-store.ts +72 -0
- package/src/types/run/config.ts +30 -0
- package/src/types/run/events.ts +13 -0
- package/src/types/run/index.ts +1 -0
- package/src/version.ts +26 -4
- package/dist/runtime/query/continuation.d.ts.map +0 -1
- package/dist/runtime/query/continuation.js.map +0 -1
|
@@ -1,13 +1,43 @@
|
|
|
1
1
|
import type { RunEvent } from '../../../../types/run/index.js'
|
|
2
2
|
import { CheckpointManager } from '../../checkpoint.js'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type IterationContext,
|
|
5
|
+
type PhaseSignal,
|
|
6
|
+
awaitDecisionOrAbort,
|
|
7
|
+
handleHITLDecision,
|
|
8
|
+
} from './context.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Cadence gate for the per-iteration checkpoint (`runConfig.checkpointEvery`,
|
|
12
|
+
* default 1 = every iteration). Off-cadence iterations skip the whole phase —
|
|
13
|
+
* no checkpoint, no `checkpoint_created` event, and no HITL
|
|
14
|
+
* `iteration_checkpoint` park (there is no checkpoint id to park on).
|
|
15
|
+
* Iterations 1, 1+N, 1+2N, … checkpoint, so the first tool iteration is
|
|
16
|
+
* always covered (a crash before the first cadence hit would otherwise leave
|
|
17
|
+
* nothing to resume from).
|
|
18
|
+
*/
|
|
19
|
+
function isOnCheckpointCadence(iterationNum: number, checkpointEvery: number | undefined): boolean {
|
|
20
|
+
const every = Math.max(1, Math.floor(checkpointEvery ?? 1))
|
|
21
|
+
return (iterationNum - 1) % every === 0
|
|
22
|
+
}
|
|
4
23
|
|
|
5
24
|
export async function* runIterationCheckpoint(
|
|
6
25
|
ctx: IterationContext,
|
|
7
26
|
iterationNum: number,
|
|
8
27
|
): AsyncGenerator<RunEvent, PhaseSignal> {
|
|
28
|
+
if (!isOnCheckpointCadence(iterationNum, ctx.runConfig.checkpointEvery)) {
|
|
29
|
+
return 'continue'
|
|
30
|
+
}
|
|
31
|
+
|
|
9
32
|
const iterCheckpoint = await ctx.checkpointMgr.create(ctx.runMgr, iterationNum)
|
|
10
33
|
|
|
34
|
+
// Growth control: keep only the newest N checkpoints when the host asked
|
|
35
|
+
// for pruning. Default undefined ⇒ never prune (today's behavior).
|
|
36
|
+
const pruneKeepLast = ctx.runConfig.pruneKeepLast
|
|
37
|
+
if (pruneKeepLast !== undefined && pruneKeepLast >= 1) {
|
|
38
|
+
await ctx.checkpointMgr.prune(Math.floor(pruneKeepLast))
|
|
39
|
+
}
|
|
40
|
+
|
|
11
41
|
await ctx.emitEvent({
|
|
12
42
|
type: 'checkpoint_created',
|
|
13
43
|
runId: ctx.runMgr.id,
|
|
@@ -17,7 +47,7 @@ export async function* runIterationCheckpoint(
|
|
|
17
47
|
yield* ctx.drainPending()
|
|
18
48
|
|
|
19
49
|
const summary = CheckpointManager.buildSummary(ctx.runMgr, iterationNum)
|
|
20
|
-
const iterDecision = await ctx
|
|
50
|
+
const iterDecision = await awaitDecisionOrAbort(ctx, {
|
|
21
51
|
type: 'iteration_checkpoint',
|
|
22
52
|
runId: ctx.runMgr.id,
|
|
23
53
|
checkpointId: iterCheckpoint.id,
|
|
@@ -1,12 +1,37 @@
|
|
|
1
|
+
import { findSafeTrimIndex } from '../../../../compaction/dangling.js'
|
|
1
2
|
import { serializeState } from '../../../../compaction/serializer.js'
|
|
2
3
|
import { buildVerifiedSummary } from '../../../../compaction/verifier.js'
|
|
3
4
|
import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
|
|
4
5
|
import { createSystemMessage } from '../../../../types/message/index.js'
|
|
5
6
|
import type { IterationContext } from './context.js'
|
|
7
|
+
import { isWorkingMemoryMessage } from './working-memory.js'
|
|
6
8
|
|
|
7
9
|
const COMPACTION_HEADER =
|
|
8
10
|
'[COMPACTED CONTEXT] The following is a structured summary of the conversation so far.'
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Identity check for a prior compaction summary in the leading floor. Used to
|
|
14
|
+
* REPLACE one in place on the per-iteration (contextWindowTokens) path so at
|
|
15
|
+
* most one `[COMPACTED CONTEXT]` block ever lives in the never-trimmed floor,
|
|
16
|
+
* and by the checkpoint-restore path to PRESERVE the summary across a resume
|
|
17
|
+
* (it is the only surviving record of the older history the run compacted away).
|
|
18
|
+
*/
|
|
19
|
+
export function isCompactionMessage(content: string | null | undefined): boolean {
|
|
20
|
+
return typeof content === 'string' && content.startsWith(COMPACTION_HEADER)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Minimum number of compactable (older) messages required before a compaction
|
|
25
|
+
* pass is worth running. When there is NOTHING between the never-trimmed
|
|
26
|
+
* leading floor and the recent window, a pass would only replace nothing with a
|
|
27
|
+
* `[COMPACTED CONTEXT]` summary that joins the floor — pure overhead that fires
|
|
28
|
+
* again next iteration (the permanent-floor thrash, ses_055 D7). Set to the
|
|
29
|
+
* literal EMPTY guard so any existing compaction consumer with ≥1 older message
|
|
30
|
+
* stays byte-identical; the per-iteration Vandal path additionally defangs the
|
|
31
|
+
* cost with `llmVerification:false`.
|
|
32
|
+
*/
|
|
33
|
+
const MIN_OLDER_MESSAGES_TO_COMPACT = 1
|
|
34
|
+
|
|
10
35
|
function estimateTokens(ctx: IterationContext): number {
|
|
11
36
|
let chars = 0
|
|
12
37
|
for (const msg of ctx.runMgr.messages) {
|
|
@@ -31,7 +56,13 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
|
|
|
31
56
|
if (!manager) return
|
|
32
57
|
|
|
33
58
|
const estimatedTokens = estimateTokens(ctx)
|
|
34
|
-
|
|
59
|
+
// F1: measure the CURRENT window against the model context window when the
|
|
60
|
+
// host supplies one, falling back to the run-level cumulative `tokenBudget`
|
|
61
|
+
// otherwise. Repointing the SINGLE `budget` assignment moves BOTH the
|
|
62
|
+
// `<= 0` guard AND the divisor at once — so "compaction on + tokenBudget=0"
|
|
63
|
+
// is no longer a silent no-op. Existing consumers (no contextWindowTokens)
|
|
64
|
+
// stay byte-identical.
|
|
65
|
+
const budget = config.contextWindowTokens ?? ctx.runConfig.tokenBudget
|
|
35
66
|
if (budget <= 0) return
|
|
36
67
|
|
|
37
68
|
const usage = estimatedTokens / budget
|
|
@@ -63,10 +94,34 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
|
|
|
63
94
|
}
|
|
64
95
|
if (systemMessages.length === 0) return
|
|
65
96
|
|
|
66
|
-
|
|
97
|
+
// Tool-pair atomicity guard. A naive cut at `length - keepRecentMessages`
|
|
98
|
+
// can land BETWEEN an assistant-with-toolCalls (which would be dropped into
|
|
99
|
+
// `olderMessages`) and its `tool` results (kept in `recentMessages`),
|
|
100
|
+
// leaving orphaned `tool_result` blocks at the head of the recent window.
|
|
101
|
+
// The Anthropic provider then emits a `tool_result` with no matching
|
|
102
|
+
// `tool_use` and the API rejects the next turn with a 400 — so compaction,
|
|
103
|
+
// whose whole job is to keep a long run alive, instead kills it. Snap the
|
|
104
|
+
// boundary FORWARD to a safe point (existing `findSafeTrimIndex`, previously
|
|
105
|
+
// only wired to the unused ConversationManager strategy classes) so no pair
|
|
106
|
+
// is split. Any message this moves out of the recent window is already
|
|
107
|
+
// represented in the extracted WorkingState the summary is built from.
|
|
108
|
+
const keepStart = findSafeTrimIndex(messages, messages.length - config.keepRecentMessages)
|
|
67
109
|
const recentMessages = messages.slice(keepStart)
|
|
68
110
|
const olderMessages = messages.slice(systemMessages.length, keepStart)
|
|
69
111
|
|
|
112
|
+
// D7: nothing meaningful to compact — skip instead of thrashing the
|
|
113
|
+
// permanent leading floor every iteration (and avoid an LLM verification
|
|
114
|
+
// call when there is no older history to summarize). Scoped to the new
|
|
115
|
+
// contextWindowTokens path so any existing consumer (tokenBudget-only) keeps
|
|
116
|
+
// its exact prior behavior — byte-identical (ses_055 verify).
|
|
117
|
+
if (config.contextWindowTokens != null && olderMessages.length < MIN_OLDER_MESSAGES_TO_COMPACT) {
|
|
118
|
+
ctx.log.debug('Skipping compaction — too few older messages', {
|
|
119
|
+
runId: ctx.runMgr.id,
|
|
120
|
+
olderMessages: olderMessages.length,
|
|
121
|
+
})
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
70
125
|
let compactedContent: string
|
|
71
126
|
|
|
72
127
|
if (config.llmVerification && manager.slotCount() < config.richStateThreshold) {
|
|
@@ -77,7 +132,38 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
|
|
|
77
132
|
|
|
78
133
|
const compactionMessage = createSystemMessage(`${COMPACTION_HEADER}\n\n${compactedContent}`)
|
|
79
134
|
|
|
80
|
-
|
|
135
|
+
// On the new per-iteration (contextWindowTokens) path, drop any PRIOR
|
|
136
|
+
// `[COMPACTED CONTEXT]` summary from the leading floor — `serializeState` is
|
|
137
|
+
// cumulative, so the new summary supersedes it. Without this the never-trimmed
|
|
138
|
+
// floor accumulates one redundant summary per pass and the frequent
|
|
139
|
+
// per-iteration trigger makes that bloat unbounded (ses_055 verify, MEDIUM).
|
|
140
|
+
// Legacy consumers (no contextWindowTokens) keep their exact prior
|
|
141
|
+
// accumulation behavior — byte-identical.
|
|
142
|
+
const preservedSystem =
|
|
143
|
+
config.contextWindowTokens != null
|
|
144
|
+
? systemMessages.filter((m) => !isCompactionMessage(m.content))
|
|
145
|
+
: systemMessages
|
|
146
|
+
const newMessages = [...preservedSystem, compactionMessage, ...recentMessages]
|
|
147
|
+
|
|
148
|
+
// OPAQUE survival guard (ses_055 D1): the pinned working-memory slot is a
|
|
149
|
+
// leading system message, so it is kept in `preservedSystem` (the compaction
|
|
150
|
+
// filter only drops prior `[COMPACTED CONTEXT]` summaries, never the WM slot)
|
|
151
|
+
// and survives for free — this branch is DEFENSIVE-ONLY, exercised only if a
|
|
152
|
+
// future change drops the slot from the rebuilt set. It re-pins the block
|
|
153
|
+
// already present in `messages` (the one `refreshWorkingMemory` placed).
|
|
154
|
+
// Identity is the sentinel HEADER only — no path parsing, no second provider
|
|
155
|
+
// call, no host format knowledge in the SDK.
|
|
156
|
+
const survives = newMessages.some((m) => m.role === 'system' && isWorkingMemoryMessage(m.content))
|
|
157
|
+
if (!survives) {
|
|
158
|
+
const priorSlot = messages.find((m) => m.role === 'system' && isWorkingMemoryMessage(m.content))
|
|
159
|
+
if (priorSlot) {
|
|
160
|
+
// Re-pin as the last leading system message, before the summary.
|
|
161
|
+
newMessages.splice(preservedSystem.length, 0, priorSlot)
|
|
162
|
+
ctx.log.warn('Re-pinned working-memory slot dropped by compaction', {
|
|
163
|
+
runId: ctx.runMgr.id,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
81
167
|
|
|
82
168
|
const oldCount = messages.length
|
|
83
169
|
messages.length = 0
|
|
@@ -5,7 +5,8 @@ import type { CompactionConfig } from '../../../../config/runtime.js'
|
|
|
5
5
|
import type { PlanManager } from '../../../../manager/plan/lifecycle.js'
|
|
6
6
|
import type { RunPersistence } from '../../../../manager/run/persistence.js'
|
|
7
7
|
import type { ActivityStore } from '../../../../store/activity/memory.js'
|
|
8
|
-
import type { TaskGateway
|
|
8
|
+
import type { TaskGateway } from '../../../../types/agent/gateway.js'
|
|
9
|
+
import type { WorkingMemoryProvider } from '../../../../types/agent/working-memory.js'
|
|
9
10
|
import type { HITLResumeDecision, ResumeHandler } from '../../../../types/hitl/index.js'
|
|
10
11
|
import type { TaskId } from '../../../../types/ids/index.js'
|
|
11
12
|
import type { LLMProvider } from '../../../../types/provider/index.js'
|
|
@@ -55,14 +56,14 @@ export interface IterationContext {
|
|
|
55
56
|
|
|
56
57
|
readonly taskStore?: TaskStore
|
|
57
58
|
|
|
58
|
-
readonly pendingNotifications: TaskHandle[]
|
|
59
|
-
|
|
60
59
|
readonly launchedTasks: Map<TaskId, LaunchedTaskMeta>
|
|
61
60
|
|
|
62
61
|
readonly compactionConfig?: CompactionConfig
|
|
63
62
|
|
|
64
63
|
readonly workingStateManager?: WorkingStateManager
|
|
65
64
|
|
|
65
|
+
readonly workingMemoryProvider?: WorkingMemoryProvider
|
|
66
|
+
|
|
66
67
|
readonly advisoryCtx?: AdvisoryContext
|
|
67
68
|
|
|
68
69
|
readonly agentBus?: AgentBus
|
|
@@ -74,6 +75,57 @@ export interface IterationContext {
|
|
|
74
75
|
|
|
75
76
|
export type PhaseSignal = 'continue' | 'stop'
|
|
76
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Await a HITL `resumeHandler` decision, but RACE it against the run's abort
|
|
80
|
+
* signal. A Stop that arrives while the run is parked on a tool-review or
|
|
81
|
+
* iteration checkpoint used to do nothing until the host eventually answered
|
|
82
|
+
* (the park await was not cancellable). Racing the signal lets a Stop resolve
|
|
83
|
+
* the park immediately as an `abort` decision, which `handleHITLDecision`
|
|
84
|
+
* turns into `setStopReason('cancelled') + markCancelled + stop`. Fails closed:
|
|
85
|
+
* a resume-handler rejection also resolves to `abort` rather than hanging.
|
|
86
|
+
*/
|
|
87
|
+
export async function awaitDecisionOrAbort(
|
|
88
|
+
ctx: IterationContext,
|
|
89
|
+
request: Parameters<ResumeHandler>[0],
|
|
90
|
+
): Promise<HITLResumeDecision> {
|
|
91
|
+
const signal = ctx.abortController?.signal
|
|
92
|
+
// No abort signal wired (e.g. a minimal test harness) → behave exactly as a
|
|
93
|
+
// direct resumeHandler await, no race. In production RunContextFactory always
|
|
94
|
+
// provides the controller, so the race below is live.
|
|
95
|
+
if (!signal) return ctx.resumeHandler(request)
|
|
96
|
+
const abortDecision: HITLResumeDecision = {
|
|
97
|
+
action: 'abort',
|
|
98
|
+
reason: 'run aborted while parked for HITL',
|
|
99
|
+
}
|
|
100
|
+
if (signal.aborted) return abortDecision
|
|
101
|
+
return new Promise<HITLResumeDecision>((resolve) => {
|
|
102
|
+
let settled = false
|
|
103
|
+
const onAbort = (): void => {
|
|
104
|
+
if (settled) return
|
|
105
|
+
settled = true
|
|
106
|
+
resolve(abortDecision)
|
|
107
|
+
}
|
|
108
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
109
|
+
Promise.resolve(ctx.resumeHandler(request)).then(
|
|
110
|
+
(decision) => {
|
|
111
|
+
if (settled) return
|
|
112
|
+
settled = true
|
|
113
|
+
signal.removeEventListener('abort', onAbort)
|
|
114
|
+
resolve(decision)
|
|
115
|
+
},
|
|
116
|
+
(err) => {
|
|
117
|
+
if (settled) return
|
|
118
|
+
settled = true
|
|
119
|
+
signal.removeEventListener('abort', onAbort)
|
|
120
|
+
resolve({
|
|
121
|
+
action: 'abort',
|
|
122
|
+
reason: err instanceof Error ? err.message : 'resume handler failed',
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
77
129
|
export async function* handleHITLDecision(
|
|
78
130
|
ctx: IterationContext,
|
|
79
131
|
decision: HITLResumeDecision,
|
|
@@ -125,6 +177,10 @@ export async function* handleHITLDecision(
|
|
|
125
177
|
case 'approve_tools':
|
|
126
178
|
case 'modify_tools':
|
|
127
179
|
case 'reject_tools':
|
|
180
|
+
// 'answer_question' can only arrive misdirected at an iteration
|
|
181
|
+
// checkpoint (answers are consumed inside the ask_user_question
|
|
182
|
+
// tool's own park); treat it as a plain continue.
|
|
183
|
+
case 'answer_question':
|
|
128
184
|
return 'continue'
|
|
129
185
|
default: {
|
|
130
186
|
const _exhaustive: never = decision
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { HITLResumeDecision } from '../../../../types/hitl/index.js'
|
|
4
|
+
import type { RunId } from '../../../../types/ids/index.js'
|
|
5
|
+
import { type IterationContext, awaitDecisionOrAbort } from './context.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* C12: a Stop that arrives while the run is parked on a HITL decision must
|
|
9
|
+
* interrupt the park (resolve as `abort`) instead of hanging until the host
|
|
10
|
+
* eventually answers. `awaitDecisionOrAbort` races `resumeHandler` against
|
|
11
|
+
* `ctx.abortController.signal`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function ctxWith(opts: {
|
|
15
|
+
controller?: AbortController
|
|
16
|
+
resumeHandler: IterationContext['resumeHandler']
|
|
17
|
+
}): IterationContext {
|
|
18
|
+
return {
|
|
19
|
+
abortController: opts.controller,
|
|
20
|
+
resumeHandler: opts.resumeHandler,
|
|
21
|
+
runMgr: { id: 'run_abort_test' as RunId },
|
|
22
|
+
} as unknown as IterationContext
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const REVIEW_REQUEST = {
|
|
26
|
+
type: 'tool_review' as const,
|
|
27
|
+
runId: 'run_abort_test' as RunId,
|
|
28
|
+
checkpointId: 'cp_1' as `cp_${string}`,
|
|
29
|
+
toolCalls: [],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('awaitDecisionOrAbort (C12 — cancellable HITL park)', () => {
|
|
33
|
+
it('returns abort immediately when the signal is already aborted', async () => {
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
controller.abort()
|
|
36
|
+
const ctx = ctxWith({
|
|
37
|
+
controller,
|
|
38
|
+
// A handler that never resolves — proving we did not wait on it.
|
|
39
|
+
resumeHandler: () => new Promise<HITLResumeDecision>(() => {}),
|
|
40
|
+
})
|
|
41
|
+
const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
|
|
42
|
+
expect(decision.action).toBe('abort')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('resolves as abort when the signal fires DURING the park', async () => {
|
|
46
|
+
const controller = new AbortController()
|
|
47
|
+
const ctx = ctxWith({
|
|
48
|
+
controller,
|
|
49
|
+
resumeHandler: () => new Promise<HITLResumeDecision>(() => {}), // never answers
|
|
50
|
+
})
|
|
51
|
+
const pending = awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
|
|
52
|
+
controller.abort()
|
|
53
|
+
const decision = await pending
|
|
54
|
+
expect(decision.action).toBe('abort')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('returns the real decision when the handler answers before any abort', async () => {
|
|
58
|
+
const controller = new AbortController()
|
|
59
|
+
const ctx = ctxWith({
|
|
60
|
+
controller,
|
|
61
|
+
resumeHandler: async () => ({ action: 'approve_tools' }) as HITLResumeDecision,
|
|
62
|
+
})
|
|
63
|
+
const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
|
|
64
|
+
expect(decision.action).toBe('approve_tools')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('degrades to a direct resumeHandler await when no controller is wired', async () => {
|
|
68
|
+
const ctx = ctxWith({
|
|
69
|
+
resumeHandler: async () => ({ action: 'reject_tools' }) as HITLResumeDecision,
|
|
70
|
+
})
|
|
71
|
+
const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
|
|
72
|
+
expect(decision.action).toBe('reject_tools')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('fails closed to abort when the resume handler rejects', async () => {
|
|
76
|
+
const controller = new AbortController()
|
|
77
|
+
const ctx = ctxWith({
|
|
78
|
+
controller,
|
|
79
|
+
resumeHandler: async () => {
|
|
80
|
+
throw new Error('handler boom')
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
|
|
84
|
+
expect(decision.action).toBe('abort')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { type HITLResumeDecision, autoApproveHandler } from '../../../../types/hitl/index.js'
|
|
4
|
+
import type { CheckpointId, RunId } from '../../../../types/ids/index.js'
|
|
5
|
+
import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
|
|
6
|
+
import type { RunEvent } from '../../../../types/run/index.js'
|
|
7
|
+
import { type IterationContext, handleHITLDecision } from './context.js'
|
|
8
|
+
import { runToolReview } from './tool-review.js'
|
|
9
|
+
|
|
10
|
+
async function drainGenerator<TReturn>(
|
|
11
|
+
gen: AsyncGenerator<RunEvent, TReturn>,
|
|
12
|
+
): Promise<{ events: RunEvent[]; value: TReturn }> {
|
|
13
|
+
const events: RunEvent[] = []
|
|
14
|
+
let next = await gen.next()
|
|
15
|
+
while (!next.done) {
|
|
16
|
+
events.push(next.value)
|
|
17
|
+
next = await gen.next()
|
|
18
|
+
}
|
|
19
|
+
return { events, value: next.value }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('autoApproveHandler user_question case', () => {
|
|
23
|
+
it('returns the non-fabricating no-answer sentinel with the questionId echoed', async () => {
|
|
24
|
+
const decision = await autoApproveHandler({
|
|
25
|
+
type: 'user_question',
|
|
26
|
+
runId: 'run_headless_test' as RunId,
|
|
27
|
+
checkpointId: 'cp_question_toolu_1' as CheckpointId,
|
|
28
|
+
question: {
|
|
29
|
+
questionId: 'toolu_1',
|
|
30
|
+
question: 'Pick one?',
|
|
31
|
+
options: [
|
|
32
|
+
{ id: 'opt_1', label: 'A' },
|
|
33
|
+
{ id: 'opt_2', label: 'B' },
|
|
34
|
+
],
|
|
35
|
+
multiSelect: false,
|
|
36
|
+
allowFreeText: true,
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expect(decision).toEqual({
|
|
41
|
+
action: 'answer_question',
|
|
42
|
+
selectedOptionIds: [],
|
|
43
|
+
freeText: 'No user is available to answer. Proceed using your best judgment.',
|
|
44
|
+
questionId: 'toolu_1',
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('handleHITLDecision answer_question case', () => {
|
|
50
|
+
it("treats a misdirected 'answer_question' at an iteration checkpoint as continue", async () => {
|
|
51
|
+
// The branch returns without touching the context — answers are
|
|
52
|
+
// consumed inside the ask_user_question tool's own park, so an
|
|
53
|
+
// answer arriving here can only be misdirected.
|
|
54
|
+
const ctx = {} as IterationContext
|
|
55
|
+
const { events, value } = await drainGenerator(
|
|
56
|
+
handleHITLDecision(
|
|
57
|
+
ctx,
|
|
58
|
+
{ action: 'answer_question', selectedOptionIds: ['opt_1'] },
|
|
59
|
+
'cp_checkpoint_1',
|
|
60
|
+
'iteration checkpoint',
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
expect(value).toBe('continue')
|
|
64
|
+
expect(events).toEqual([])
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('runToolReview answer_question case', () => {
|
|
69
|
+
function reviewContext(decision: HITLResumeDecision) {
|
|
70
|
+
const warns: string[] = []
|
|
71
|
+
const pushed: unknown[] = []
|
|
72
|
+
let batches = 0
|
|
73
|
+
const ctx = {
|
|
74
|
+
tools: { get: () => undefined },
|
|
75
|
+
checkpointMgr: {
|
|
76
|
+
create: async () => ({ id: 'cp_review_1' as CheckpointId }),
|
|
77
|
+
},
|
|
78
|
+
emitEvent: async () => {},
|
|
79
|
+
drainPending: (): Generator<RunEvent> => [][Symbol.iterator]() as Generator<RunEvent>,
|
|
80
|
+
resumeHandler: async () => decision,
|
|
81
|
+
log: {
|
|
82
|
+
debug: () => {},
|
|
83
|
+
info: () => {},
|
|
84
|
+
warn: (message: string) => {
|
|
85
|
+
warns.push(message)
|
|
86
|
+
},
|
|
87
|
+
error: () => {},
|
|
88
|
+
},
|
|
89
|
+
runMgr: {
|
|
90
|
+
id: 'run_tool_review_test' as RunId,
|
|
91
|
+
pushMessage: (message: unknown) => {
|
|
92
|
+
pushed.push(message)
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
toolExecutor: {
|
|
96
|
+
executeBatch: async () => {
|
|
97
|
+
batches += 1
|
|
98
|
+
return { messages: [], results: [] }
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
} as unknown as IterationContext
|
|
102
|
+
return { ctx, warns, pushed, batchCount: () => batches }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const response: ChatCompletionResponse = {
|
|
106
|
+
id: 'resp_1',
|
|
107
|
+
model: 'test-model',
|
|
108
|
+
message: {
|
|
109
|
+
role: 'assistant',
|
|
110
|
+
content: null,
|
|
111
|
+
toolCalls: [
|
|
112
|
+
{
|
|
113
|
+
id: 'toolu_review_1',
|
|
114
|
+
type: 'function',
|
|
115
|
+
function: { name: 'ask_user_question', arguments: '{}' },
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
finishReason: 'tool_calls',
|
|
120
|
+
usage: {
|
|
121
|
+
promptTokens: 0,
|
|
122
|
+
completionTokens: 0,
|
|
123
|
+
totalTokens: 0,
|
|
124
|
+
cachedTokens: 0,
|
|
125
|
+
cacheWriteTokens: 0,
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
it("warns and executes the batch on a misdirected 'answer_question' instead of stalling", async () => {
|
|
130
|
+
const { ctx, warns, batchCount } = reviewContext({
|
|
131
|
+
action: 'answer_question',
|
|
132
|
+
selectedOptionIds: [],
|
|
133
|
+
})
|
|
134
|
+
const { value } = await drainGenerator(runToolReview(ctx, response, 1))
|
|
135
|
+
expect(value).toBe('executed')
|
|
136
|
+
expect(batchCount()).toBe(1)
|
|
137
|
+
expect(warns).toHaveLength(1)
|
|
138
|
+
})
|
|
139
|
+
})
|
|
@@ -2,7 +2,7 @@ import { createUserMessage } from '../../../../types/message/index.js'
|
|
|
2
2
|
import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
|
|
3
3
|
import type { RunEvent } from '../../../../types/run/index.js'
|
|
4
4
|
import type { VerificationGate } from '../../../../verification/index.js'
|
|
5
|
-
import type
|
|
5
|
+
import { type IterationContext, awaitDecisionOrAbort } from './context.js'
|
|
6
6
|
|
|
7
7
|
interface VerificationAwareContext extends IterationContext {
|
|
8
8
|
readonly verificationGate?: VerificationGate
|
|
@@ -94,7 +94,7 @@ export async function* runToolReview(
|
|
|
94
94
|
})
|
|
95
95
|
yield* ctx.drainPending()
|
|
96
96
|
|
|
97
|
-
const reviewDecision = await ctx
|
|
97
|
+
const reviewDecision = await awaitDecisionOrAbort(ctx, {
|
|
98
98
|
type: 'tool_review',
|
|
99
99
|
runId: ctx.runMgr.id,
|
|
100
100
|
checkpointId: reviewCheckpoint.id,
|
|
@@ -198,7 +198,11 @@ export async function* runToolReview(
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
case 'approve_plan':
|
|
201
|
-
case 'reject_plan':
|
|
201
|
+
case 'reject_plan':
|
|
202
|
+
// 'answer_question' belongs to an ask_user_question park, not a
|
|
203
|
+
// tool review — like the misdirected plan decisions above, warn
|
|
204
|
+
// and proceed with execution rather than stalling the run.
|
|
205
|
+
case 'answer_question': {
|
|
202
206
|
ctx.log.warn('Unexpected plan decision during tool review', {
|
|
203
207
|
action: reviewDecision.action,
|
|
204
208
|
})
|