@namzu/sdk 36.0.0 → 37.0.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 +180 -0
- package/README.md +68 -41
- 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 +2 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/compaction/extractor.d.ts +1 -1
- package/dist/compaction/extractor.d.ts.map +1 -1
- package/dist/compaction/extractor.js +15 -7
- package/dist/compaction/extractor.js.map +1 -1
- package/dist/compaction/manager.d.ts +2 -0
- package/dist/compaction/manager.d.ts.map +1 -1
- package/dist/compaction/manager.js +12 -1
- package/dist/compaction/manager.js.map +1 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +82 -15
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/contracts/schemas.d.ts +10 -10
- package/dist/eval/harness-verification.d.ts +63 -0
- package/dist/eval/harness-verification.d.ts.map +1 -0
- package/dist/eval/harness-verification.js +165 -0
- package/dist/eval/harness-verification.js.map +1 -0
- package/dist/eval/index.d.ts +2 -0
- package/dist/eval/index.d.ts.map +1 -1
- package/dist/eval/index.js +1 -0
- package/dist/eval/index.js.map +1 -1
- package/dist/execution/code-runtime/json-value.d.ts +10 -0
- package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
- package/dist/execution/code-runtime/json-value.js +89 -0
- package/dist/execution/code-runtime/json-value.js.map +1 -0
- package/dist/execution/code-runtime/worker-program.d.ts +8 -0
- package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
- package/dist/execution/code-runtime/worker-program.js +240 -0
- package/dist/execution/code-runtime/worker-program.js.map +1 -0
- package/dist/execution/code-runtime/worker.d.ts +22 -0
- package/dist/execution/code-runtime/worker.d.ts.map +1 -1
- package/dist/execution/code-runtime/worker.js +148 -111
- package/dist/execution/code-runtime/worker.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +12 -0
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +306 -37
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +2 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +6 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/provider/capabilities.d.ts +3 -1
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +13 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +2 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -1
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -1
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/registry/tool/execute.d.ts +3 -0
- package/dist/registry/tool/execute.d.ts.map +1 -1
- package/dist/registry/tool/execute.js +11 -80
- package/dist/registry/tool/execute.js.map +1 -1
- package/dist/run/command-gate.d.ts.map +1 -1
- package/dist/run/command-gate.js +65 -12
- package/dist/run/command-gate.js.map +1 -1
- package/dist/run/index.d.ts +2 -0
- package/dist/run/index.d.ts.map +1 -1
- package/dist/run/index.js +1 -0
- package/dist/run/index.js.map +1 -1
- package/dist/run/memory-promoter.d.ts +3 -35
- package/dist/run/memory-promoter.d.ts.map +1 -1
- package/dist/run/memory-promoter.js +32 -40
- package/dist/run/memory-promoter.js.map +1 -1
- package/dist/run/memory-recall.d.ts +25 -0
- package/dist/run/memory-recall.d.ts.map +1 -0
- package/dist/run/memory-recall.js +228 -0
- package/dist/run/memory-recall.js.map +1 -0
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +1 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/run/token-budget.d.ts +2 -0
- package/dist/run/token-budget.d.ts.map +1 -1
- package/dist/run/token-budget.js +4 -0
- package/dist/run/token-budget.js.map +1 -1
- package/dist/run/workspace-fingerprint.d.ts +10 -3
- package/dist/run/workspace-fingerprint.d.ts.map +1 -1
- package/dist/run/workspace-fingerprint.js +17 -8
- package/dist/run/workspace-fingerprint.js.map +1 -1
- package/dist/runtime/query/checkpoint.d.ts +14 -0
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +82 -1
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +5 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +70 -14
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +5 -8
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +54 -7
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +10 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +325 -36
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/native-output.d.ts +10 -0
- package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
- package/dist/runtime/query/iteration/native-output.js +32 -0
- package/dist/runtime/query/iteration/native-output.js.map +1 -0
- package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/advisory.js +18 -13
- package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +32 -11
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
- package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
- package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +2 -0
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +6 -2
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +17 -2
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/runtime/query/tool-call-budget.d.ts +19 -0
- package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
- package/dist/runtime/query/tool-call-budget.js +110 -0
- package/dist/runtime/query/tool-call-budget.js.map +1 -0
- package/dist/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +4 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/scheduler/local.d.ts.map +1 -1
- package/dist/scheduler/local.js +1 -0
- package/dist/scheduler/local.js.map +1 -1
- package/dist/session/handoff/capacity.d.ts +2 -2
- package/dist/session/handoff/capacity.d.ts.map +1 -1
- package/dist/session/handoff/capacity.js +3 -2
- package/dist/session/handoff/capacity.js.map +1 -1
- package/dist/store/memory/disk.d.ts +6 -2
- package/dist/store/memory/disk.d.ts.map +1 -1
- package/dist/store/memory/disk.js +42 -25
- package/dist/store/memory/disk.js.map +1 -1
- package/dist/store/memory/index.d.ts +4 -0
- package/dist/store/memory/index.d.ts.map +1 -1
- package/dist/store/memory/index.js +55 -19
- package/dist/store/memory/index.js.map +1 -1
- package/dist/store/memory/memory.d.ts +3 -2
- package/dist/store/memory/memory.d.ts.map +1 -1
- package/dist/store/memory/memory.js +11 -2
- package/dist/store/memory/memory.js.map +1 -1
- package/dist/store/memory/operation-lock.d.ts +11 -0
- package/dist/store/memory/operation-lock.d.ts.map +1 -0
- package/dist/store/memory/operation-lock.js +94 -0
- package/dist/store/memory/operation-lock.js.map +1 -0
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +60 -10
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/tools/builtins/run-code.d.ts +6 -0
- package/dist/tools/builtins/run-code.d.ts.map +1 -1
- package/dist/tools/builtins/run-code.js +15 -4
- package/dist/tools/builtins/run-code.js.map +1 -1
- package/dist/tools/builtins/search-tools.d.ts.map +1 -1
- package/dist/tools/builtins/search-tools.js +14 -2
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +3 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/memory/delete.d.ts +4 -0
- package/dist/tools/memory/delete.d.ts.map +1 -0
- package/dist/tools/memory/delete.js +26 -0
- package/dist/tools/memory/delete.js.map +1 -0
- package/dist/tools/memory/index.d.ts +2 -0
- package/dist/tools/memory/index.d.ts.map +1 -1
- package/dist/tools/memory/index.js +6 -0
- package/dist/tools/memory/index.js.map +1 -1
- package/dist/tools/memory/save.d.ts.map +1 -1
- package/dist/tools/memory/save.js +3 -2
- package/dist/tools/memory/save.js.map +1 -1
- package/dist/tools/memory/search.d.ts.map +1 -1
- package/dist/tools/memory/search.js +12 -5
- package/dist/tools/memory/search.js.map +1 -1
- package/dist/tools/memory/update.d.ts +4 -0
- package/dist/tools/memory/update.d.ts.map +1 -0
- package/dist/tools/memory/update.js +47 -0
- package/dist/tools/memory/update.js.map +1 -0
- package/dist/types/agent/scheduler.d.ts +5 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +10 -0
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/computer-use/index.d.ts +6 -0
- package/dist/types/computer-use/index.d.ts.map +1 -1
- package/dist/types/computer-use/index.js.map +1 -1
- package/dist/types/errors/catalog.js +1 -1
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/hitl/index.d.ts +11 -1
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/memory/index.d.ts +14 -1
- package/dist/types/memory/index.d.ts.map +1 -1
- package/dist/types/project/entity.d.ts +1 -0
- package/dist/types/project/entity.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +2 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/provider/model.d.ts +4 -0
- package/dist/types/provider/model.d.ts.map +1 -1
- package/dist/types/run/answer-review.d.ts +2 -0
- package/dist/types/run/answer-review.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +9 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/prepare-step.d.ts +19 -1
- package/dist/types/run/prepare-step.d.ts.map +1 -1
- package/dist/types/structured-output/index.d.ts +8 -2
- package/dist/types/structured-output/index.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +15 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/utils/json-snapshot.d.ts +3 -0
- package/dist/utils/json-snapshot.d.ts.map +1 -0
- package/dist/utils/json-snapshot.js +79 -0
- package/dist/utils/json-snapshot.js.map +1 -0
- package/package.json +4 -2
- package/src/bridge/a2a/mapper.ts +2 -0
- package/src/bridge/sse/mapper.ts +2 -0
- package/src/compaction/extractor.ts +21 -8
- package/src/compaction/manager.ts +11 -1
- package/src/compaction/verifier.ts +80 -18
- package/src/eval/harness-verification.ts +270 -0
- package/src/eval/index.ts +10 -0
- package/src/execution/code-runtime/json-value.ts +82 -0
- package/src/execution/code-runtime/worker-program.ts +239 -0
- package/src/execution/code-runtime/worker.ts +164 -111
- package/src/manager/agent/lifecycle.ts +340 -45
- package/src/manager/run/persistence.ts +7 -0
- package/src/provider/capabilities.ts +25 -1
- package/src/provider/fallback.ts +2 -0
- package/src/public-runtime.ts +4 -0
- package/src/public-tools.ts +5 -1
- package/src/public-types.ts +1 -0
- package/src/registry/tool/execute.ts +17 -79
- package/src/run/command-gate.ts +61 -13
- package/src/run/index.ts +2 -0
- package/src/run/memory-promoter.ts +32 -40
- package/src/run/memory-recall.ts +254 -0
- package/src/run/reporter.ts +1 -0
- package/src/run/token-budget.ts +4 -0
- package/src/run/workspace-fingerprint.ts +17 -8
- package/src/runtime/query/checkpoint.ts +96 -2
- package/src/runtime/query/executor.ts +77 -14
- package/src/runtime/query/index.ts +73 -16
- package/src/runtime/query/iteration/index.ts +377 -44
- package/src/runtime/query/iteration/native-output.ts +35 -0
- package/src/runtime/query/iteration/phases/advisory.ts +21 -11
- package/src/runtime/query/iteration/phases/compaction.ts +36 -14
- package/src/runtime/query/iteration/phases/context.ts +9 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
- package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
- package/src/runtime/query/iteration/stream-turn.ts +2 -0
- package/src/runtime/query/steering.ts +19 -2
- package/src/runtime/query/tool-call-budget.ts +115 -0
- package/src/runtime/query/tooling.ts +6 -0
- package/src/scheduler/local.ts +1 -0
- package/src/session/handoff/capacity.ts +7 -3
- package/src/store/memory/disk.ts +52 -30
- package/src/store/memory/index.ts +75 -27
- package/src/store/memory/memory.ts +18 -6
- package/src/store/memory/operation-lock.ts +99 -0
- package/src/tools/builtins/computer-use.ts +61 -7
- package/src/tools/builtins/run-code.ts +24 -5
- package/src/tools/builtins/search-tools.ts +18 -3
- package/src/tools/defineTool.ts +5 -0
- package/src/tools/memory/delete.ts +29 -0
- package/src/tools/memory/index.ts +6 -0
- package/src/tools/memory/save.ts +4 -2
- package/src/tools/memory/search.ts +12 -5
- package/src/tools/memory/update.ts +50 -0
- package/src/types/agent/scheduler.ts +6 -0
- package/src/types/agent/task.ts +13 -0
- package/src/types/computer-use/index.ts +6 -0
- package/src/types/errors/catalog.ts +1 -1
- package/src/types/hitl/index.ts +11 -1
- package/src/types/memory/index.ts +16 -1
- package/src/types/project/entity.ts +1 -0
- package/src/types/provider/config.ts +2 -0
- package/src/types/provider/model.ts +4 -0
- package/src/types/run/answer-review.ts +2 -0
- package/src/types/run/events.ts +10 -0
- package/src/types/run/prepare-step.ts +19 -1
- package/src/types/structured-output/index.ts +15 -2
- package/src/types/tool/index.ts +16 -0
- package/src/utils/json-snapshot.ts +76 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AGENT_MANAGER_DEFAULTS } from '../../constants/agent/index.js'
|
|
2
|
+
import { EMPTY_TOKEN_USAGE } from '../../constants/limits.js'
|
|
2
3
|
import { GENAI } from '../../constants/telemetry/index.js'
|
|
3
4
|
import type { AgentRegistry } from '../../registry/agent/definitions.js'
|
|
4
5
|
import {
|
|
@@ -91,6 +92,18 @@ interface AgentManagerBaseDeps {
|
|
|
91
92
|
*/
|
|
92
93
|
export type AgentManagerDeps = AgentManagerBaseDeps & TopicManagerDependency
|
|
93
94
|
|
|
95
|
+
/** Internal backpressure: the parent's unmeasured request owns its budget until its receipt. */
|
|
96
|
+
class ParentBudgetRequestPending extends Error {}
|
|
97
|
+
|
|
98
|
+
interface PendingSpawn {
|
|
99
|
+
ready: boolean
|
|
100
|
+
readonly task: AgentTask
|
|
101
|
+
readonly options: SendMessageOptions
|
|
102
|
+
readonly context: AgentTaskContext
|
|
103
|
+
readonly listener?: RunEventListener
|
|
104
|
+
readonly removeAbortListener: () => void
|
|
105
|
+
}
|
|
106
|
+
|
|
94
107
|
interface ChildSpawnRecord {
|
|
95
108
|
subSessionId: SubSessionId
|
|
96
109
|
childSessionId: SessionId
|
|
@@ -141,6 +154,12 @@ export class AgentManager {
|
|
|
141
154
|
private spawnLocks: Map<SessionId, Promise<void>> = new Map()
|
|
142
155
|
private deps: AgentManagerDeps
|
|
143
156
|
private topicManager: TopicManager
|
|
157
|
+
private readonly pendingSpawns = new Map<SessionId, PendingSpawn[]>()
|
|
158
|
+
private readonly drainingParents = new Set<SessionId>()
|
|
159
|
+
private readonly executingTasks = new Set<TaskId>()
|
|
160
|
+
private readonly cancelingTasks = new Set<TaskId>()
|
|
161
|
+
private admissionTimer: ReturnType<typeof setTimeout> | undefined
|
|
162
|
+
private disposed = false
|
|
144
163
|
|
|
145
164
|
constructor(
|
|
146
165
|
registry: AgentRegistry,
|
|
@@ -149,6 +168,9 @@ export class AgentManager {
|
|
|
149
168
|
) {
|
|
150
169
|
this.registry = registry
|
|
151
170
|
this.config = { ...AGENT_MANAGER_DEFAULTS, ...config }
|
|
171
|
+
const maxPending = this.config.maxPendingTasks ?? 128
|
|
172
|
+
if (!Number.isSafeInteger(maxPending) || maxPending < 1)
|
|
173
|
+
throw new Error('maxPendingTasks must be a positive safe integer')
|
|
152
174
|
this.log = resolveLogger(deps.log).child({
|
|
153
175
|
[SCOPE_ATTRIBUTE]: 'manager/agent/lifecycle',
|
|
154
176
|
})
|
|
@@ -161,6 +183,182 @@ export class AgentManager {
|
|
|
161
183
|
context: AgentTaskContext,
|
|
162
184
|
listener?: RunEventListener,
|
|
163
185
|
): Promise<AgentTask> {
|
|
186
|
+
if (this.disposed) throw new Error('Agent manager is disposed')
|
|
187
|
+
if (this.config.capacityBehavior === 'queue')
|
|
188
|
+
return this.enqueueMessage(options, context, listener)
|
|
189
|
+
return this.startMessage(options, context, listener)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async enqueueMessage(
|
|
193
|
+
options: SendMessageOptions,
|
|
194
|
+
context: AgentTaskContext,
|
|
195
|
+
listener?: RunEventListener,
|
|
196
|
+
): Promise<AgentTask> {
|
|
197
|
+
context.parentAbortController.signal.throwIfAborted()
|
|
198
|
+
if (context.depth >= this.config.maxDepth)
|
|
199
|
+
throw new Error(`Max task depth ${this.config.maxDepth} exceeded`)
|
|
200
|
+
if (options.tenantId !== context.tenantId)
|
|
201
|
+
throw new Error('Tenant mismatch: cross-tenant spawn rejected')
|
|
202
|
+
const definition = this.registry.getOrThrow(options.agentId)
|
|
203
|
+
const count = [...this.pendingSpawns.values()].reduce((sum, queue) => sum + queue.length, 0)
|
|
204
|
+
if (count >= (this.config.maxPendingTasks ?? 128))
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Delegation queue is full (${this.config.maxPendingTasks ?? 128} pending tasks). Wait for existing tasks before submitting more work.`,
|
|
207
|
+
)
|
|
208
|
+
const task: AgentTask = {
|
|
209
|
+
taskId: generateTaskId(),
|
|
210
|
+
agentId: options.agentId,
|
|
211
|
+
agent: definition.typedAgent,
|
|
212
|
+
childAbortController: createChildAbortController(context.parentAbortController),
|
|
213
|
+
context,
|
|
214
|
+
state: 'pending',
|
|
215
|
+
pendingMessages: [],
|
|
216
|
+
createdAt: Date.now(),
|
|
217
|
+
runEventListener: listener,
|
|
218
|
+
}
|
|
219
|
+
this.instances.set(task.taskId, task)
|
|
220
|
+
const onAbort = (): void => {
|
|
221
|
+
// Before admission this observer owns cancellation. Once child scope
|
|
222
|
+
// exists, startup rollback may abort its controller for a configuration
|
|
223
|
+
// error; that is a failure, not a user cancellation.
|
|
224
|
+
if (task.context === context || context.parentAbortController.signal.aborted)
|
|
225
|
+
this.cancel(task.taskId, 'parent')
|
|
226
|
+
}
|
|
227
|
+
task.childAbortController.signal.addEventListener('abort', onAbort, { once: true })
|
|
228
|
+
const entry: PendingSpawn = {
|
|
229
|
+
ready: false,
|
|
230
|
+
task,
|
|
231
|
+
options,
|
|
232
|
+
context,
|
|
233
|
+
listener,
|
|
234
|
+
removeAbortListener: () =>
|
|
235
|
+
task.childAbortController.signal.removeEventListener('abort', onAbort),
|
|
236
|
+
}
|
|
237
|
+
const queue = this.pendingSpawns.get(options.parentSessionId) ?? []
|
|
238
|
+
queue.push(entry)
|
|
239
|
+
this.pendingSpawns.set(options.parentSessionId, queue)
|
|
240
|
+
this.emit({
|
|
241
|
+
type: 'pending',
|
|
242
|
+
taskId: task.taskId,
|
|
243
|
+
agentId: options.agentId,
|
|
244
|
+
parentAgentId: context.parentAgentId,
|
|
245
|
+
depth: context.depth,
|
|
246
|
+
})
|
|
247
|
+
try {
|
|
248
|
+
await listener?.({
|
|
249
|
+
type: 'agent_pending',
|
|
250
|
+
runId: context.parentRunId,
|
|
251
|
+
taskId: task.taskId,
|
|
252
|
+
parentAgentId: context.parentAgentId,
|
|
253
|
+
childAgentId: options.agentId,
|
|
254
|
+
depth: context.depth,
|
|
255
|
+
})
|
|
256
|
+
entry.ready = true
|
|
257
|
+
} catch (error) {
|
|
258
|
+
this.failAdmission(entry, error)
|
|
259
|
+
}
|
|
260
|
+
this.pumpAdmissions(options.parentSessionId)
|
|
261
|
+
return task
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private pumpAdmissions(parentSessionId: SessionId): void {
|
|
265
|
+
if (this.disposed || this.drainingParents.has(parentSessionId)) return
|
|
266
|
+
this.drainingParents.add(parentSessionId)
|
|
267
|
+
void this.drainAdmissions(parentSessionId)
|
|
268
|
+
.finally(() => {
|
|
269
|
+
this.drainingParents.delete(parentSessionId)
|
|
270
|
+
// External handoffs and metadata changes have no manager event. A
|
|
271
|
+
// single slow, unref'ed timer observes those without spinning or
|
|
272
|
+
// keeping a host alive. Actual completions wake admission directly.
|
|
273
|
+
if (!this.disposed && this.pendingSpawns.size > 0 && !this.admissionTimer) {
|
|
274
|
+
this.admissionTimer = setTimeout(() => {
|
|
275
|
+
this.admissionTimer = undefined
|
|
276
|
+
for (const parent of this.pendingSpawns.keys()) this.pumpAdmissions(parent)
|
|
277
|
+
}, 250)
|
|
278
|
+
this.admissionTimer.unref?.()
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
.catch((error) =>
|
|
282
|
+
this.log.error('Delegation admission failed', {
|
|
283
|
+
'exception.message': toErrorMessage(error),
|
|
284
|
+
}),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async drainAdmissions(parentSessionId: SessionId): Promise<void> {
|
|
289
|
+
const queue = this.pendingSpawns.get(parentSessionId)
|
|
290
|
+
if (!queue) return
|
|
291
|
+
while (!this.disposed && queue.length > 0) {
|
|
292
|
+
const entry = queue[0]
|
|
293
|
+
if (!entry) break
|
|
294
|
+
if (isTerminalAgentTaskState(entry.task.state)) {
|
|
295
|
+
entry.removeAbortListener()
|
|
296
|
+
queue.shift()
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
299
|
+
// A concurrent enqueue can pump this same FIFO while the head's
|
|
300
|
+
// pending listener still owns its acknowledgement. Do not provision
|
|
301
|
+
// or spend until that acknowledgement succeeds.
|
|
302
|
+
if (!entry.ready) return
|
|
303
|
+
try {
|
|
304
|
+
await entry.options.beforeStart?.()
|
|
305
|
+
entry.task.childAbortController.signal.throwIfAborted()
|
|
306
|
+
await this.validateSpawn(entry.options, entry.context)
|
|
307
|
+
entry.task.childAbortController.signal.throwIfAborted()
|
|
308
|
+
await this.startMessage(entry.options, entry.context, entry.listener, entry.task)
|
|
309
|
+
entry.removeAbortListener()
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (
|
|
312
|
+
(error instanceof ParentBudgetRequestPending ||
|
|
313
|
+
(error instanceof DelegationCapacityExceeded && error.details.dimension === 'width')) &&
|
|
314
|
+
!isTerminalAgentTaskState(entry.task.state)
|
|
315
|
+
)
|
|
316
|
+
return
|
|
317
|
+
this.failAdmission(entry, error)
|
|
318
|
+
}
|
|
319
|
+
queue.shift()
|
|
320
|
+
}
|
|
321
|
+
if (queue.length === 0 && this.pendingSpawns.get(parentSessionId) === queue)
|
|
322
|
+
this.pendingSpawns.delete(parentSessionId)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private failAdmission(entry: PendingSpawn, reason: unknown): void {
|
|
326
|
+
entry.removeAbortListener()
|
|
327
|
+
const task = entry.task
|
|
328
|
+
if (isTerminalAgentTaskState(task.state)) return
|
|
329
|
+
task.childAbortController.abort(reason)
|
|
330
|
+
const error = toErrorMessage(reason)
|
|
331
|
+
task.result = {
|
|
332
|
+
runId: entry.context.parentRunId,
|
|
333
|
+
status: 'failed',
|
|
334
|
+
usage: { ...EMPTY_TOKEN_USAGE },
|
|
335
|
+
cost: { ...ZERO_COST },
|
|
336
|
+
iterations: 0,
|
|
337
|
+
durationMs: Date.now() - task.createdAt,
|
|
338
|
+
messages: [],
|
|
339
|
+
lastError: error,
|
|
340
|
+
}
|
|
341
|
+
task.state = 'failed'
|
|
342
|
+
task.completedAt = Date.now()
|
|
343
|
+
this.emit({ type: 'failed', taskId: task.taskId, error })
|
|
344
|
+
this.emitRunEvent(task, {
|
|
345
|
+
type: 'agent_failed',
|
|
346
|
+
runId: entry.context.parentRunId,
|
|
347
|
+
taskId: task.taskId,
|
|
348
|
+
error,
|
|
349
|
+
})
|
|
350
|
+
this.scheduleEviction(task.taskId)
|
|
351
|
+
this.resolveCompletionCallbacks(task.taskId)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private async startMessage(
|
|
355
|
+
options: SendMessageOptions,
|
|
356
|
+
context: AgentTaskContext,
|
|
357
|
+
listener?: RunEventListener,
|
|
358
|
+
queuedTask?: AgentTask,
|
|
359
|
+
): Promise<AgentTask> {
|
|
360
|
+
await options.beforeStart?.()
|
|
361
|
+
queuedTask?.childAbortController.signal.throwIfAborted()
|
|
164
362
|
if (context.depth >= this.config.maxDepth) {
|
|
165
363
|
throw new Error(
|
|
166
364
|
`Max task depth ${this.config.maxDepth} exceeded (current: ${context.depth}). Recursive agent delegation is limited to prevent resource exhaustion.`,
|
|
@@ -200,9 +398,32 @@ export class AgentManager {
|
|
|
200
398
|
// Reserve synchronously before provisioning yields. The authority is shared
|
|
201
399
|
// across managers and nested sessions, while the session lock below only
|
|
202
400
|
// serializes filesystem capacity checks.
|
|
401
|
+
// Queued siblings share the available budget with the parent. The first
|
|
402
|
+
// of eight children receives at most one ninth; later siblings receive
|
|
403
|
+
// comparable grants instead of a geometric 1/2, 1/4, ... starvation tail.
|
|
404
|
+
let budgetShares = 1
|
|
405
|
+
if (queuedTask) {
|
|
406
|
+
const project = await requireOpenProject(
|
|
407
|
+
this.deps.sessionStore,
|
|
408
|
+
context.projectId,
|
|
409
|
+
context.tenantId,
|
|
410
|
+
'spawn',
|
|
411
|
+
)
|
|
412
|
+
const children = await this.deps.sessionStore.getChildren(
|
|
413
|
+
options.parentSessionId,
|
|
414
|
+
context.tenantId,
|
|
415
|
+
)
|
|
416
|
+
const active = children.filter(
|
|
417
|
+
(child) =>
|
|
418
|
+
child.status !== 'idle' && child.status !== 'failed' && child.status !== 'archived',
|
|
419
|
+
).length
|
|
420
|
+
budgetShares = Math.max(2, project.config.maxDelegationWidth - active + 1)
|
|
421
|
+
}
|
|
422
|
+
context.parentAbortController.signal.throwIfAborted()
|
|
423
|
+
queuedTask?.childAbortController.signal.throwIfAborted()
|
|
203
424
|
const remaining = context.budget.remaining
|
|
204
425
|
const maxAllocation = Number.isFinite(remaining)
|
|
205
|
-
? Math.floor(remaining * this.config.maxBudgetFraction)
|
|
426
|
+
? Math.floor(Math.min(remaining * this.config.maxBudgetFraction, remaining / budgetShares))
|
|
206
427
|
: (options.budgetAllocation?.tokenBudget ??
|
|
207
428
|
(options.configOverrides?.tokenBudget === 0
|
|
208
429
|
? 200_000
|
|
@@ -217,6 +438,12 @@ export class AgentManager {
|
|
|
217
438
|
message: `Cannot spawn "${options.agentId}": the parent has ${remaining} tokens remaining; a child allocation must be a finite positive integer.`,
|
|
218
439
|
})
|
|
219
440
|
}
|
|
441
|
+
// No await between this check and reserve: a parent's response may start
|
|
442
|
+
// during the scope reads above. Its own in-flight request is temporary
|
|
443
|
+
// backpressure; descendant requests are expected during a fan-out.
|
|
444
|
+
// Invalid/zero allocations above still fail rather than waiting forever.
|
|
445
|
+
if (queuedTask && context.budget.hasInFlightRequest)
|
|
446
|
+
throw new ParentBudgetRequestPending('The parent provider request is still in flight')
|
|
220
447
|
const childBudget = context.budget.reserve(allocatedTokens)
|
|
221
448
|
let spawnRecord: ChildSpawnRecord
|
|
222
449
|
try {
|
|
@@ -230,9 +457,12 @@ export class AgentManager {
|
|
|
230
457
|
let childAbortController: AbortController | undefined
|
|
231
458
|
let agentTask: AgentTask | undefined
|
|
232
459
|
try {
|
|
233
|
-
childAbortController =
|
|
460
|
+
childAbortController =
|
|
461
|
+
queuedTask?.childAbortController ??
|
|
462
|
+
createChildAbortController(context.parentAbortController)
|
|
234
463
|
|
|
235
|
-
|
|
464
|
+
childAbortController.signal.throwIfAborted()
|
|
465
|
+
const taskId = queuedTask?.taskId ?? generateTaskId()
|
|
236
466
|
|
|
237
467
|
const childParentActor: ActorRef = {
|
|
238
468
|
kind: 'agent',
|
|
@@ -279,40 +509,42 @@ export class AgentManager {
|
|
|
279
509
|
...(resolvedDenies.length > 0 ? { toolDenies: resolvedDenies } : {}),
|
|
280
510
|
}
|
|
281
511
|
|
|
282
|
-
agentTask = {
|
|
512
|
+
agentTask = Object.assign(queuedTask ?? {}, {
|
|
283
513
|
taskId,
|
|
284
514
|
agentId: options.agentId,
|
|
285
515
|
agent,
|
|
286
516
|
childAbortController,
|
|
287
517
|
context: childContext,
|
|
288
518
|
state: 'pending',
|
|
289
|
-
pendingMessages: [],
|
|
290
|
-
createdAt: Date.now(),
|
|
519
|
+
pendingMessages: queuedTask?.pendingMessages ?? [],
|
|
520
|
+
createdAt: queuedTask?.createdAt ?? Date.now(),
|
|
291
521
|
runEventListener: listener,
|
|
292
|
-
}
|
|
522
|
+
} satisfies AgentTask)
|
|
293
523
|
|
|
294
524
|
childAbortController.signal.throwIfAborted()
|
|
295
525
|
this.instances.set(taskId, agentTask)
|
|
296
526
|
if (resolvedDenies.length > 0) spawnRecord.resolvedToolDenies = resolvedDenies
|
|
297
527
|
this.spawnRecords.set(taskId, spawnRecord)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
agentId: options.agentId,
|
|
302
|
-
parentAgentId: context.parentAgentId,
|
|
303
|
-
depth: context.depth,
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
if (listener) {
|
|
307
|
-
await listener({
|
|
308
|
-
type: 'agent_pending',
|
|
309
|
-
runId: context.parentRunId,
|
|
528
|
+
if (!queuedTask)
|
|
529
|
+
this.emit({
|
|
530
|
+
type: 'pending',
|
|
310
531
|
taskId,
|
|
532
|
+
agentId: options.agentId,
|
|
311
533
|
parentAgentId: context.parentAgentId,
|
|
312
|
-
childAgentId: options.agentId,
|
|
313
534
|
depth: context.depth,
|
|
314
535
|
})
|
|
315
536
|
|
|
537
|
+
if (listener) {
|
|
538
|
+
if (!queuedTask)
|
|
539
|
+
await listener({
|
|
540
|
+
type: 'agent_pending',
|
|
541
|
+
runId: context.parentRunId,
|
|
542
|
+
taskId,
|
|
543
|
+
parentAgentId: context.parentAgentId,
|
|
544
|
+
childAgentId: options.agentId,
|
|
545
|
+
depth: context.depth,
|
|
546
|
+
})
|
|
547
|
+
|
|
316
548
|
const lineage: Lineage = {
|
|
317
549
|
parentSessionId: spawnRecord.parentSessionId,
|
|
318
550
|
rootSessionId: spawnRecord.rootSessionId,
|
|
@@ -503,17 +735,35 @@ export class AgentManager {
|
|
|
503
735
|
|
|
504
736
|
await childBudget.flush()
|
|
505
737
|
childAbortController.signal.throwIfAborted()
|
|
506
|
-
this.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
738
|
+
this.executingTasks.add(taskId)
|
|
739
|
+
const runningTask = agentTask
|
|
740
|
+
this.runChild(runningTask, options, childConfig, listener)
|
|
741
|
+
.catch(async (err) => {
|
|
742
|
+
// A thrown invocation supplied no final usage receipt. Keep its
|
|
743
|
+
// reservation, including when its task handle was canceled or evicted.
|
|
744
|
+
let failure = err
|
|
745
|
+
try {
|
|
746
|
+
await childBudget.flush()
|
|
747
|
+
} catch (writeError) {
|
|
748
|
+
failure = writeError
|
|
749
|
+
}
|
|
750
|
+
// Cancellation may already have published a terminal handle. Its
|
|
751
|
+
// invocation has only NOW stopped; release the persisted edge here.
|
|
752
|
+
if (!this.instances.has(taskId) || isTerminalAgentTaskState(runningTask.state)) {
|
|
753
|
+
await this.failSubSession(spawnRecord)
|
|
754
|
+
} else this.markFailed(taskId, toErrorMessage(failure))
|
|
755
|
+
})
|
|
756
|
+
.finally(() => {
|
|
757
|
+
this.executingTasks.delete(taskId)
|
|
758
|
+
if (!this.instances.has(taskId)) this.spawnRecords.delete(taskId)
|
|
759
|
+
this.pumpAdmissions(options.parentSessionId)
|
|
760
|
+
})
|
|
761
|
+
.catch((error) =>
|
|
762
|
+
this.log.warn('Child cleanup failed', {
|
|
763
|
+
'namzu.task.id': taskId,
|
|
764
|
+
'exception.message': toErrorMessage(error),
|
|
765
|
+
}),
|
|
766
|
+
)
|
|
517
767
|
|
|
518
768
|
return agentTask
|
|
519
769
|
} catch (err) {
|
|
@@ -521,7 +771,7 @@ export class AgentManager {
|
|
|
521
771
|
// provisioned resources are still ours to return. A builder/listener
|
|
522
772
|
// failure must not strand a pending task the caller never received.
|
|
523
773
|
if (agentTask) {
|
|
524
|
-
await this.rollbackUnstartedSpawn(agentTask, spawnRecord, err)
|
|
774
|
+
await this.rollbackUnstartedSpawn(agentTask, spawnRecord, err, queuedTask !== undefined)
|
|
525
775
|
} else {
|
|
526
776
|
childAbortController?.abort()
|
|
527
777
|
childBudget.settle(0)
|
|
@@ -536,6 +786,7 @@ export class AgentManager {
|
|
|
536
786
|
agentTask: AgentTask,
|
|
537
787
|
spawnRecord: ChildSpawnRecord,
|
|
538
788
|
reason: unknown,
|
|
789
|
+
retainHandle = false,
|
|
539
790
|
): Promise<void> {
|
|
540
791
|
agentTask.childAbortController.abort()
|
|
541
792
|
agentTask.context.budget.settle(0)
|
|
@@ -559,7 +810,20 @@ export class AgentManager {
|
|
|
559
810
|
}
|
|
560
811
|
}
|
|
561
812
|
this.clearEvictionTimer(agentTask.taskId)
|
|
562
|
-
|
|
813
|
+
if (retainHandle) {
|
|
814
|
+
agentTask.result ??= {
|
|
815
|
+
runId: agentTask.context.parentRunId,
|
|
816
|
+
status: 'failed',
|
|
817
|
+
usage: { ...EMPTY_TOKEN_USAGE },
|
|
818
|
+
cost: { ...ZERO_COST },
|
|
819
|
+
iterations: 0,
|
|
820
|
+
durationMs: Date.now() - agentTask.createdAt,
|
|
821
|
+
messages: [],
|
|
822
|
+
lastError: toErrorMessage(reason),
|
|
823
|
+
}
|
|
824
|
+
agentTask.completedAt = Date.now()
|
|
825
|
+
this.scheduleEviction(agentTask.taskId)
|
|
826
|
+
} else this.instances.delete(agentTask.taskId)
|
|
563
827
|
this.spawnRecords.delete(agentTask.taskId)
|
|
564
828
|
this.resolveCompletionCallbacks(agentTask.taskId)
|
|
565
829
|
await this.rollbackSpawnResources(spawnRecord)
|
|
@@ -581,14 +845,22 @@ export class AgentManager {
|
|
|
581
845
|
}
|
|
582
846
|
|
|
583
847
|
cancel(taskId: TaskId, cause?: CancelCause): void {
|
|
848
|
+
if (this.cancelingTasks.has(taskId)) return
|
|
584
849
|
const agentTask = this.instances.get(taskId)
|
|
585
850
|
if (!agentTask || isTerminalAgentTaskState(agentTask.state)) return
|
|
586
851
|
|
|
587
852
|
// Was the bare string `'canceled'`, which `abortReasonText` suppresses
|
|
588
853
|
// by name — its docblock cites this exact call site — so the child's
|
|
589
854
|
// run saw a cancellation with no attributable origin at all.
|
|
590
|
-
|
|
591
|
-
this
|
|
855
|
+
// Abort listeners run synchronously. A pending task's parent observer
|
|
856
|
+
// must not reenter this method and replace an explicit user's cause.
|
|
857
|
+
this.cancelingTasks.add(taskId)
|
|
858
|
+
try {
|
|
859
|
+
agentTask.childAbortController.abort(cause ? new RunCancelled(cause) : undefined)
|
|
860
|
+
this.markCanceled(taskId, cause)
|
|
861
|
+
} finally {
|
|
862
|
+
this.cancelingTasks.delete(taskId)
|
|
863
|
+
}
|
|
592
864
|
}
|
|
593
865
|
|
|
594
866
|
cancelAll(parentRunId: RunId, cause: CancelCause = 'parent'): void {
|
|
@@ -687,12 +959,18 @@ export class AgentManager {
|
|
|
687
959
|
if (isTerminalAgentTaskState(agentTask.state)) {
|
|
688
960
|
this.clearEvictionTimer(taskId)
|
|
689
961
|
this.instances.delete(taskId)
|
|
690
|
-
this.spawnRecords.delete(taskId)
|
|
962
|
+
if (!this.executingTasks.has(taskId)) this.spawnRecords.delete(taskId)
|
|
691
963
|
}
|
|
692
964
|
}
|
|
693
965
|
}
|
|
694
966
|
|
|
695
967
|
dispose(): void {
|
|
968
|
+
this.disposed = true
|
|
969
|
+
if (this.admissionTimer) clearTimeout(this.admissionTimer)
|
|
970
|
+
this.admissionTimer = undefined
|
|
971
|
+
for (const queue of this.pendingSpawns.values())
|
|
972
|
+
for (const entry of queue) entry.removeAbortListener()
|
|
973
|
+
this.pendingSpawns.clear()
|
|
696
974
|
for (const taskId of this.instances.keys()) {
|
|
697
975
|
this.clearEvictionTimer(taskId)
|
|
698
976
|
}
|
|
@@ -704,7 +982,8 @@ export class AgentManager {
|
|
|
704
982
|
this.cancel(taskId)
|
|
705
983
|
}
|
|
706
984
|
this.instances.clear()
|
|
707
|
-
this.spawnRecords.
|
|
985
|
+
for (const taskId of this.spawnRecords.keys())
|
|
986
|
+
if (!this.executingTasks.has(taskId)) this.spawnRecords.delete(taskId)
|
|
708
987
|
this.listeners.length = 0
|
|
709
988
|
}
|
|
710
989
|
|
|
@@ -748,10 +1027,10 @@ export class AgentManager {
|
|
|
748
1027
|
}
|
|
749
1028
|
}
|
|
750
1029
|
|
|
751
|
-
private async
|
|
1030
|
+
private async validateSpawn(
|
|
752
1031
|
options: SendMessageOptions,
|
|
753
1032
|
context: AgentTaskContext,
|
|
754
|
-
): Promise<
|
|
1033
|
+
): Promise<void> {
|
|
755
1034
|
// Phase 9: deps are unconditional required. Every spawn produces a
|
|
756
1035
|
// SubSession + Session + WorkspaceRef triple (Convention #0: no
|
|
757
1036
|
// partial/legacy path).
|
|
@@ -782,6 +1061,8 @@ export class AgentManager {
|
|
|
782
1061
|
`Parent session ${options.parentSessionId} not found for tenant ${context.tenantId} — spawn rejected`,
|
|
783
1062
|
)
|
|
784
1063
|
}
|
|
1064
|
+
if (parentSession.status === 'archived')
|
|
1065
|
+
throw new Error('Delegation parent session is archived')
|
|
785
1066
|
if (parentSession.topicId !== context.topicId) {
|
|
786
1067
|
throw new Error(
|
|
787
1068
|
`Topic mismatch on spawn: parent session ${parentSession.id} is on topic ${parentSession.topicId}, but context.topicId=${context.topicId}. Cross-topic spawn is forbidden (session-hierarchy.md §6.3).`,
|
|
@@ -812,6 +1093,15 @@ export class AgentManager {
|
|
|
812
1093
|
project.config.maxDelegationWidth,
|
|
813
1094
|
context.tenantId,
|
|
814
1095
|
)
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private async provisionSpawnUnlocked(
|
|
1099
|
+
options: SendMessageOptions,
|
|
1100
|
+
context: AgentTaskContext,
|
|
1101
|
+
): Promise<ChildSpawnRecord> {
|
|
1102
|
+
await this.validateSpawn(options, context)
|
|
1103
|
+
context.parentAbortController.signal.throwIfAborted()
|
|
1104
|
+
const store = this.deps.sessionStore
|
|
815
1105
|
|
|
816
1106
|
// Ancestry walk gives both the child depth and the root session id
|
|
817
1107
|
// attached to every sub-session event from here down.
|
|
@@ -1228,6 +1518,7 @@ export class AgentManager {
|
|
|
1228
1518
|
}
|
|
1229
1519
|
|
|
1230
1520
|
private scheduleEviction(taskId: TaskId): void {
|
|
1521
|
+
if (this.disposed) return
|
|
1231
1522
|
const agentTask = this.instances.get(taskId)
|
|
1232
1523
|
if (!agentTask) return
|
|
1233
1524
|
|
|
@@ -1235,7 +1526,7 @@ export class AgentManager {
|
|
|
1235
1526
|
|
|
1236
1527
|
const timer = setTimeout(() => {
|
|
1237
1528
|
this.instances.delete(taskId)
|
|
1238
|
-
this.spawnRecords.delete(taskId)
|
|
1529
|
+
if (!this.executingTasks.has(taskId)) this.spawnRecords.delete(taskId)
|
|
1239
1530
|
this.evictionTimers.delete(taskId)
|
|
1240
1531
|
this.log.info('Agent task evicted', { 'namzu.agent.task_id': taskId })
|
|
1241
1532
|
}, this.config.evictionMs)
|
|
@@ -1274,15 +1565,19 @@ export class AgentManager {
|
|
|
1274
1565
|
|
|
1275
1566
|
private emitRunEvent(agentTask: AgentTask, event: RunEvent): void {
|
|
1276
1567
|
if (!agentTask.runEventListener) return
|
|
1277
|
-
|
|
1278
|
-
agentTask.runEventListener(event)
|
|
1279
|
-
} catch (err) {
|
|
1568
|
+
const reportFailure = (error: unknown): void => {
|
|
1280
1569
|
this.log.error('RunEvent emission error', {
|
|
1281
1570
|
'namzu.event.type': event.type,
|
|
1282
|
-
'
|
|
1283
|
-
'exception.message': toErrorMessage(err),
|
|
1571
|
+
'exception.message': toErrorMessage(error),
|
|
1284
1572
|
})
|
|
1285
1573
|
}
|
|
1574
|
+
try {
|
|
1575
|
+
// Terminal observation must not delay completion or leak a rejected
|
|
1576
|
+
// listener promise into the host as an unhandled rejection.
|
|
1577
|
+
void Promise.resolve(agentTask.runEventListener(event)).catch(reportFailure)
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
reportFailure(error)
|
|
1580
|
+
}
|
|
1286
1581
|
}
|
|
1287
1582
|
}
|
|
1288
1583
|
|
|
@@ -306,6 +306,7 @@ export class RunPersistence {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
markFailed(error: string, providerError?: ProviderErrorInfo): void {
|
|
309
|
+
this.clearStructuredOutput()
|
|
309
310
|
this.run.status = 'failed'
|
|
310
311
|
this.run.stopReason = 'error'
|
|
311
312
|
this.run.lastError = error
|
|
@@ -314,6 +315,7 @@ export class RunPersistence {
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
markCancelled(): void {
|
|
318
|
+
this.clearStructuredOutput()
|
|
317
319
|
this.run.status = 'cancelled'
|
|
318
320
|
this.run.stopReason = 'cancelled'
|
|
319
321
|
this.run.endedAt = Date.now()
|
|
@@ -459,6 +461,11 @@ export class RunPersistence {
|
|
|
459
461
|
* and without the override flag it would walk the tail and put the stale
|
|
460
462
|
* prose back.
|
|
461
463
|
*/
|
|
464
|
+
/** Invalidate a structured value without replacing the host's textual result. */
|
|
465
|
+
clearStructuredOutput(): void {
|
|
466
|
+
delete this.run.structuredOutput
|
|
467
|
+
}
|
|
468
|
+
|
|
462
469
|
setStructuredOutput(value: unknown): void {
|
|
463
470
|
this.run.structuredOutput = value
|
|
464
471
|
this.setResult(typeof value === 'string' ? value : JSON.stringify(value))
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ChatCompletionParams,
|
|
3
|
+
LLMProvider,
|
|
4
|
+
ProviderCapabilities,
|
|
5
|
+
} from '../types/provider/index.js'
|
|
6
|
+
import { ProviderRequestError } from './errors.js'
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Fully-resolved capability set: every flag present, no optionals left.
|
|
@@ -59,3 +64,22 @@ export function resolveProviderCapabilities(
|
|
|
59
64
|
maxOutputTokens: declared?.maxOutputTokens,
|
|
60
65
|
}
|
|
61
66
|
}
|
|
67
|
+
|
|
68
|
+
/** Require explicit wire support before dispatching a native JSON Schema request. */
|
|
69
|
+
export function assertNativeStructuredOutputSupported(
|
|
70
|
+
provider: Pick<LLMProvider, 'id' | 'capabilities'>,
|
|
71
|
+
params: Pick<ChatCompletionParams, 'responseFormat'>,
|
|
72
|
+
): void {
|
|
73
|
+
if (
|
|
74
|
+
params.responseFormat?.type === 'json_schema' &&
|
|
75
|
+
provider.capabilities?.supportsNativeStructuredOutput !== true
|
|
76
|
+
) {
|
|
77
|
+
throw new ProviderRequestError({
|
|
78
|
+
kind: 'bad_request',
|
|
79
|
+
providerId: provider.id,
|
|
80
|
+
providerCode: 'native_structured_output_unsupported',
|
|
81
|
+
detail:
|
|
82
|
+
'Native JSON Schema output requires a provider that declares supportsNativeStructuredOutput: true.',
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/provider/fallback.ts
CHANGED
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
ThinkingConfig,
|
|
32
32
|
} from '../types/provider/index.js'
|
|
33
33
|
import type { Logger } from '../utils/logger.js'
|
|
34
|
+
import { assertNativeStructuredOutputSupported } from './capabilities.js'
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* One member of the chain: a constructed provider, and the model to ask it for.
|
|
@@ -396,6 +397,7 @@ export function withProviderFallback(
|
|
|
396
397
|
chainIndex: cursor,
|
|
397
398
|
},
|
|
398
399
|
}
|
|
400
|
+
assertNativeStructuredOutputSupported(member.provider, request)
|
|
399
401
|
let produced = false
|
|
400
402
|
try {
|
|
401
403
|
for await (const chunk of member.provider.chatStream(request)) {
|
package/src/public-runtime.ts
CHANGED
|
@@ -380,6 +380,8 @@ export type {
|
|
|
380
380
|
// nothing.
|
|
381
381
|
export { RUN_MEMORY_TAG, createMemoryPromoter } from './run/index.js'
|
|
382
382
|
export type { MemoryPromoterOptions } from './run/index.js'
|
|
383
|
+
export { createMemoryRecallStep } from './run/memory-recall.js'
|
|
384
|
+
export type { MemoryRecallOptions } from './run/memory-recall.js'
|
|
383
385
|
|
|
384
386
|
// ─── personas, skills, advisory ──────────────────────────────────────────
|
|
385
387
|
|
|
@@ -1111,6 +1113,8 @@ export { anyOf, hasToolCall, stepCountIs } from './types/run/step.js'
|
|
|
1111
1113
|
// ─── evaluation harness ──────────────────────────────────────────────────
|
|
1112
1114
|
|
|
1113
1115
|
export {
|
|
1116
|
+
compareHarnessTrials,
|
|
1117
|
+
reviewHarnessCandidate,
|
|
1114
1118
|
completionScorer,
|
|
1115
1119
|
containsScorer,
|
|
1116
1120
|
customScorer,
|
package/src/public-tools.ts
CHANGED
|
@@ -97,7 +97,11 @@ export {
|
|
|
97
97
|
buildTaskUpdateTool,
|
|
98
98
|
} from './tools/task/index.js'
|
|
99
99
|
export { buildAdvisoryTools } from './tools/advisory/index.js'
|
|
100
|
-
export {
|
|
100
|
+
export {
|
|
101
|
+
buildMemoryTools,
|
|
102
|
+
buildUpdateMemoryTool,
|
|
103
|
+
buildDeleteMemoryTool,
|
|
104
|
+
} from './tools/memory/index.js'
|
|
101
105
|
export { buildCoordinatorTools } from './tools/coordinator/index.js'
|
|
102
106
|
export {
|
|
103
107
|
buildAskUserQuestionTool,
|
package/src/public-types.ts
CHANGED
|
@@ -19,6 +19,7 @@ export type * from './types/common/index.js'
|
|
|
19
19
|
export type { CoalesceOptions } from './streaming/coalesce.js'
|
|
20
20
|
export type * from './types/bidi/index.js'
|
|
21
21
|
export type * from './types/tool/index.js'
|
|
22
|
+
export type { WorkerCodeRuntimeOptions } from './execution/code-runtime/worker.js'
|
|
22
23
|
export type {
|
|
23
24
|
CodeRunOutcome,
|
|
24
25
|
CodeRunResult,
|