@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,33 +1,13 @@
|
|
|
1
1
|
import { SpanStatusCode } from '@opentelemetry/api'
|
|
2
|
-
import type { AdvisoryContext } from '../../../advisory/context.js'
|
|
3
2
|
import { extractFromAssistantMessage } from '../../../compaction/extractor.js'
|
|
4
|
-
import
|
|
5
|
-
import type { CompactionConfig } from '../../../config/runtime.js'
|
|
6
|
-
import type { PlanManager } from '../../../manager/plan/lifecycle.js'
|
|
7
|
-
import type { RunPersistence } from '../../../manager/run/persistence.js'
|
|
3
|
+
import { AUTO_CONTINUATION_USER_MESSAGE } from '../../../constants/continuation.js'
|
|
8
4
|
import { collect } from '../../../provider/collect.js'
|
|
9
|
-
import type { ActivityStore } from '../../../store/activity/memory.js'
|
|
10
5
|
import { GENAI, NAMZU, agentIterationSpanName } from '../../../telemetry/attributes.js'
|
|
11
6
|
import { getTracer } from '../../../telemetry/runtime-accessors.js'
|
|
12
|
-
import type { ResumeHandler } from '../../../types/hitl/index.js'
|
|
13
|
-
import type { ToolUseId } from '../../../types/ids/index.js'
|
|
14
7
|
import { createAssistantMessage, createUserMessage } from '../../../types/message/index.js'
|
|
15
|
-
import type {
|
|
16
|
-
ChatCompletionResponse,
|
|
17
|
-
LLMProvider,
|
|
18
|
-
StreamChunk,
|
|
19
|
-
} from '../../../types/provider/index.js'
|
|
20
|
-
import type { AgentRunConfig, RunEvent, StopReason } from '../../../types/run/index.js'
|
|
21
|
-
import type { MessageStopReason } from '../../../types/run/stop-reason.js'
|
|
22
|
-
import type { ToolRegistryContract } from '../../../types/tool/index.js'
|
|
8
|
+
import type { RunEvent, StopReason } from '../../../types/run/index.js'
|
|
23
9
|
import { toErrorMessage } from '../../../utils/error.js'
|
|
24
10
|
import { generateMessageId } from '../../../utils/id.js'
|
|
25
|
-
import type { Logger } from '../../../utils/logger.js'
|
|
26
|
-
import type { CheckpointManager } from '../checkpoint.js'
|
|
27
|
-
import { AUTO_CONTINUATION_USER_MESSAGE } from '../continuation.js'
|
|
28
|
-
import type { EmitEvent } from '../events.js'
|
|
29
|
-
import type { ToolExecutor } from '../executor.js'
|
|
30
|
-
import type { GuardCoordinator } from '../guard.js'
|
|
31
11
|
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
32
12
|
import { runAdvisoryPhase } from './phases/advisory.js'
|
|
33
13
|
import { runIterationCheckpoint } from './phases/checkpoint.js'
|
|
@@ -35,735 +15,274 @@ import { runCompactionCheck } from './phases/compaction.js'
|
|
|
35
15
|
import type { IterationContext } from './phases/index.js'
|
|
36
16
|
import { runPlanGate } from './phases/plan.js'
|
|
37
17
|
import { runToolReview } from './phases/tool-review.js'
|
|
18
|
+
import { refreshWorkingMemory } from './phases/working-memory.js'
|
|
19
|
+
import { streamProviderTurn } from './stream-turn.js'
|
|
38
20
|
|
|
39
21
|
export type { IterationContext } from './phases/index.js'
|
|
40
22
|
export type { PhaseSignal } from './phases/index.js'
|
|
41
23
|
export type { ToolReviewOutcome } from './phases/index.js'
|
|
42
24
|
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
runConfig: AgentRunConfig
|
|
46
|
-
tools: ToolRegistryContract
|
|
47
|
-
allowedTools?: string[]
|
|
48
|
-
taskGateway?: import('../../../types/agent/gateway.js').TaskGateway
|
|
49
|
-
taskStore?: import('../../../types/task/index.js').TaskStore
|
|
50
|
-
launchedTasks?: Map<
|
|
51
|
-
import('../../../types/ids/index.js').TaskId,
|
|
52
|
-
import('./phases/context.js').LaunchedTaskMeta
|
|
53
|
-
>
|
|
54
|
-
compactionConfig?: CompactionConfig
|
|
55
|
-
workingStateManager?: WorkingStateManager
|
|
56
|
-
advisoryCtx?: AdvisoryContext
|
|
57
|
-
agentBus?: import('../../../bus/index.js').AgentBus
|
|
58
|
-
verificationGate?: import('../../../verification/gate.js').VerificationGate
|
|
59
|
-
pluginManager?: import('../../../plugin/lifecycle.js').PluginLifecycleManager
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Escape the five XML metacharacters so an interpolated value cannot
|
|
64
|
-
* break out of a tag. Used for the simple identifier fields in the
|
|
65
|
-
* `<task-notification>` envelope (taskId, agentId, status) — values
|
|
66
|
-
* here are controlled enums / opaque ids in practice, but escaping
|
|
67
|
-
* keeps the envelope robust against any future producer that lets a
|
|
68
|
-
* `<` or `&` leak in.
|
|
69
|
-
*/
|
|
70
|
-
function xmlEscape(value: string): string {
|
|
71
|
-
return value
|
|
72
|
-
.replace(/&/g, '&')
|
|
73
|
-
.replace(/</g, '<')
|
|
74
|
-
.replace(/>/g, '>')
|
|
75
|
-
.replace(/"/g, '"')
|
|
76
|
-
.replace(/'/g, ''')
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Wrap free-form worker output in a CDATA section. CDATA preserves
|
|
81
|
-
* the raw text — code, markdown angle brackets, ampersands — so the
|
|
82
|
-
* supervisor sees what the worker actually produced instead of an
|
|
83
|
-
* escape-encoded approximation. The only termination CDATA forbids
|
|
84
|
-
* is the literal `]]>` sequence; we split-and-rejoin around it to
|
|
85
|
-
* keep the section well-formed regardless of payload.
|
|
86
|
-
*/
|
|
87
|
-
function cdataWrap(value: string): string {
|
|
88
|
-
return `<![CDATA[${value.replace(/]]>/g, ']]]]><![CDATA[>')}]]>`
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Map a provider's coarse `finishReason` plus the orchestrator's
|
|
93
|
-
* `forceFinalize` flag onto the per-message {@link MessageStopReason}
|
|
94
|
-
* union the v3 `message_completed` event surfaces.
|
|
95
|
-
*/
|
|
96
|
-
function synthesizeMessageStopReason(
|
|
97
|
-
finishReason: 'stop' | 'tool_calls' | 'length' | 'content_filter',
|
|
98
|
-
forceFinalize: boolean,
|
|
99
|
-
): MessageStopReason {
|
|
100
|
-
if (forceFinalize) return 'forced_finalize'
|
|
101
|
-
switch (finishReason) {
|
|
102
|
-
case 'tool_calls':
|
|
103
|
-
return 'tool_use'
|
|
104
|
-
case 'length':
|
|
105
|
-
return 'max_tokens'
|
|
106
|
-
case 'content_filter':
|
|
107
|
-
return 'refusal'
|
|
108
|
-
default:
|
|
109
|
-
return 'end_turn'
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
interface StreamingTurnResult {
|
|
114
|
-
response: ChatCompletionResponse
|
|
115
|
-
messageId: import('../../../types/ids/index.js').MessageId
|
|
116
|
-
}
|
|
25
|
+
export class IterationOrchestrator {
|
|
26
|
+
private ctx: IterationContext
|
|
117
27
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* lifecycle natively (message_started → text_delta* + tool_input_*
|
|
121
|
-
* → message_completed). Returns the aggregated `ChatCompletionResponse`
|
|
122
|
-
* for downstream code that still expects the legacy shape (assistant
|
|
123
|
-
* message construction, working-state extraction, telemetry attribute
|
|
124
|
-
* stamping).
|
|
125
|
-
*
|
|
126
|
-
* Per-delta `emitEvent` calls are followed by a `drainPending()`
|
|
127
|
-
* yield so SSE consumers see live progress instead of a burst at
|
|
128
|
-
* end-of-message. The bus's ephemeral filter (D1) ensures these
|
|
129
|
-
* deltas never hit transcript.jsonl.
|
|
130
|
-
*
|
|
131
|
-
* Edge cases (codex A3, A4, A5):
|
|
132
|
-
* - Stream ends without `finishReason` (anthropic-sdk-typescript#842
|
|
133
|
-
* dropped message_stop): we still emit `message_completed` from a
|
|
134
|
-
* finally-style fall-through path with `stopReason: 'refusal'`.
|
|
135
|
-
* - `tool_input_delta` with no `toolUseId` registered yet: we drop
|
|
136
|
-
* the fragment and log a warning (proxies seen to misorder events).
|
|
137
|
-
* - `chunk.error`: when no tool input is recoverable, we surface as
|
|
138
|
-
* a thrown error after emitting the message_completed terminator so
|
|
139
|
-
* consumer cards still close. If a tool-use block was already open,
|
|
140
|
-
* we instead synthesize a tool call with runtime truncation metadata
|
|
141
|
-
* so the executor can return a model-readable retry hint.
|
|
142
|
-
*/
|
|
143
|
-
async function* streamProviderTurn(
|
|
144
|
-
provider: LLMProvider,
|
|
145
|
-
params: import('../../../types/provider/index.js').ChatCompletionParams,
|
|
146
|
-
emitEvent: EmitEvent,
|
|
147
|
-
drainPending: () => Generator<RunEvent>,
|
|
148
|
-
runId: import('../../../types/ids/index.js').RunId,
|
|
149
|
-
iteration: number,
|
|
150
|
-
forceFinalize: boolean,
|
|
151
|
-
log: Logger,
|
|
152
|
-
): AsyncGenerator<RunEvent, StreamingTurnResult> {
|
|
153
|
-
const messageId = generateMessageId()
|
|
154
|
-
await emitEvent({ type: 'message_started', runId, iteration, messageId })
|
|
155
|
-
yield* drainPending()
|
|
156
|
-
|
|
157
|
-
let id = ''
|
|
158
|
-
const model = ''
|
|
159
|
-
let textBuf = ''
|
|
160
|
-
let finishReason: ChatCompletionResponse['finishReason'] = 'stop'
|
|
161
|
-
let usage: ChatCompletionResponse['usage'] = {
|
|
162
|
-
promptTokens: 0,
|
|
163
|
-
completionTokens: 0,
|
|
164
|
-
totalTokens: 0,
|
|
165
|
-
cachedTokens: 0,
|
|
166
|
-
cacheWriteTokens: 0,
|
|
28
|
+
constructor(ctx: IterationContext) {
|
|
29
|
+
this.ctx = ctx
|
|
167
30
|
}
|
|
168
|
-
const toolBuckets = new Map<
|
|
169
|
-
number,
|
|
170
|
-
{
|
|
171
|
-
id: string
|
|
172
|
-
name: string
|
|
173
|
-
argsBuf: string
|
|
174
|
-
started: boolean
|
|
175
|
-
completed: boolean
|
|
176
|
-
/**
|
|
177
|
-
* Parsed input. `null` while the bucket is still streaming.
|
|
178
|
-
* The synthesized
|
|
179
|
-
* `ChatCompletionResponse.toolCalls[].function.arguments` is
|
|
180
|
-
* derived from this — never from the raw buffer — so the
|
|
181
|
-
* downstream executor (`runtime/query/executor.ts`) never has
|
|
182
|
-
* to re-parse a truncated string. A truncated tool call is
|
|
183
|
-
* surfaced as `arguments: "{}"` plus `metadata.inputTruncated`
|
|
184
|
-
* so tool args remain clean while the executor can still
|
|
185
|
-
* return a specific retry hint.
|
|
186
|
-
*/
|
|
187
|
-
parsed: unknown | null
|
|
188
|
-
inputTruncated: boolean
|
|
189
|
-
}
|
|
190
|
-
>()
|
|
191
|
-
let streamError: string | undefined
|
|
192
31
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (chunk.error) {
|
|
198
|
-
streamError = chunk.error
|
|
199
|
-
break
|
|
200
|
-
}
|
|
201
|
-
if (!id && chunk.id) id = chunk.id
|
|
202
|
-
|
|
203
|
-
if (chunk.delta.content) {
|
|
204
|
-
textBuf += chunk.delta.content
|
|
205
|
-
await emitEvent({
|
|
206
|
-
type: 'text_delta',
|
|
207
|
-
runId,
|
|
208
|
-
iteration,
|
|
209
|
-
messageId,
|
|
210
|
-
text: chunk.delta.content,
|
|
211
|
-
})
|
|
212
|
-
yield* drainPending()
|
|
213
|
-
}
|
|
32
|
+
async *runLoop(): AsyncGenerator<RunEvent> {
|
|
33
|
+
const { runConfig, runMgr } = this.ctx
|
|
34
|
+
const { model } = runConfig
|
|
35
|
+
const tracer = getTracer()
|
|
214
36
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!bucket) {
|
|
218
|
-
bucket = {
|
|
219
|
-
id: tc.id ?? '',
|
|
220
|
-
name: tc.function?.name ?? '',
|
|
221
|
-
argsBuf: '',
|
|
222
|
-
started: false,
|
|
223
|
-
completed: false,
|
|
224
|
-
parsed: null,
|
|
225
|
-
inputTruncated: false,
|
|
226
|
-
}
|
|
227
|
-
toolBuckets.set(tc.index, bucket)
|
|
228
|
-
}
|
|
229
|
-
if (tc.id && !bucket.id) bucket.id = tc.id
|
|
230
|
-
if (tc.function?.name && !bucket.name) bucket.name = tc.function.name
|
|
231
|
-
|
|
232
|
-
if (!bucket.started && bucket.id && bucket.name) {
|
|
233
|
-
bucket.started = true
|
|
234
|
-
await emitEvent({
|
|
235
|
-
type: 'tool_input_started',
|
|
236
|
-
runId,
|
|
237
|
-
iteration,
|
|
238
|
-
messageId,
|
|
239
|
-
toolUseId: bucket.id as ToolUseId,
|
|
240
|
-
toolName: bucket.name,
|
|
241
|
-
})
|
|
242
|
-
yield* drainPending()
|
|
243
|
-
}
|
|
37
|
+
const planSignal = yield* runPlanGate(this.ctx)
|
|
38
|
+
if (planSignal === 'stop') return
|
|
244
39
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!bucket.id) {
|
|
248
|
-
log.warn('tool_input_delta arrived before tool id was known; dropping fragment', {
|
|
249
|
-
runId,
|
|
250
|
-
index: tc.index,
|
|
251
|
-
length: fragment.length,
|
|
252
|
-
})
|
|
253
|
-
} else {
|
|
254
|
-
bucket.argsBuf += fragment
|
|
255
|
-
await emitEvent({
|
|
256
|
-
type: 'tool_input_delta',
|
|
257
|
-
runId,
|
|
258
|
-
toolUseId: bucket.id as ToolUseId,
|
|
259
|
-
partialJson: fragment,
|
|
260
|
-
})
|
|
261
|
-
yield* drainPending()
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
40
|
+
while (true) {
|
|
41
|
+
const guardResult = this.ctx.guard.beforeIteration(runMgr, this.ctx.abortController.signal)
|
|
265
42
|
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
parsed = bucket.argsBuf ? JSON.parse(bucket.argsBuf) : {}
|
|
274
|
-
} catch (err) {
|
|
275
|
-
bucket.inputTruncated = true
|
|
276
|
-
log.warn('tool input JSON parse failed at content_block_stop', {
|
|
277
|
-
runId,
|
|
278
|
-
toolUseId: endId,
|
|
279
|
-
error: err instanceof Error ? err.message : String(err),
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
bucket.parsed = parsed
|
|
283
|
-
await emitEvent({
|
|
284
|
-
type: 'tool_input_completed',
|
|
285
|
-
runId,
|
|
286
|
-
toolUseId: endId as ToolUseId,
|
|
287
|
-
input: parsed,
|
|
288
|
-
...(bucket.inputTruncated ? { inputTruncated: true } : {}),
|
|
289
|
-
})
|
|
290
|
-
yield* drainPending()
|
|
43
|
+
if (guardResult.shouldStop) {
|
|
44
|
+
if (guardResult.isCancelled) {
|
|
45
|
+
this.ctx.log.info('Run cancelled by signal', { runId: runMgr.id })
|
|
46
|
+
runMgr.setStopReason('cancelled')
|
|
47
|
+
runMgr.markCancelled()
|
|
48
|
+
break
|
|
291
49
|
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (chunk.finishReason) finishReason = chunk.finishReason
|
|
295
|
-
if (chunk.usage) usage = chunk.usage
|
|
296
|
-
}
|
|
297
|
-
} catch (err) {
|
|
298
|
-
streamError = err instanceof Error ? err.message : String(err)
|
|
299
|
-
}
|
|
300
50
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// `argsBuf` ends with something like `"content":"…some prefix` —
|
|
309
|
-
// not parseable.
|
|
310
|
-
//
|
|
311
|
-
// Two cases coalesce here:
|
|
312
|
-
// 1. The buffer parses cleanly (the provider just forgot to emit
|
|
313
|
-
// `content_block_stop` but the args are intact) — keep parsed.
|
|
314
|
-
// 2. The buffer is truncated mid-literal — `parsed = {}` is the
|
|
315
|
-
// safe fallback so the executor's `JSON.parse(arguments)`
|
|
316
|
-
// succeeds and downstream consumers don't crash. The PRICE
|
|
317
|
-
// we used to pay was the model getting back a generic
|
|
318
|
-
// "<field> is required" Zod error and not realising its
|
|
319
|
-
// previous tool call was truncated server-side, so it would
|
|
320
|
-
// retry with the SAME long input and hit the same cutoff in
|
|
321
|
-
// a loop. Detect the truncation case and mark the tool call
|
|
322
|
-
// with runtime metadata; the executor surfaces a specific
|
|
323
|
-
// "your tool call was cut off by max_tokens — retry with
|
|
324
|
-
// shorter input or split into smaller calls" message that the
|
|
325
|
-
// model can act on.
|
|
326
|
-
for (const bucket of toolBuckets.values()) {
|
|
327
|
-
if (bucket.started && !bucket.completed) {
|
|
328
|
-
bucket.completed = true
|
|
329
|
-
let parsed: unknown = {}
|
|
330
|
-
let truncated = false
|
|
331
|
-
if (bucket.argsBuf) {
|
|
332
|
-
try {
|
|
333
|
-
parsed = JSON.parse(bucket.argsBuf)
|
|
334
|
-
} catch {
|
|
335
|
-
// argsBuf had content but didn't parse — almost
|
|
336
|
-
// certainly the max_tokens-mid-literal cutoff. Mark
|
|
337
|
-
// the bucket so the executor can return a model-
|
|
338
|
-
// readable hint instead of a generic Zod error.
|
|
339
|
-
truncated = true
|
|
340
|
-
parsed = {}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
bucket.parsed = parsed
|
|
344
|
-
bucket.inputTruncated = truncated
|
|
345
|
-
if (truncated) {
|
|
346
|
-
log.warn('tool input truncated by upstream cutoff (no toolCallEnd, argsBuf unparsable)', {
|
|
347
|
-
runId,
|
|
348
|
-
toolUseId: bucket.id,
|
|
349
|
-
toolName: bucket.name,
|
|
350
|
-
bufferLength: bucket.argsBuf.length,
|
|
51
|
+
const stopReason = guardResult.stopReason ?? 'end_turn'
|
|
52
|
+
this.ctx.log.info('Guard enforcing stop', {
|
|
53
|
+
runId: runMgr.id,
|
|
54
|
+
stopReason,
|
|
55
|
+
iteration: runMgr.currentIteration,
|
|
56
|
+
inputTokens: runMgr.tokenUsage.promptTokens,
|
|
57
|
+
outputTokens: runMgr.tokenUsage.completionTokens,
|
|
351
58
|
})
|
|
59
|
+
await this.requestFinalResponse(model, stopReason)
|
|
60
|
+
yield* this.ctx.drainPending()
|
|
61
|
+
runMgr.setStopReason(stopReason)
|
|
62
|
+
break
|
|
352
63
|
}
|
|
353
|
-
await emitEvent({
|
|
354
|
-
type: 'tool_input_completed',
|
|
355
|
-
runId,
|
|
356
|
-
toolUseId: bucket.id as ToolUseId,
|
|
357
|
-
input: parsed,
|
|
358
|
-
...(truncated ? { inputTruncated: true } : {}),
|
|
359
|
-
})
|
|
360
|
-
yield* drainPending()
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// `arguments` MUST be valid JSON for the executor's `JSON.parse`
|
|
365
|
-
// (`runtime/query/executor.ts:executeSingle`) to succeed. We
|
|
366
|
-
// always serialise from the bucket's `parsed` object (filled by
|
|
367
|
-
// either the `toolCallEnd` branch above or the post-stream flush
|
|
368
|
-
// loop) instead of re-emitting `argsBuf`. When the provider
|
|
369
|
-
// stream truncated mid-input, `metadata.inputTruncated` carries that
|
|
370
|
-
// state; the executor parses cleanly and returns a specific
|
|
371
|
-
// model-readable retry hint instead of the generic "Invalid JSON in
|
|
372
|
-
// tool arguments" intercept.
|
|
373
|
-
const toolCalls = [...toolBuckets.entries()]
|
|
374
|
-
.sort(([a], [b]) => a - b)
|
|
375
|
-
.map(([, b]) => ({
|
|
376
|
-
id: b.id,
|
|
377
|
-
type: 'function' as const,
|
|
378
|
-
function: {
|
|
379
|
-
name: b.name,
|
|
380
|
-
arguments: JSON.stringify(b.parsed ?? {}),
|
|
381
|
-
},
|
|
382
|
-
...(b.inputTruncated ? { metadata: { inputTruncated: true } } : {}),
|
|
383
|
-
}))
|
|
384
|
-
|
|
385
|
-
const recoveredToolInputFromStreamError =
|
|
386
|
-
streamError !== undefined && toolCalls.some((tc) => tc.id && tc.function.name)
|
|
387
|
-
const effectiveFinishReason: ChatCompletionResponse['finishReason'] =
|
|
388
|
-
recoveredToolInputFromStreamError ? 'tool_calls' : finishReason
|
|
389
|
-
|
|
390
|
-
if (recoveredToolInputFromStreamError) {
|
|
391
|
-
log.warn('provider stream failed after tool input; surfacing tool call to executor', {
|
|
392
|
-
runId,
|
|
393
|
-
iteration,
|
|
394
|
-
error: streamError,
|
|
395
|
-
toolCallCount: toolCalls.length,
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const stopReason: MessageStopReason = streamError
|
|
400
|
-
? recoveredToolInputFromStreamError
|
|
401
|
-
? 'tool_use'
|
|
402
|
-
: 'refusal'
|
|
403
|
-
: synthesizeMessageStopReason(effectiveFinishReason, forceFinalize)
|
|
404
|
-
|
|
405
|
-
await emitEvent({
|
|
406
|
-
type: 'message_completed',
|
|
407
|
-
runId,
|
|
408
|
-
iteration,
|
|
409
|
-
messageId,
|
|
410
|
-
stopReason,
|
|
411
|
-
usage,
|
|
412
|
-
content: textBuf || undefined,
|
|
413
|
-
})
|
|
414
|
-
yield* drainPending()
|
|
415
|
-
|
|
416
|
-
if (streamError && !recoveredToolInputFromStreamError) {
|
|
417
|
-
throw new Error(`Provider stream error: ${streamError}`)
|
|
418
|
-
}
|
|
419
64
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
usage,
|
|
430
|
-
}
|
|
431
|
-
return { response, messageId }
|
|
432
|
-
}
|
|
65
|
+
const forceFinalize = guardResult.forceFinalize
|
|
66
|
+
const iterationNum = runMgr.incrementIteration()
|
|
67
|
+
this.ctx.log.debug('Iteration started', {
|
|
68
|
+
runId: runMgr.id,
|
|
69
|
+
iteration: iterationNum,
|
|
70
|
+
model,
|
|
71
|
+
forceFinalize,
|
|
72
|
+
messageCount: runMgr.messages.length,
|
|
73
|
+
})
|
|
433
74
|
|
|
434
|
-
|
|
435
|
-
|
|
75
|
+
const iterationActivity = this.ctx.activityStore.create({
|
|
76
|
+
type: 'llm_turn',
|
|
77
|
+
description: `LLM iteration ${iterationNum}`,
|
|
78
|
+
})
|
|
79
|
+
if (iterationActivity) {
|
|
80
|
+
this.ctx.activityStore.start(iterationActivity.id)
|
|
81
|
+
}
|
|
436
82
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
emitEvent: EmitEvent,
|
|
444
|
-
drainPending: () => Generator<RunEvent>,
|
|
445
|
-
abortController: AbortController,
|
|
446
|
-
log: Logger,
|
|
447
|
-
resumeHandler: ResumeHandler,
|
|
448
|
-
checkpointMgr: CheckpointManager,
|
|
449
|
-
planManager: PlanManager,
|
|
450
|
-
) {
|
|
451
|
-
this.ctx = {
|
|
452
|
-
provider: config.provider,
|
|
453
|
-
runConfig: config.runConfig,
|
|
454
|
-
tools: config.tools,
|
|
455
|
-
allowedTools: config.allowedTools,
|
|
456
|
-
runMgr,
|
|
457
|
-
toolExecutor,
|
|
458
|
-
guard,
|
|
459
|
-
activityStore,
|
|
460
|
-
emitEvent,
|
|
461
|
-
drainPending,
|
|
462
|
-
abortController,
|
|
463
|
-
log,
|
|
464
|
-
resumeHandler,
|
|
465
|
-
checkpointMgr,
|
|
466
|
-
planManager,
|
|
467
|
-
taskGateway: config.taskGateway,
|
|
468
|
-
taskStore: config.taskStore,
|
|
469
|
-
pendingNotifications: [],
|
|
470
|
-
launchedTasks: config.launchedTasks ?? new Map(),
|
|
471
|
-
compactionConfig: config.compactionConfig,
|
|
472
|
-
workingStateManager: config.workingStateManager,
|
|
473
|
-
advisoryCtx: config.advisoryCtx,
|
|
474
|
-
agentBus: config.agentBus,
|
|
475
|
-
verificationGate: config.verificationGate,
|
|
476
|
-
pluginManager: config.pluginManager,
|
|
477
|
-
}
|
|
478
|
-
}
|
|
83
|
+
const iterSpan = tracer.startSpan(agentIterationSpanName(iterationNum))
|
|
84
|
+
iterSpan.setAttributes({
|
|
85
|
+
[NAMZU.ITERATION]: iterationNum,
|
|
86
|
+
[NAMZU.RUN_ID]: runMgr.id,
|
|
87
|
+
[GENAI.REQUEST_MODEL]: model,
|
|
88
|
+
})
|
|
479
89
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
90
|
+
await this.ctx.emitEvent({
|
|
91
|
+
type: 'iteration_started',
|
|
92
|
+
runId: runMgr.id,
|
|
93
|
+
iteration: iterationNum,
|
|
94
|
+
})
|
|
95
|
+
yield* this.ctx.drainPending()
|
|
484
96
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
// drain stays as a no-op until a follow-up tears it out.
|
|
496
|
-
let unsubscribeTaskListener: (() => void) | undefined
|
|
497
|
-
void unsubscribeTaskListener
|
|
97
|
+
try {
|
|
98
|
+
if (this.ctx.pluginManager) {
|
|
99
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
100
|
+
'iteration_start',
|
|
101
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
102
|
+
this.ctx.emitEvent,
|
|
103
|
+
)
|
|
104
|
+
applyLifecycleHookResults('iteration_start', hookResults)
|
|
105
|
+
yield* this.ctx.drainPending()
|
|
106
|
+
}
|
|
498
107
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
108
|
+
// Re-pin the working-memory block from ground truth at the primacy
|
|
109
|
+
// edge BEFORE compaction runs (so the refreshed slot is what
|
|
110
|
+
// compaction preserves). No-op when no provider is configured.
|
|
111
|
+
await refreshWorkingMemory(this.ctx)
|
|
112
|
+
await runCompactionCheck(this.ctx)
|
|
113
|
+
|
|
114
|
+
// Cache discipline: keep the tools param byte-stable even on the
|
|
115
|
+
// forced-final iteration and forbid tool use via tool_choice
|
|
116
|
+
// 'none' instead. Dropping the tools array would invalidate the
|
|
117
|
+
// entire prompt-cache prefix (tools render at position 0) and
|
|
118
|
+
// risks a 400 because the history still carries
|
|
119
|
+
// tool_use/tool_result blocks.
|
|
120
|
+
const openAITools = this.ctx.tools.toLLMTools(this.ctx.allowedTools)
|
|
121
|
+
|
|
122
|
+
const messages = forceFinalize
|
|
123
|
+
? [
|
|
124
|
+
...runMgr.messages,
|
|
125
|
+
createUserMessage(
|
|
126
|
+
'[SYSTEM] You are approaching your resource limits. Provide your final, comprehensive response now based on everything you have gathered so far. Do not request any more tool calls.',
|
|
127
|
+
),
|
|
128
|
+
]
|
|
129
|
+
: runMgr.messages
|
|
130
|
+
|
|
131
|
+
if (this.ctx.pluginManager) {
|
|
132
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
133
|
+
'pre_llm_call',
|
|
134
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
135
|
+
this.ctx.emitEvent,
|
|
136
|
+
)
|
|
137
|
+
applyLifecycleHookResults('pre_llm_call', hookResults)
|
|
138
|
+
yield* this.ctx.drainPending()
|
|
139
|
+
}
|
|
502
140
|
|
|
503
|
-
|
|
504
|
-
|
|
141
|
+
// Phase 4 (ses_001-tool-stream-events): consume the
|
|
142
|
+
// streaming response natively, emitting message and
|
|
143
|
+
// tool-input lifecycle events as deltas arrive. The
|
|
144
|
+
// helper yields RunEvents through drainPending() so SSE
|
|
145
|
+
// consumers see live progress; its return value is the
|
|
146
|
+
// aggregated `ChatCompletionResponse` for the legacy
|
|
147
|
+
// downstream paths (assistantMsg construction, working
|
|
148
|
+
// state extraction, telemetry attribute stamping).
|
|
149
|
+
const { response } = yield* streamProviderTurn(
|
|
150
|
+
this.ctx.provider,
|
|
151
|
+
{
|
|
152
|
+
model,
|
|
153
|
+
messages,
|
|
154
|
+
tools: openAITools.length > 0 ? openAITools : undefined,
|
|
155
|
+
toolChoice: forceFinalize && openAITools.length > 0 ? 'none' : undefined,
|
|
156
|
+
temperature: runConfig.temperature,
|
|
157
|
+
maxTokens: runConfig.maxResponseTokens,
|
|
158
|
+
cacheControl: { type: 'auto' },
|
|
159
|
+
// Thread the run abort into the model call so a Stop tears the
|
|
160
|
+
// in-flight turn down (provider passes it to fetch; the consumer
|
|
161
|
+
// also races it). Inert when never aborted.
|
|
162
|
+
signal: this.ctx.abortController.signal,
|
|
163
|
+
},
|
|
164
|
+
this.ctx.emitEvent,
|
|
165
|
+
this.ctx.drainPending,
|
|
166
|
+
runMgr.id,
|
|
167
|
+
iterationNum,
|
|
168
|
+
forceFinalize,
|
|
169
|
+
this.ctx.log,
|
|
170
|
+
)
|
|
505
171
|
|
|
506
|
-
|
|
507
|
-
if (guardResult.isCancelled) {
|
|
508
|
-
this.ctx.log.info('Run cancelled by signal', { runId: runMgr.id })
|
|
509
|
-
runMgr.setStopReason('cancelled')
|
|
510
|
-
runMgr.markCancelled()
|
|
511
|
-
break
|
|
512
|
-
}
|
|
172
|
+
runMgr.accumulateUsage(response.usage)
|
|
513
173
|
|
|
514
|
-
|
|
515
|
-
this.ctx.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
})
|
|
522
|
-
await this.requestFinalResponse(model, stopReason)
|
|
174
|
+
if (this.ctx.pluginManager) {
|
|
175
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
176
|
+
'post_llm_call',
|
|
177
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
178
|
+
this.ctx.emitEvent,
|
|
179
|
+
)
|
|
180
|
+
applyLifecycleHookResults('post_llm_call', hookResults)
|
|
523
181
|
yield* this.ctx.drainPending()
|
|
524
|
-
runMgr.setStopReason(stopReason)
|
|
525
|
-
break
|
|
526
182
|
}
|
|
527
183
|
|
|
528
|
-
|
|
529
|
-
const iterationNum = runMgr.incrementIteration()
|
|
530
|
-
this.ctx.log.debug('Iteration started', {
|
|
184
|
+
this.ctx.log.debug('LLM response received', {
|
|
531
185
|
runId: runMgr.id,
|
|
532
186
|
iteration: iterationNum,
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
description: `LLM iteration ${iterationNum}`,
|
|
541
|
-
})
|
|
542
|
-
if (iterationActivity) {
|
|
543
|
-
this.ctx.activityStore.start(iterationActivity.id)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
const iterSpan = tracer.startSpan(agentIterationSpanName(iterationNum))
|
|
547
|
-
iterSpan.setAttributes({
|
|
548
|
-
[NAMZU.ITERATION]: iterationNum,
|
|
549
|
-
[NAMZU.RUN_ID]: runMgr.id,
|
|
550
|
-
[GENAI.REQUEST_MODEL]: model,
|
|
187
|
+
finishReason: response.finishReason,
|
|
188
|
+
hasContent: response.message.content !== null && response.message.content.length > 0,
|
|
189
|
+
toolCallCount: response.message.toolCalls?.length ?? 0,
|
|
190
|
+
promptTokens: response.usage.promptTokens,
|
|
191
|
+
completionTokens: response.usage.completionTokens,
|
|
192
|
+
totalTokens: runMgr.tokenUsage.totalTokens,
|
|
193
|
+
totalCost: runMgr.costInfo.totalCost,
|
|
551
194
|
})
|
|
552
195
|
|
|
553
196
|
await this.ctx.emitEvent({
|
|
554
|
-
type: '
|
|
197
|
+
type: 'token_usage_updated',
|
|
555
198
|
runId: runMgr.id,
|
|
556
|
-
|
|
199
|
+
usage: runMgr.tokenUsage,
|
|
200
|
+
cost: runMgr.costInfo,
|
|
557
201
|
})
|
|
558
|
-
yield* this.ctx.drainPending()
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
if (this.ctx.pluginManager) {
|
|
562
|
-
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
563
|
-
'iteration_start',
|
|
564
|
-
{ runId: runMgr.id, iteration: iterationNum },
|
|
565
|
-
this.ctx.emitEvent,
|
|
566
|
-
)
|
|
567
|
-
applyLifecycleHookResults('iteration_start', hookResults)
|
|
568
|
-
yield* this.ctx.drainPending()
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
if (this.ctx.pendingNotifications.length > 0) {
|
|
572
|
-
await this.injectOneTaskNotification()
|
|
573
|
-
}
|
|
574
202
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
),
|
|
587
|
-
]
|
|
588
|
-
: runMgr.messages
|
|
589
|
-
|
|
590
|
-
if (this.ctx.pluginManager) {
|
|
591
|
-
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
592
|
-
'pre_llm_call',
|
|
593
|
-
{ runId: runMgr.id, iteration: iterationNum },
|
|
594
|
-
this.ctx.emitEvent,
|
|
595
|
-
)
|
|
596
|
-
applyLifecycleHookResults('pre_llm_call', hookResults)
|
|
597
|
-
yield* this.ctx.drainPending()
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Phase 4 (ses_001-tool-stream-events): consume the
|
|
601
|
-
// streaming response natively, emitting message and
|
|
602
|
-
// tool-input lifecycle events as deltas arrive. The
|
|
603
|
-
// helper yields RunEvents through drainPending() so SSE
|
|
604
|
-
// consumers see live progress; its return value is the
|
|
605
|
-
// aggregated `ChatCompletionResponse` for the legacy
|
|
606
|
-
// downstream paths (assistantMsg construction, working
|
|
607
|
-
// state extraction, telemetry attribute stamping).
|
|
608
|
-
const { response } = yield* streamProviderTurn(
|
|
609
|
-
this.ctx.provider,
|
|
610
|
-
{
|
|
611
|
-
model,
|
|
612
|
-
messages,
|
|
613
|
-
tools: openAITools && openAITools.length > 0 ? openAITools : undefined,
|
|
614
|
-
temperature: runConfig.temperature,
|
|
615
|
-
maxTokens: runConfig.maxResponseTokens,
|
|
616
|
-
cacheControl: { type: 'auto' },
|
|
617
|
-
},
|
|
618
|
-
this.ctx.emitEvent,
|
|
619
|
-
this.ctx.drainPending,
|
|
620
|
-
runMgr.id,
|
|
621
|
-
iterationNum,
|
|
622
|
-
forceFinalize,
|
|
623
|
-
this.ctx.log,
|
|
203
|
+
const assistantMsg = createAssistantMessage(
|
|
204
|
+
response.message.content,
|
|
205
|
+
forceFinalize ? undefined : response.message.toolCalls,
|
|
206
|
+
)
|
|
207
|
+
runMgr.pushMessage(assistantMsg)
|
|
208
|
+
|
|
209
|
+
if (this.ctx.workingStateManager && this.ctx.compactionConfig && assistantMsg.content) {
|
|
210
|
+
extractFromAssistantMessage(
|
|
211
|
+
this.ctx.workingStateManager,
|
|
212
|
+
assistantMsg.content,
|
|
213
|
+
this.ctx.compactionConfig,
|
|
624
214
|
)
|
|
215
|
+
}
|
|
625
216
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (this.ctx.pluginManager) {
|
|
629
|
-
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
630
|
-
'post_llm_call',
|
|
631
|
-
{ runId: runMgr.id, iteration: iterationNum },
|
|
632
|
-
this.ctx.emitEvent,
|
|
633
|
-
)
|
|
634
|
-
applyLifecycleHookResults('post_llm_call', hookResults)
|
|
635
|
-
yield* this.ctx.drainPending()
|
|
636
|
-
}
|
|
217
|
+
yield* this.ctx.drainPending()
|
|
637
218
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
toolCallCount: response.message.toolCalls?.length ?? 0,
|
|
644
|
-
promptTokens: response.usage.promptTokens,
|
|
645
|
-
completionTokens: response.usage.completionTokens,
|
|
646
|
-
totalTokens: runMgr.tokenUsage.totalTokens,
|
|
647
|
-
totalCost: runMgr.costInfo.totalCost,
|
|
648
|
-
})
|
|
219
|
+
iterSpan.setAttributes({
|
|
220
|
+
[GENAI.USAGE_INPUT_TOKENS]: response.usage.promptTokens,
|
|
221
|
+
[GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
|
|
222
|
+
})
|
|
223
|
+
iterSpan.setStatus({ code: SpanStatusCode.OK })
|
|
649
224
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
cost: runMgr.costInfo,
|
|
225
|
+
if (iterationActivity) {
|
|
226
|
+
this.ctx.activityStore.complete(iterationActivity.id, {
|
|
227
|
+
content: response.message.content,
|
|
228
|
+
hasToolCalls: forceFinalize ? false : !!response.message.toolCalls?.length,
|
|
655
229
|
})
|
|
230
|
+
}
|
|
656
231
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
232
|
+
if (
|
|
233
|
+
forceFinalize ||
|
|
234
|
+
response.finishReason === 'stop' ||
|
|
235
|
+
!response.message.toolCalls ||
|
|
236
|
+
response.message.toolCalls.length === 0
|
|
237
|
+
) {
|
|
238
|
+
// Every task-dispatch tool (create_task, continue_task, Agent)
|
|
239
|
+
// is BLOCKING: the worker's output returns as the dispatching
|
|
240
|
+
// tool_use's canonical tool_result, so by the time the model
|
|
241
|
+
// ends its turn nothing launched by this run should still be
|
|
242
|
+
// in flight. A running task here is an orphan (interrupted
|
|
243
|
+
// tool execution, cancel race) with no delivery path back to
|
|
244
|
+
// the parent — the <task-notification> producer was removed
|
|
245
|
+
// in dc16d58, so waiting on the queue could only ever time
|
|
246
|
+
// out. Log the orphans honestly and end the turn normally.
|
|
247
|
+
if (!forceFinalize && this.hasRunningAgentTasks()) {
|
|
248
|
+
this.ctx.log.warn(
|
|
249
|
+
'LLM ended turn with agent tasks still running — ending run without waiting (orphan tasks have no delivery path)',
|
|
250
|
+
{
|
|
251
|
+
runId: runMgr.id,
|
|
252
|
+
iteration: iterationNum,
|
|
253
|
+
},
|
|
668
254
|
)
|
|
669
255
|
}
|
|
670
256
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
257
|
+
const hasContent =
|
|
258
|
+
response.message.content !== null && response.message.content.length > 0
|
|
259
|
+
|
|
260
|
+
// Auto-continuation on `stop_reason: max_tokens`. The
|
|
261
|
+
// model hit its per-call output cap mid-text (NOT
|
|
262
|
+
// mid-tool-use — that path is handled separately
|
|
263
|
+
// below via `inputTruncated`). Push a synthetic
|
|
264
|
+
// "continue" user message and let the loop fire
|
|
265
|
+
// another turn. The provider receives the partial
|
|
266
|
+
// assistant content + the continue prompt and
|
|
267
|
+
// resumes from where it left off, mirroring the
|
|
268
|
+
// Claude.ai "Continue" affordance.
|
|
269
|
+
//
|
|
270
|
+
// Guards:
|
|
271
|
+
// - `hasContent` so we don't loop forever on an
|
|
272
|
+
// empty cutoff (Anthropic occasionally emits
|
|
273
|
+
// `stop_reason: max_tokens` with no content
|
|
274
|
+
// when an injected pre-fill blocks the model).
|
|
275
|
+
// - `!forceFinalize` so the forced-finalize path
|
|
276
|
+
// never auto-continues — that path is invoked
|
|
277
|
+
// specifically to extract a closing summary.
|
|
278
|
+
// - max_iterations bounds the loop in any case.
|
|
279
|
+
if (!forceFinalize && response.finishReason === 'length' && hasContent) {
|
|
280
|
+
this.ctx.log.info('LLM hit max_tokens mid-text — auto-continuing', {
|
|
281
|
+
runId: runMgr.id,
|
|
282
|
+
iteration: iterationNum,
|
|
283
|
+
completionTokens: response.usage.completionTokens,
|
|
683
284
|
})
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
if (
|
|
687
|
-
forceFinalize ||
|
|
688
|
-
response.finishReason === 'stop' ||
|
|
689
|
-
!response.message.toolCalls ||
|
|
690
|
-
response.message.toolCalls.length === 0
|
|
691
|
-
) {
|
|
692
|
-
const hasRunningTasks = this.hasRunningAgentTasks()
|
|
693
|
-
const hasPendingNotifications = this.ctx.pendingNotifications.length > 0
|
|
694
|
-
|
|
695
|
-
if (!forceFinalize && (hasRunningTasks || hasPendingNotifications)) {
|
|
696
|
-
this.ctx.log.info(
|
|
697
|
-
'LLM ended turn but agent tasks still running — waiting for notifications',
|
|
698
|
-
{
|
|
699
|
-
runId: runMgr.id,
|
|
700
|
-
runningTasks: hasRunningTasks,
|
|
701
|
-
pendingNotifications: hasPendingNotifications,
|
|
702
|
-
},
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
await this.ctx.emitEvent({
|
|
706
|
-
type: 'iteration_completed',
|
|
707
|
-
runId: runMgr.id,
|
|
708
|
-
iteration: iterationNum,
|
|
709
|
-
hasToolCalls: false,
|
|
710
|
-
})
|
|
711
|
-
yield* this.ctx.drainPending()
|
|
712
|
-
iterSpan.end()
|
|
713
|
-
|
|
714
|
-
yield* this.waitAndInjectNotifications()
|
|
715
|
-
continue
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const hasContent =
|
|
719
|
-
response.message.content !== null && response.message.content.length > 0
|
|
720
|
-
|
|
721
|
-
// Auto-continuation on `stop_reason: max_tokens`. The
|
|
722
|
-
// model hit its per-call output cap mid-text (NOT
|
|
723
|
-
// mid-tool-use — that path is handled separately
|
|
724
|
-
// below via `inputTruncated`). Push a synthetic
|
|
725
|
-
// "continue" user message and let the loop fire
|
|
726
|
-
// another turn. The provider receives the partial
|
|
727
|
-
// assistant content + the continue prompt and
|
|
728
|
-
// resumes from where it left off, mirroring the
|
|
729
|
-
// Claude.ai "Continue" affordance.
|
|
730
|
-
//
|
|
731
|
-
// Guards:
|
|
732
|
-
// - `hasContent` so we don't loop forever on an
|
|
733
|
-
// empty cutoff (Anthropic occasionally emits
|
|
734
|
-
// `stop_reason: max_tokens` with no content
|
|
735
|
-
// when an injected pre-fill blocks the model).
|
|
736
|
-
// - `!forceFinalize` so the forced-finalize path
|
|
737
|
-
// never auto-continues — that path is invoked
|
|
738
|
-
// specifically to extract a closing summary.
|
|
739
|
-
// - max_iterations bounds the loop in any case.
|
|
740
|
-
if (!forceFinalize && response.finishReason === 'length' && hasContent) {
|
|
741
|
-
this.ctx.log.info('LLM hit max_tokens mid-text — auto-continuing', {
|
|
742
|
-
runId: runMgr.id,
|
|
743
|
-
iteration: iterationNum,
|
|
744
|
-
completionTokens: response.usage.completionTokens,
|
|
745
|
-
})
|
|
746
|
-
runMgr.pushMessage(createUserMessage(AUTO_CONTINUATION_USER_MESSAGE))
|
|
747
|
-
await this.ctx.emitEvent({
|
|
748
|
-
type: 'iteration_completed',
|
|
749
|
-
runId: runMgr.id,
|
|
750
|
-
iteration: iterationNum,
|
|
751
|
-
hasToolCalls: false,
|
|
752
|
-
})
|
|
753
|
-
yield* this.ctx.drainPending()
|
|
754
|
-
iterSpan.end()
|
|
755
|
-
continue
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
if (!hasContent && !forceFinalize) {
|
|
759
|
-
this.ctx.log.warn('Empty completion detected — requesting final summary', {
|
|
760
|
-
iteration: iterationNum,
|
|
761
|
-
finishReason: response.finishReason,
|
|
762
|
-
})
|
|
763
|
-
await this.requestFinalResponse(model, 'end_turn')
|
|
764
|
-
yield* this.ctx.drainPending()
|
|
765
|
-
}
|
|
766
|
-
|
|
285
|
+
runMgr.pushMessage(createUserMessage(AUTO_CONTINUATION_USER_MESSAGE))
|
|
767
286
|
await this.ctx.emitEvent({
|
|
768
287
|
type: 'iteration_completed',
|
|
769
288
|
runId: runMgr.id,
|
|
@@ -771,38 +290,16 @@ export class IterationOrchestrator {
|
|
|
771
290
|
hasToolCalls: false,
|
|
772
291
|
})
|
|
773
292
|
yield* this.ctx.drainPending()
|
|
774
|
-
runMgr.setStopReason('end_turn')
|
|
775
|
-
iterSpan.end()
|
|
776
|
-
break
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
const reviewOutcome = yield* runToolReview(this.ctx, response, iterationNum)
|
|
780
|
-
|
|
781
|
-
if (reviewOutcome === 'stop') {
|
|
782
|
-
iterSpan.end()
|
|
783
|
-
return
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
if (reviewOutcome === 'rejected') {
|
|
787
293
|
iterSpan.end()
|
|
788
294
|
continue
|
|
789
295
|
}
|
|
790
296
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
await runAdvisoryPhase(this.ctx, iterationNum, response)
|
|
798
|
-
|
|
799
|
-
if (this.ctx.pluginManager) {
|
|
800
|
-
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
801
|
-
'iteration_end',
|
|
802
|
-
{ runId: runMgr.id, iteration: iterationNum },
|
|
803
|
-
this.ctx.emitEvent,
|
|
804
|
-
)
|
|
805
|
-
applyLifecycleHookResults('iteration_end', hookResults)
|
|
297
|
+
if (!hasContent && !forceFinalize) {
|
|
298
|
+
this.ctx.log.warn('Empty completion detected — requesting final summary', {
|
|
299
|
+
iteration: iterationNum,
|
|
300
|
+
finishReason: response.finishReason,
|
|
301
|
+
})
|
|
302
|
+
await this.requestFinalResponse(model, 'end_turn')
|
|
806
303
|
yield* this.ctx.drainPending()
|
|
807
304
|
}
|
|
808
305
|
|
|
@@ -810,26 +307,89 @@ export class IterationOrchestrator {
|
|
|
810
307
|
type: 'iteration_completed',
|
|
811
308
|
runId: runMgr.id,
|
|
812
309
|
iteration: iterationNum,
|
|
813
|
-
hasToolCalls:
|
|
310
|
+
hasToolCalls: false,
|
|
814
311
|
})
|
|
815
312
|
yield* this.ctx.drainPending()
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
313
|
+
// A Stop that lands AFTER the final turn streamed but before
|
|
314
|
+
// this break must settle the run as cancelled, not end_turn —
|
|
315
|
+
// otherwise the just-produced answer is recorded as a clean
|
|
316
|
+
// completion. Mirrors the between-iteration cancel at :511.
|
|
317
|
+
if (this.ctx.abortController.signal.aborted) {
|
|
318
|
+
runMgr.setStopReason('cancelled')
|
|
319
|
+
runMgr.markCancelled()
|
|
320
|
+
iterSpan.end()
|
|
321
|
+
break
|
|
820
322
|
}
|
|
323
|
+
runMgr.setStopReason('end_turn')
|
|
324
|
+
iterSpan.end()
|
|
325
|
+
break
|
|
326
|
+
}
|
|
821
327
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
328
|
+
const reviewOutcome = yield* runToolReview(this.ctx, response, iterationNum)
|
|
329
|
+
|
|
330
|
+
if (reviewOutcome === 'stop') {
|
|
331
|
+
iterSpan.end()
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (reviewOutcome === 'rejected') {
|
|
336
|
+
iterSpan.end()
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const checkpointSignal = yield* runIterationCheckpoint(this.ctx, iterationNum)
|
|
341
|
+
if (checkpointSignal === 'stop') {
|
|
827
342
|
iterSpan.end()
|
|
828
|
-
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await runAdvisoryPhase(this.ctx, iterationNum, response)
|
|
347
|
+
|
|
348
|
+
if (this.ctx.pluginManager) {
|
|
349
|
+
const hookResults = await this.ctx.pluginManager.executeHooks(
|
|
350
|
+
'iteration_end',
|
|
351
|
+
{ runId: runMgr.id, iteration: iterationNum },
|
|
352
|
+
this.ctx.emitEvent,
|
|
353
|
+
)
|
|
354
|
+
applyLifecycleHookResults('iteration_end', hookResults)
|
|
355
|
+
yield* this.ctx.drainPending()
|
|
829
356
|
}
|
|
357
|
+
|
|
358
|
+
await this.ctx.emitEvent({
|
|
359
|
+
type: 'iteration_completed',
|
|
360
|
+
runId: runMgr.id,
|
|
361
|
+
iteration: iterationNum,
|
|
362
|
+
hasToolCalls: true,
|
|
363
|
+
})
|
|
364
|
+
yield* this.ctx.drainPending()
|
|
365
|
+
iterSpan.end()
|
|
366
|
+
} catch (err) {
|
|
367
|
+
// A Stop that aborted the in-flight turn surfaces here as a
|
|
368
|
+
// thrown abort (the provider stream was raced against the run
|
|
369
|
+
// signal). Settle it as a CANCELLATION — mirroring the
|
|
370
|
+
// between-iteration cancel at the top of the loop — rather than
|
|
371
|
+
// recording it as an SDK failure (error span + failed activity)
|
|
372
|
+
// and re-throwing. The run then returns cleanly with a
|
|
373
|
+
// 'cancelled' stop reason instead of propagating an error.
|
|
374
|
+
if (this.ctx.abortController.signal.aborted) {
|
|
375
|
+
runMgr.setStopReason('cancelled')
|
|
376
|
+
runMgr.markCancelled()
|
|
377
|
+
iterSpan.end()
|
|
378
|
+
break
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (iterationActivity) {
|
|
382
|
+
this.ctx.activityStore.fail(iterationActivity.id, toErrorMessage(err))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
iterSpan.setStatus({
|
|
386
|
+
code: SpanStatusCode.ERROR,
|
|
387
|
+
message: toErrorMessage(err),
|
|
388
|
+
})
|
|
389
|
+
iterSpan.recordException(err instanceof Error ? err : new Error(String(err)))
|
|
390
|
+
iterSpan.end()
|
|
391
|
+
throw err
|
|
830
392
|
}
|
|
831
|
-
} finally {
|
|
832
|
-
unsubscribeTaskListener?.()
|
|
833
393
|
}
|
|
834
394
|
}
|
|
835
395
|
|
|
@@ -840,90 +400,6 @@ export class IterationOrchestrator {
|
|
|
840
400
|
.some((t) => t.state !== 'completed' && t.state !== 'failed' && t.state !== 'canceled')
|
|
841
401
|
}
|
|
842
402
|
|
|
843
|
-
private async *waitAndInjectNotifications(): AsyncGenerator<RunEvent> {
|
|
844
|
-
const deadline = Date.now() + (this.ctx.runConfig.timeoutMs ?? 120_000)
|
|
845
|
-
while (
|
|
846
|
-
this.ctx.pendingNotifications.length === 0 &&
|
|
847
|
-
Date.now() < deadline &&
|
|
848
|
-
!this.ctx.abortController.signal.aborted
|
|
849
|
-
) {
|
|
850
|
-
await new Promise((r) => setTimeout(r, 250))
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
await this.injectOneTaskNotification()
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
* Canonical async completion delivery (ses_009-task-notification-envelope).
|
|
858
|
-
*
|
|
859
|
-
* Drains every pending task completion in one pass and emits each as
|
|
860
|
-
* a plain USER text message wrapped in the `<task-notification>`
|
|
861
|
-
* envelope the supervisor prompt expects.
|
|
862
|
-
*
|
|
863
|
-
* Why not a `tool_result` block bound to the dispatching tool_use_id:
|
|
864
|
-
* `create_task` is documented as NON-BLOCKING and returns
|
|
865
|
-
* "Task launched: …" immediately. That immediate return is already
|
|
866
|
-
* recorded as the canonical tool_result for that tool_use, so a
|
|
867
|
-
* second tool_result for the SAME tool_use_id — emitted later, after
|
|
868
|
-
* intervening assistant turns — is rejected by Anthropic with
|
|
869
|
-
* `messages.<n>.content.0: unexpected tool_use_id found in
|
|
870
|
-
* tool_result blocks` because the immediately-prior assistant
|
|
871
|
-
* message no longer carries the matching tool_use. Wrapping as a
|
|
872
|
-
* user text envelope sidesteps the pairing rule entirely.
|
|
873
|
-
*
|
|
874
|
-
* Coalescing N drops into one drain replaces the previous
|
|
875
|
-
* one-at-a-time pattern which forced a separate orchestrator
|
|
876
|
-
* iteration per completed task on wide fan-outs.
|
|
877
|
-
*/
|
|
878
|
-
private async injectOneTaskNotification(): Promise<void> {
|
|
879
|
-
if (this.ctx.pendingNotifications.length === 0) return
|
|
880
|
-
const handles = this.ctx.pendingNotifications.splice(0)
|
|
881
|
-
|
|
882
|
-
for (const handle of handles) {
|
|
883
|
-
const meta = this.ctx.launchedTasks.get(handle.taskId)
|
|
884
|
-
const resultText =
|
|
885
|
-
handle.result?.result ??
|
|
886
|
-
handle.result?.lastError ??
|
|
887
|
-
`Task finished with state: ${handle.state}`
|
|
888
|
-
|
|
889
|
-
if (meta?.planTaskId && this.ctx.taskStore) {
|
|
890
|
-
const success = handle.state === 'completed'
|
|
891
|
-
await this.ctx.taskStore.update(meta.planTaskId as `task_${string}`, {
|
|
892
|
-
status: 'completed',
|
|
893
|
-
description: success ? undefined : `Failed: ${resultText.substring(0, 200)}`,
|
|
894
|
-
})
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
this.ctx.launchedTasks.delete(handle.taskId)
|
|
898
|
-
|
|
899
|
-
// `remaining-tasks` = inflight workers still pending after this
|
|
900
|
-
// one drains. `launchedTasks` is the single source of truth:
|
|
901
|
-
// it holds every dispatched worker that has NOT yet been
|
|
902
|
-
// drained + delete()'d. The drain batch entries are still
|
|
903
|
-
// inside launchedTasks until each iteration's delete() above
|
|
904
|
-
// removes them, so reading the size right after that delete
|
|
905
|
-
// gives the honest count. Adding `handles.length - 1 - i`
|
|
906
|
-
// here used to double-count this same queue.
|
|
907
|
-
const remainingTasks = this.ctx.launchedTasks.size
|
|
908
|
-
const envelope =
|
|
909
|
-
`<task-notification>\n<task-id>${xmlEscape(handle.taskId)}</task-id>\n` +
|
|
910
|
-
`<agent-id>${xmlEscape(handle.agentId)}</agent-id>\n` +
|
|
911
|
-
`<status>${xmlEscape(handle.state)}</status>\n` +
|
|
912
|
-
`<result>${cdataWrap(resultText)}</result>\n` +
|
|
913
|
-
`<remaining-tasks>${remainingTasks}</remaining-tasks>\n</task-notification>`
|
|
914
|
-
|
|
915
|
-
this.ctx.runMgr.pushMessage(createUserMessage(envelope))
|
|
916
|
-
|
|
917
|
-
this.ctx.log.info('Task notification injected', {
|
|
918
|
-
taskId: handle.taskId,
|
|
919
|
-
agentId: handle.agentId,
|
|
920
|
-
state: handle.state,
|
|
921
|
-
planTaskId: meta?.planTaskId,
|
|
922
|
-
remainingNotifications: remainingTasks,
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
403
|
private async requestFinalResponse(model: string, reason: StopReason): Promise<void> {
|
|
928
404
|
const lastAssistant = [...this.ctx.runMgr.messages]
|
|
929
405
|
.reverse()
|
|
@@ -948,13 +424,22 @@ export class IterationOrchestrator {
|
|
|
948
424
|
),
|
|
949
425
|
]
|
|
950
426
|
|
|
427
|
+
// Same cache discipline as the forced-final iteration: keep the
|
|
428
|
+
// tools param identical to prior iterations (cache prefix intact,
|
|
429
|
+
// no 400 on tool blocks in history) and forbid use via tool_choice.
|
|
430
|
+
const finalTools = this.ctx.tools.toLLMTools(this.ctx.allowedTools)
|
|
951
431
|
const response = await collect(
|
|
952
432
|
this.ctx.provider.chatStream({
|
|
953
433
|
model,
|
|
954
434
|
messages: finalMessages,
|
|
435
|
+
tools: finalTools.length > 0 ? finalTools : undefined,
|
|
436
|
+
toolChoice: finalTools.length > 0 ? 'none' : undefined,
|
|
955
437
|
temperature: this.ctx.runConfig.temperature,
|
|
956
438
|
maxTokens: this.ctx.runConfig.maxResponseTokens,
|
|
957
439
|
cacheControl: { type: 'auto' },
|
|
440
|
+
// Cancellable too: a Stop during the closing summary must not
|
|
441
|
+
// stream to completion.
|
|
442
|
+
signal: this.ctx.abortController.signal,
|
|
958
443
|
}),
|
|
959
444
|
)
|
|
960
445
|
|