@sema-agent/core 5.64.0 → 6.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 +70 -0
- package/dist/agents/subagent.d.ts +2 -2
- package/dist/agents/subagent.js +11 -0
- package/dist/agents/verify.d.ts +1 -1
- package/dist/brain/anthropic.js +1 -1
- package/dist/brain/errors.d.ts +29 -0
- package/dist/brain/errors.js +20 -0
- package/dist/brain/open-responses.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +1 -0
- package/dist/brain/status-sink.js +12 -1
- package/dist/brain/stream-engine.js +17 -6
- package/dist/core/auto-compaction.d.ts +26 -0
- package/dist/core/auto-compaction.js +7 -2
- package/dist/core/auto-mode-arming.d.ts +138 -0
- package/dist/core/auto-mode-arming.js +181 -0
- package/dist/core/auto-mode-defaults.d.ts +13 -0
- package/dist/core/auto-mode-defaults.js +5 -0
- package/dist/core/auto-mode-prompt.d.ts +14 -3
- package/dist/core/auto-mode-prompt.js +10 -7
- package/dist/core/auto-mode-rebuild.d.ts +75 -0
- package/dist/core/auto-mode-rebuild.js +41 -0
- package/dist/core/auto-mode.d.ts +15 -0
- package/dist/core/auto-mode.js +4 -2
- package/dist/core/checkpoint-store.d.ts +113 -4
- package/dist/core/context-edit.d.ts +47 -5
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/file-history-retention.d.ts +106 -0
- package/dist/core/file-history-retention.js +36 -0
- package/dist/core/file-history-store.d.ts +768 -0
- package/dist/core/file-history-store.js +880 -0
- package/dist/core/governance-codes.d.ts +2 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +48 -8
- package/dist/core/hooks.js +39 -22
- package/dist/core/lsp.d.ts +2 -2
- package/dist/core/mcp.d.ts +29 -7
- package/dist/core/memory-engine/consolidation-driver.d.ts +11 -0
- package/dist/core/memory-engine/consolidation-driver.js +71 -4
- package/dist/core/memory-engine/consolidation.d.ts +25 -2
- package/dist/core/memory-engine/consolidation.js +4 -1
- package/dist/core/memory-engine/distiller.d.ts +84 -1
- package/dist/core/memory-engine/distiller.js +68 -0
- package/dist/core/memory-engine/dual-root.js +6 -0
- package/dist/core/memory-engine/engine.d.ts +329 -15
- package/dist/core/memory-engine/engine.js +364 -34
- package/dist/core/memory-engine/file-backend.d.ts +30 -0
- package/dist/core/memory-engine/file-backend.js +14 -13
- package/dist/core/memory-engine/frontmatter.d.ts +22 -1
- package/dist/core/memory-engine/frontmatter.js +3 -0
- package/dist/core/memory-engine/header-hints.d.ts +5 -0
- package/dist/core/memory-engine/index.d.ts +5 -4
- package/dist/core/memory-engine/index.js +5 -4
- package/dist/core/memory-engine/layout.d.ts +88 -2
- package/dist/core/memory-engine/layout.js +112 -3
- package/dist/core/memory-engine/provenance-wording.d.ts +7 -0
- package/dist/core/memory-engine/provenance-wording.js +3 -0
- package/dist/core/memory-engine/tools.d.ts +89 -8
- package/dist/core/memory-engine/tools.js +263 -22
- package/dist/core/memory-engine/types.d.ts +80 -1
- package/dist/core/memory-recall.d.ts +6 -0
- package/dist/core/memory.d.ts +27 -1
- package/dist/core/memory.js +16 -2
- package/dist/core/permission-rule-consent.d.ts +20 -0
- package/dist/core/permission-rule-consent.js +12 -3
- package/dist/core/permission-rule-model.d.ts +67 -7
- package/dist/core/permission-rule-model.js +53 -7
- package/dist/core/permission-rule-store.js +15 -10
- package/dist/core/permission-rule-sync.js +15 -11
- package/dist/core/remote-env.d.ts +3 -3
- package/dist/core/retention-policy.d.ts +9 -0
- package/dist/core/retention-policy.js +5 -2
- package/dist/core/retention.d.ts +13 -2
- package/dist/core/runner/assemble-result.d.ts +19 -1
- package/dist/core/runner/assemble-result.js +17 -2
- package/dist/core/runner/compaction-call-options.d.ts +93 -0
- package/dist/core/runner/compaction-call-options.js +3 -0
- package/dist/core/runner/memory-capture-optout.d.ts +80 -0
- package/dist/core/runner/memory-capture-optout.js +53 -0
- package/dist/core/runner/prepare-config-doors.d.ts +5 -0
- package/dist/core/runner/prepare-config-doors.js +16 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +119 -5
- package/dist/core/runner/prepare-hands-readface.js +103 -8
- package/dist/core/runner/prepare-memory.d.ts +88 -0
- package/dist/core/runner/prepare-memory.js +306 -25
- package/dist/core/runner/prepare-task.d.ts +156 -5
- package/dist/core/runner/prepare-task.js +488 -98
- package/dist/core/runner/runtask.d.ts +27 -20
- package/dist/core/runner/runtask.js +283 -99
- package/dist/core/runner/session-file-state-replay.d.ts +18 -10
- package/dist/core/runner/session-file-state-replay.js +52 -1
- package/dist/core/runner/tool-disclosure.js +2 -1
- package/dist/core/runner/turn-attachments.d.ts +22 -12
- package/dist/core/session-store.d.ts +1 -1
- package/dist/core/session-store.js +6 -1
- package/dist/core/session.d.ts +34 -1
- package/dist/core/store-contracts/file-history-store-contract.d.ts +3 -0
- package/dist/core/store-contracts/file-history-store-contract.js +720 -0
- package/dist/core/stub-env.d.ts +4 -0
- package/dist/core/stub-env.js +1 -0
- package/dist/core/task-registry-shared.js +30 -2
- package/dist/core/tool-errors.js +1 -0
- package/dist/core/tool-policy.d.ts +172 -1
- package/dist/core/tool-policy.js +32 -1
- package/dist/core/tool-result-store.js +2 -1
- package/dist/core/trace.d.ts +24 -0
- package/dist/core/types.d.ts +875 -97
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-text.d.ts +1 -1
- package/dist/core/untrusted-text.js +8 -0
- package/dist/core/workflow-run-store-contract.js +17 -0
- package/dist/core/workflow-run-store.d.ts +20 -0
- package/dist/core/workflow-run-store.js +1 -0
- package/dist/engine/compaction/compaction.d.ts +88 -10
- package/dist/engine/compaction/compaction.js +109 -30
- package/dist/engine/execution-env/node-execution-env.d.ts +9 -1
- package/dist/engine/execution-env/node-execution-env.js +28 -0
- package/dist/engine/harness/agent-harness.d.ts +52 -1
- package/dist/engine/harness/agent-harness.js +36 -1
- package/dist/engine/harness/types.d.ts +44 -1
- package/dist/engine/llm/types.d.ts +50 -4
- package/dist/engine/loop/agent-loop.d.ts +5 -1
- package/dist/engine/loop/agent-loop.js +25 -0
- package/dist/engine/loop/types.d.ts +19 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
- package/dist/engine/session/session.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +14 -6
- package/dist/orchestration/run-workflow-tool.d.ts +20 -2
- package/dist/orchestration/run-workflow-tool.js +22 -3
- package/dist/orchestration/workflow-governance.d.ts +59 -1
- package/dist/orchestration/workflow-governance.js +61 -8
- package/dist/orchestration/workflow-meta.d.ts +4 -2
- package/dist/orchestration/workflow-primitives.js +56 -13
- package/dist/orchestration/workflow-types.d.ts +112 -1
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/orchestration/workflow.d.ts +20 -0
- package/dist/orchestration/workflow.js +182 -14
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompts/default.d.ts +15 -7
- package/dist/prompts/default.js +3 -0
- package/dist/stores/file/file-history-store.d.ts +368 -0
- package/dist/stores/file/file-history-store.js +1248 -0
- package/dist/stores/file/index.d.ts +22 -13
- package/dist/stores/file/index.js +4 -4
- package/dist/stores/file/permission-rule-store.js +1 -0
- package/dist/stores/file/strategy-store.d.ts +3 -3
- package/dist/tools/fs/bash-readonly-classifier.d.ts +87 -3
- package/dist/tools/fs/bash-readonly-classifier.js +106 -4
- package/dist/tools/fs/fs-bash.js +9 -5
- package/dist/tools/fs/fs-shared.d.ts +52 -1
- package/dist/tools/fs/fs-shared.js +14 -0
- package/dist/tools/fs/fs-write.d.ts +5 -5
- package/dist/tools/fs/fs-write.js +71 -14
- package/dist/tools/fs/index.d.ts +6 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/web.js +2 -1
- package/package.json +5 -1
- package/test/export-surface.snapshot.json +159 -23
- package/dist/core/file-snapshot-store.d.ts +0 -165
- package/dist/core/file-snapshot-store.js +0 -259
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +0 -13
- package/dist/core/store-contracts/file-snapshot-store-contract.js +0 -134
- package/dist/stores/file/file-snapshot-store.d.ts +0 -58
- package/dist/stores/file/file-snapshot-store.js +0 -353
|
@@ -230,6 +230,22 @@ export interface RunWorkflowToolDeps {
|
|
|
230
230
|
* reason `forwardEvent`/`inheritedGateForChildren` ride deps); `ctx.interactionPosture` still
|
|
231
231
|
* wins when a wrapping path provides it. */
|
|
232
232
|
parentInteractionPosture?: "interactive" | "headless";
|
|
233
|
+
/**
|
|
234
|
+
* design/383 §2.5 (rescan post-6.0.0-RC) — the HOST session's live capture opt-out state,
|
|
235
|
+
* threaded as a MOUNT dep for the same unenriched-auto-mount reason as the posture seat above.
|
|
236
|
+
* A call-time GETTER on purpose (`ctx.memoryCaptureOptedOut` is live — a mid-run flip must
|
|
237
|
+
* floor agents the workflow spawns after it); the enriched-ctx getters win when a wrapping
|
|
238
|
+
* mount provides them. Rides `RunWorkflowOptions.parentMemoryCaptureState` into both spawn legs.
|
|
239
|
+
*/
|
|
240
|
+
parentMemoryCaptureState?: () => {
|
|
241
|
+
optedOut: boolean;
|
|
242
|
+
indeterminate: boolean;
|
|
243
|
+
controlDir?: string;
|
|
244
|
+
ancestors?: ReadonlyArray<{
|
|
245
|
+
sessionId: string;
|
|
246
|
+
controlDir?: string;
|
|
247
|
+
}>;
|
|
248
|
+
};
|
|
233
249
|
/** TRUSTED nesting depth from the run's internals (NOT a tool param) — passed to `startWorkflow` so a
|
|
234
250
|
* cross-process child workflow is rejected by the one-level guard. */
|
|
235
251
|
workflowDepth?: number;
|
|
@@ -317,8 +333,10 @@ export interface RunWorkflowToolDeps {
|
|
|
317
333
|
parentCheckpointStoreDisabled?: boolean;
|
|
318
334
|
/** #235 — the deployment's structured notice sink (`RunnerDeps.onNotice`), forwarded into the governed
|
|
319
335
|
* build so the fields a script's agent spec wrote that did NOT reach the child are announced instead of
|
|
320
|
-
* vanishing (`workflow.governance_key_stripped`).
|
|
321
|
-
*
|
|
336
|
+
* vanishing (`workflow.governance_key_stripped`). #505 ① — the same seat now also carries the OPTIONS
|
|
337
|
+
* membrane's twin announcement (`workflow.agent_option_ignored`: keys of `agent(spec, opts)`'s second
|
|
338
|
+
* argument that did not apply). A dep for the same reason as the seats above: this auto-mount's execute
|
|
339
|
+
* ctx is minimal. Absent ⇒ the historic `console.warn` loudness. */
|
|
322
340
|
onNotice?: (n: import("../core/types.js").EngineNotice) => void;
|
|
323
341
|
/** Twin of the above for design/148 S1's center-artifact inheritance (see the `ctx.centerArtifactDigest`/
|
|
324
342
|
* `ctx.centerSourceRevision` reads at the `startWorkflow` options site) — `centerAdoption` resolves
|
|
@@ -330,8 +330,12 @@ export async function createRunWorkflowTool(d) {
|
|
|
330
330
|
"until the next phase() call. Scoped — await phase('Fix', async () => { ...agents... }); groups just the " +
|
|
331
331
|
"body (equivalently, pass { phase: 'Fix' } in an agent's opts). Give each stage a phase so progress " +
|
|
332
332
|
"renders as stage groups; use the same titles you declared in meta.phases.\n" +
|
|
333
|
-
"• spec.modelName works only when the deployment configured a workflow model allowlist
|
|
334
|
-
"
|
|
333
|
+
"• spec.modelName works only when the deployment configured a workflow model allowlist, and its legal " +
|
|
334
|
+
"values are that deployment's own model CATALOG KEYS — not generic tier words (a bare 'opus'/'sonnet'/" +
|
|
335
|
+
"'haiku' is a name the catalog almost certainly does not hold, and is refused rather than approximated). " +
|
|
336
|
+
"It also goes on the SPEC (the first argument), never in an agent()'s options object; `model` is accepted " +
|
|
337
|
+
"there as an alias for the same field. If unsure, omit it and the agent runs on the deployment's default " +
|
|
338
|
+
"role.\n" +
|
|
335
339
|
"\n" +
|
|
336
340
|
"Don't race. After launching, you know nothing about what the workflow found. Never fabricate or predict its " +
|
|
337
341
|
"results in any format — not as prose, summary, or structured output. When a completion notifier is wired " +
|
|
@@ -507,6 +511,19 @@ export async function createRunWorkflowTool(d) {
|
|
|
507
511
|
parentToolCallId: ctx.toolCallId,
|
|
508
512
|
...(sourceTaskId !== undefined ? { parentTaskId: sourceTaskId } : {}),
|
|
509
513
|
...((ctx.interactionPosture ?? d.parentInteractionPosture) !== undefined ? { interactionPosture: (ctx.interactionPosture ?? d.parentInteractionPosture) } : {}),
|
|
514
|
+
...(() => {
|
|
515
|
+
if ("memoryCaptureOptedOut" in ctx) {
|
|
516
|
+
return {
|
|
517
|
+
parentMemoryCaptureState: () => ({
|
|
518
|
+
optedOut: ctx.memoryCaptureOptedOut === true,
|
|
519
|
+
indeterminate: ctx.memoryCaptureIndeterminate === true,
|
|
520
|
+
...(ctx.memoryCaptureControlDir !== undefined ? { controlDir: ctx.memoryCaptureControlDir } : {}),
|
|
521
|
+
...(ctx.memoryCaptureAncestors !== undefined ? { ancestors: ctx.memoryCaptureAncestors } : {}),
|
|
522
|
+
}),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return d.parentMemoryCaptureState !== undefined ? { parentMemoryCaptureState: d.parentMemoryCaptureState } : {};
|
|
526
|
+
})(),
|
|
510
527
|
...(d.parentModel !== undefined ? { defaultModel: d.parentModel } : {}),
|
|
511
528
|
...(d.parentGetApiKeyAndHeaders !== undefined ? { defaultGetApiKeyAndHeaders: d.parentGetApiKeyAndHeaders } : {}),
|
|
512
529
|
...(parentCenterArtifactDigest !== undefined ? { parentCenterArtifactDigest } : {}),
|
|
@@ -607,12 +624,14 @@ export async function createRunWorkflowTool(d) {
|
|
|
607
624
|
.catch(() => { });
|
|
608
625
|
}
|
|
609
626
|
return {
|
|
610
|
-
details: { type: "workflow-run", status: "backgrounded", workflowRunId: runId },
|
|
627
|
+
details: { type: "workflow-run", status: "backgrounded", workflowRunId: runId, task_id: runId },
|
|
611
628
|
content: JSON.stringify({
|
|
612
629
|
runId,
|
|
613
630
|
task_id: runId,
|
|
614
631
|
status: "started",
|
|
615
632
|
...(persistedScriptPath !== undefined ? { scriptPath: persistedScriptPath } : {}),
|
|
633
|
+
handling: "This tool result is internal metadata — never quote or paste any part of it (the ids above, and scriptPath when present) into a user-facing reply. " +
|
|
634
|
+
"In your own words, briefly tell the user what you launched; do not echo this result.",
|
|
616
635
|
note: (() => {
|
|
617
636
|
const pollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}" })` : undefined;
|
|
618
637
|
const blockingPollExpr = d.taskRegistry ? `TaskOutput({ task_id: "${runId}", block: true })` : undefined;
|
|
@@ -37,6 +37,14 @@ export declare class WorkflowModelNotAllowedError extends Error {
|
|
|
37
37
|
*/
|
|
38
38
|
export interface WorkflowAgentSpec {
|
|
39
39
|
objective: string;
|
|
40
|
+
/**
|
|
41
|
+
* The canonical spelling, and the only one this type expresses. At RUNTIME the spec ALSO accepts
|
|
42
|
+
* `model` as an alias for this field (see {@link SPEC_KEY_ALIASES}) — the same axis is spelled
|
|
43
|
+
* `model` by the Agent tool and by agents frontmatter, and a script author who reaches for the
|
|
44
|
+
* spelling those two taught should not silently get the deployment's default model instead. The
|
|
45
|
+
* alias carries a NAME exactly like this field does; a `Model` OBJECT is refused under either
|
|
46
|
+
* spelling.
|
|
47
|
+
*/
|
|
40
48
|
modelName?: string;
|
|
41
49
|
thinking?: ThinkingLevel;
|
|
42
50
|
systemPrompt?: string;
|
|
@@ -83,6 +91,28 @@ export interface WorkflowAgentSpec {
|
|
|
83
91
|
* for a BREAKING window.
|
|
84
92
|
*/
|
|
85
93
|
export declare const WHITELIST_KEYS: readonly ["objective", "modelName", "thinking", "systemPrompt", "images", "limits", "readFace", "readDenyPatterns"];
|
|
94
|
+
/**
|
|
95
|
+
* #505 ② — ACCEPTED ALIAS SPELLINGS on the spec: `alias → the {@link WHITELIST_KEYS} member it means`.
|
|
96
|
+
*
|
|
97
|
+
* One axis, three faces, two words: the Agent tool takes `model`, agents frontmatter takes `model`, and
|
|
98
|
+
* this seam takes `modelName` (the name is not free to change here — `model` on a `TaskSpec` is the
|
|
99
|
+
* resolved `Model` OBJECT, and the phases meta of the workflow tool already spends the word too). Renaming
|
|
100
|
+
* across three surfaces buys uniformity at the price of a breaking churn in two of them; MUTUAL
|
|
101
|
+
* RECOGNITION buys the same authoring outcome for the price of this map. So the alias is normalized here,
|
|
102
|
+
* at the single door untrusted spec objects come through, and NOTHING downstream of {@link pickWhitelist}
|
|
103
|
+
* ever sees the alias spelling — the rest of this module, `buildGovernedChildSpec` and `resolveModelName`
|
|
104
|
+
* included, keeps reading exactly one field.
|
|
105
|
+
*
|
|
106
|
+
* An alias is NOT a whitelist key: `WHITELIST_KEYS` stays the closed set of fields that map onto `TaskSpec`,
|
|
107
|
+
* so the "no control-plane key on the whitelist" property is untouched and a `Model` object written as
|
|
108
|
+
* `model` is still structurally unable to reach the child (it is type-refused, not copied — see the
|
|
109
|
+
* normalization at the foot of `pickWhitelist`).
|
|
110
|
+
*
|
|
111
|
+
* A test pins that this map has exactly ONE member, because the normalization arm below is written for that
|
|
112
|
+
* one member by name: reading it generically would mean re-reading `s[canonical]` a second time, and the
|
|
113
|
+
* single-read discipline every other field in this file follows is worth more than a loop over one entry.
|
|
114
|
+
*/
|
|
115
|
+
export declare const SPEC_KEY_ALIASES: ReadonlyMap<string, string>;
|
|
86
116
|
/**
|
|
87
117
|
* One field of an untrusted agent spec that did NOT reach the child, and why.
|
|
88
118
|
* - `not_whitelisted` — the field is outside {@link WHITELIST_KEYS}: a control-plane field, a work field
|
|
@@ -99,8 +129,36 @@ export interface StrippedSpecKeyNote {
|
|
|
99
129
|
/** The {@link EngineNotice} family for the strip announcement (see {@link WHITELIST_KEYS}). ONE notice per
|
|
100
130
|
* governed build, listing every field that did not cross the seam — a governed script can carry an
|
|
101
131
|
* arbitrary number of unrecognized fields, and a per-field notice would turn one authoring mistake into a
|
|
102
|
-
* flood. Not de-duplicated across builds: each spawn is a distinct fact about a distinct spec
|
|
132
|
+
* flood. Not de-duplicated across builds: each spawn is a distinct fact about a distinct spec — and
|
|
133
|
+
* `detail.occurrenceId` (#495) is what makes that sentence checkable by a consumer, which otherwise saw
|
|
134
|
+
* two builds of one spec as one repeated notice with every key equal. */
|
|
103
135
|
export declare const STRIPPED_KEYS_NOTICE_CODE = "workflow.governance_key_stripped";
|
|
136
|
+
/**
|
|
137
|
+
* #505 ① — the OPTIONS-side twin of {@link STRIPPED_KEYS_NOTICE_CODE}. `agent(spec, opts)`'s SECOND
|
|
138
|
+
* argument crosses its own membrane (`safeAgentOptions` in workflow-primitives.ts), which copies a small
|
|
139
|
+
* accepted set and dropped everything else in silence — a mistyped `lable`, an option that belongs on
|
|
140
|
+
* another seam, or a known option carrying a value its type check rejects (`label: 42`) all vanished with
|
|
141
|
+
* the call still succeeding.
|
|
142
|
+
*
|
|
143
|
+
* A SEPARATE code from the spec-side strip on purpose: the two membranes accept different key sets and the
|
|
144
|
+
* message names the set, so an operator reading `workflow.governance_key_stripped` about an OPTIONS key
|
|
145
|
+
* would look for it in the spec whitelist and correctly not find it. The severe half of this family — a
|
|
146
|
+
* SPEC field written on the options object — is not announced at all but REFUSED at the membrane (a
|
|
147
|
+
* silently ignored `model` runs the child on the deployment default, which is a different agent than the
|
|
148
|
+
* author asked for); this notice covers only the residue that is genuinely just ignored.
|
|
149
|
+
*/
|
|
150
|
+
export declare const IGNORED_AGENT_OPTIONS_NOTICE_CODE = "workflow.agent_option_ignored";
|
|
151
|
+
/** The announcement budget, shared with the spec-side survey: one bound, one renderer, one place. Exported
|
|
152
|
+
* so the surveying caller can stop COLLECTING at the same number this emitter would render. */
|
|
153
|
+
export declare const MAX_ANNOUNCED_KEYS = 20;
|
|
154
|
+
/**
|
|
155
|
+
* Announce the ignored option keys — one aggregated notice per `agent()` call, never one per key (an
|
|
156
|
+
* options object's key count is the script's to choose). `sample` is expected pre-bounded by the caller to
|
|
157
|
+
* {@link MAX_ANNOUNCED_KEYS}; `total` is exact. Key names are script-authored text landing on an operator
|
|
158
|
+
* surface, so they cross {@link renderStrippedKey} — the same scrubber the spec-side line uses, never a
|
|
159
|
+
* second one — and the caller quotes nothing itself.
|
|
160
|
+
*/
|
|
161
|
+
export declare function emitIgnoredAgentOptionsNotice(total: number, sample: readonly string[], accepted: readonly string[], onNotice?: (n: EngineNotice) => void): void;
|
|
104
162
|
/** Per-child workflow ceilings the engine forces onto every spawned agent (design/98 §D.6), independent of
|
|
105
163
|
* what the script asks for. The child's effective limits = min(script, baseline, these). */
|
|
106
164
|
export interface WorkflowChildCaps {
|
|
@@ -3,6 +3,7 @@ import { tightenTaskSpec } from "../core/tighten-task-spec.js";
|
|
|
3
3
|
import { sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
4
4
|
import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
5
5
|
import { WorkflowScriptError } from "./workflow-meta.js";
|
|
6
|
+
import { uuidv7 } from "../engine/session/uuid.js";
|
|
6
7
|
export class WorkflowModelNotAllowedError extends Error {
|
|
7
8
|
modelName;
|
|
8
9
|
code = "workflow.model_not_allowed";
|
|
@@ -23,6 +24,7 @@ export const WHITELIST_KEYS = [
|
|
|
23
24
|
"readDenyPatterns",
|
|
24
25
|
];
|
|
25
26
|
const WHITELIST_KEY_SET = new Set(WHITELIST_KEYS);
|
|
27
|
+
export const SPEC_KEY_ALIASES = new Map([["model", "modelName"]]);
|
|
26
28
|
export const STRIPPED_KEYS_NOTICE_CODE = "workflow.governance_key_stripped";
|
|
27
29
|
const MAX_STRIPPED_KEYS_ANNOUNCED = 20;
|
|
28
30
|
const MAX_STRIPPED_KEY_CHARS = 64;
|
|
@@ -50,7 +52,27 @@ function emitStrippedKeysNotice(survey, onNotice) {
|
|
|
50
52
|
deliverEngineNotice(onNotice, {
|
|
51
53
|
code: STRIPPED_KEYS_NOTICE_CODE,
|
|
52
54
|
message,
|
|
53
|
-
detail: { total: survey.total, stripped: shown, ...(omitted > 0 ? { omitted } : {}) },
|
|
55
|
+
detail: { total: survey.total, stripped: shown, ...(omitted > 0 ? { omitted } : {}), occurrenceId: uuidv7() },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export const IGNORED_AGENT_OPTIONS_NOTICE_CODE = "workflow.agent_option_ignored";
|
|
62
|
+
export const MAX_ANNOUNCED_KEYS = MAX_STRIPPED_KEYS_ANNOUNCED;
|
|
63
|
+
export function emitIgnoredAgentOptionsNotice(total, sample, accepted, onNotice) {
|
|
64
|
+
const shown = sample.map(renderStrippedKey);
|
|
65
|
+
const omitted = total - shown.length;
|
|
66
|
+
const list = shown.map((k) => JSON.stringify(k)).join(", ") + (omitted > 0 ? `, +${omitted} more` : "");
|
|
67
|
+
const message = `workflow: ${total} key(s) of an \`agent(spec, opts)\` OPTIONS object (the SECOND argument) were not applied (${list}). ` +
|
|
68
|
+
`The options object accepts only: ${accepted.join(", ")} — an unrecognized key, or a known one carrying a value its ` +
|
|
69
|
+
`option does not accept, is ignored and the agent runs without it. Everything about the CHILD's behavior belongs on ` +
|
|
70
|
+
`the spec (the first argument).`;
|
|
71
|
+
try {
|
|
72
|
+
deliverEngineNotice(onNotice, {
|
|
73
|
+
code: IGNORED_AGENT_OPTIONS_NOTICE_CODE,
|
|
74
|
+
message,
|
|
75
|
+
detail: { total, ignored: shown, ...(omitted > 0 ? { omitted } : {}), occurrenceId: uuidv7() },
|
|
54
76
|
});
|
|
55
77
|
}
|
|
56
78
|
catch {
|
|
@@ -177,17 +199,38 @@ function pickWhitelist(scriptSpec) {
|
|
|
177
199
|
}
|
|
178
200
|
const s = scriptSpec;
|
|
179
201
|
const stripped = { total: 0, sample: [] };
|
|
202
|
+
let canonicalIsOwn = false;
|
|
203
|
+
let aliasIsOwn = false;
|
|
180
204
|
for (const k of Object.keys(s)) {
|
|
181
|
-
if (
|
|
205
|
+
if (k === "modelName")
|
|
206
|
+
canonicalIsOwn = true;
|
|
207
|
+
else if (k === "model")
|
|
208
|
+
aliasIsOwn = true;
|
|
209
|
+
if (WHITELIST_KEY_SET.has(k) || SPEC_KEY_ALIASES.has(k))
|
|
182
210
|
continue;
|
|
183
211
|
stripped.total++;
|
|
184
212
|
if (stripped.sample.length < MAX_STRIPPED_KEYS_ANNOUNCED)
|
|
185
213
|
stripped.sample.push({ key: k, reason: "not_whitelisted" });
|
|
186
214
|
}
|
|
215
|
+
const canonicalDesc = Object.getOwnPropertyDescriptor(s, "modelName");
|
|
216
|
+
const aliasDesc = Object.getOwnPropertyDescriptor(s, "model");
|
|
217
|
+
if ((canonicalIsOwn && canonicalDesc === undefined) || (aliasIsOwn && aliasDesc === undefined)) {
|
|
218
|
+
throw new WorkflowScriptError("agent(spec): this spec reports its own properties inconsistently between two reads of the same object — " +
|
|
219
|
+
"the model field cannot be resolved from a shape that changes while it is being read. Pass a plain object.");
|
|
220
|
+
}
|
|
221
|
+
const hiddenOwnSpelling = !canonicalIsOwn && canonicalDesc !== undefined ? "modelName" : !aliasIsOwn && aliasDesc !== undefined ? "model" : undefined;
|
|
222
|
+
if (hiddenOwnSpelling !== undefined) {
|
|
223
|
+
throw new WorkflowScriptError(`agent(spec): \`${hiddenOwnSpelling}\` is an OWN property of this spec that its own-key enumeration does not report ` +
|
|
224
|
+
`(a non-enumerable own key, or an object that answers differently across two reads). A model spelling the spec ` +
|
|
225
|
+
`hides from its own shape is refused rather than read as an inherited default — write it as an ordinary property.`);
|
|
226
|
+
}
|
|
227
|
+
const valueOfSnapshot = (d) => (d === undefined ? undefined : d.get !== undefined ? d.get.call(s) : d.value);
|
|
228
|
+
const modelNameInheritedRaw = canonicalIsOwn ? undefined : s.modelName;
|
|
229
|
+
const modelNameOwnRaw = valueOfSnapshot(canonicalDesc);
|
|
230
|
+
const modelAliasRaw = valueOfSnapshot(aliasDesc);
|
|
187
231
|
const objective = s.objective;
|
|
188
232
|
const thinking = s.thinking;
|
|
189
233
|
const systemPrompt = s.systemPrompt;
|
|
190
|
-
const modelNameRaw = s.modelName;
|
|
191
234
|
if (typeof objective !== "string" || objective.length === 0) {
|
|
192
235
|
throw new WorkflowScriptError("agent(spec): `objective` is required and must be a non-empty string");
|
|
193
236
|
}
|
|
@@ -257,12 +300,22 @@ function pickWhitelist(scriptSpec) {
|
|
|
257
300
|
}
|
|
258
301
|
safe.readDenyPatterns = entries;
|
|
259
302
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
303
|
+
const readModelName = (raw, spelling) => {
|
|
304
|
+
if (raw === undefined)
|
|
305
|
+
return undefined;
|
|
306
|
+
if (typeof raw !== "string") {
|
|
307
|
+
throw new WorkflowScriptError(`agent(spec): \`${spelling}\` must be a string (a model NAME, never a Model object)` +
|
|
308
|
+
(spelling === "modelName" ? "" : ` — \`${spelling}\` is accepted as an alias for \`modelName\`, and carries a name just like it`));
|
|
309
|
+
}
|
|
310
|
+
return raw;
|
|
311
|
+
};
|
|
312
|
+
const authoredCanonical = readModelName(modelNameOwnRaw, "modelName");
|
|
313
|
+
const aliasModelName = readModelName(modelAliasRaw, "model");
|
|
314
|
+
if (authoredCanonical !== undefined && aliasModelName !== undefined && authoredCanonical !== aliasModelName) {
|
|
315
|
+
throw new WorkflowScriptError(`agent(spec): \`modelName\` and \`model\` are two spellings of ONE field but name DIFFERENT models ` +
|
|
316
|
+
`("${authoredCanonical}" vs "${aliasModelName}") — write exactly one of them (\`modelName\` is the canonical spelling).`);
|
|
265
317
|
}
|
|
318
|
+
const modelName = authoredCanonical ?? aliasModelName ?? readModelName(modelNameInheritedRaw, "modelName");
|
|
266
319
|
return { safe, modelName, stripped };
|
|
267
320
|
}
|
|
268
321
|
function clampResourceLimits(safe, base, caps) {
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* "fix your meta" error BEFORE the (hard) sandbox ever runs the body.
|
|
13
13
|
*/
|
|
14
14
|
import type { WorkflowMeta } from "./workflow-script-runner.js";
|
|
15
|
-
/** Thrown when
|
|
16
|
-
* a forbidden construct)
|
|
15
|
+
/** Thrown when a workflow script is the party at fault: malformed at the static layer (bad meta, oversized
|
|
16
|
+
* source, a forbidden construct), or — at RUN time — calling a primitive with a shape it cannot run (a
|
|
17
|
+
* host-API stub in the sandbox, an unusable agent spec, a `parallel`/`pipeline` member that is not a
|
|
18
|
+
* function). Carries a `code` so the tool can return a structured error the model can act on. */
|
|
17
19
|
export declare class WorkflowScriptError extends Error {
|
|
18
20
|
readonly code = "workflow.script_error";
|
|
19
21
|
constructor(message: string);
|
|
@@ -1,21 +1,64 @@
|
|
|
1
1
|
import { assertSupportedAgentIsolation } from "./workflow.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { WorkflowScriptError } from "./workflow-meta.js";
|
|
3
|
+
import { buildGovernedChildSpec, emitIgnoredAgentOptionsNotice, overlayWorktreeBaseline, MAX_ANNOUNCED_KEYS, SPEC_KEY_ALIASES, WHITELIST_KEYS, } from "./workflow-governance.js";
|
|
4
|
+
const ACCEPTED_AGENT_OPTION_KEYS = ["label", "phase", "schema", "agentType", "isolation"];
|
|
5
|
+
const ACCEPTED_AGENT_OPTION_KEY_SET = new Set(ACCEPTED_AGENT_OPTION_KEYS);
|
|
6
|
+
const SPEC_KEYS_MISPLACED_ON_OPTIONS = new Map([
|
|
7
|
+
...WHITELIST_KEYS.map((k) => [k, k]),
|
|
8
|
+
...SPEC_KEY_ALIASES,
|
|
9
|
+
["effort", "thinking"],
|
|
10
|
+
]);
|
|
11
|
+
function safeAgentOptions(opts, onNotice) {
|
|
4
12
|
if (typeof opts !== "object" || opts === null)
|
|
5
13
|
return {};
|
|
6
14
|
const o = opts;
|
|
15
|
+
const names = Object.keys(o);
|
|
16
|
+
for (const name of names) {
|
|
17
|
+
const spelling = SPEC_KEYS_MISPLACED_ON_OPTIONS.get(name);
|
|
18
|
+
if (spelling === undefined || o[name] === undefined)
|
|
19
|
+
continue;
|
|
20
|
+
throw new WorkflowScriptError(`agent(spec, opts): \`${name}\` is a SPEC field written on the OPTIONS object (the SECOND argument), where nothing ` +
|
|
21
|
+
`reads it — move it into the spec (the FIRST argument)${name === spelling ? "" : `, spelled \`${spelling}\``}. ` +
|
|
22
|
+
`The options object accepts only: ${ACCEPTED_AGENT_OPTION_KEYS.join(", ")}. The agent was NOT started: silently ` +
|
|
23
|
+
`ignoring \`${name}\` would have run a different child than the one asked for.`);
|
|
24
|
+
}
|
|
7
25
|
const out = {};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (typeof
|
|
11
|
-
out.
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (
|
|
26
|
+
const rejected = new Set();
|
|
27
|
+
const label = o.label;
|
|
28
|
+
if (typeof label === "string")
|
|
29
|
+
out.label = label;
|
|
30
|
+
else if (label !== undefined)
|
|
31
|
+
rejected.add("label");
|
|
32
|
+
const phase = o.phase;
|
|
33
|
+
if (typeof phase === "string")
|
|
34
|
+
out.phase = phase;
|
|
35
|
+
else if (phase !== undefined)
|
|
36
|
+
rejected.add("phase");
|
|
37
|
+
const schema = o.schema;
|
|
38
|
+
if (schema !== undefined)
|
|
39
|
+
out.schema = schema;
|
|
40
|
+
const agentType = o.agentType;
|
|
41
|
+
if (typeof agentType === "string")
|
|
42
|
+
out.agentType = agentType;
|
|
43
|
+
else if (agentType !== undefined)
|
|
44
|
+
rejected.add("agentType");
|
|
45
|
+
const isolation = o.isolation;
|
|
46
|
+
assertSupportedAgentIsolation(isolation);
|
|
47
|
+
if (isolation === "worktree")
|
|
18
48
|
out.isolation = "worktree";
|
|
49
|
+
else if (isolation !== undefined)
|
|
50
|
+
rejected.add("isolation");
|
|
51
|
+
let total = 0;
|
|
52
|
+
const sample = [];
|
|
53
|
+
for (const name of names) {
|
|
54
|
+
if (ACCEPTED_AGENT_OPTION_KEY_SET.has(name) && !rejected.has(name))
|
|
55
|
+
continue;
|
|
56
|
+
total++;
|
|
57
|
+
if (sample.length < MAX_ANNOUNCED_KEYS)
|
|
58
|
+
sample.push(name);
|
|
59
|
+
}
|
|
60
|
+
if (total > 0)
|
|
61
|
+
emitIgnoredAgentOptionsNotice(total, sample, ACCEPTED_AGENT_OPTION_KEYS, onNotice);
|
|
19
62
|
return out;
|
|
20
63
|
}
|
|
21
64
|
function formatResourceClampNote(notes) {
|
|
@@ -26,7 +69,7 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentThi
|
|
|
26
69
|
const agent = (spec, opts) => {
|
|
27
70
|
if (typeof spec === "string")
|
|
28
71
|
spec = { objective: spec };
|
|
29
|
-
const agentOpts = safeAgentOptions(opts);
|
|
72
|
+
const agentOpts = safeAgentOptions(opts, governance?.onNotice);
|
|
30
73
|
const effectiveBaseline = (b) => agentOpts.isolation === "worktree" && b.worktreeBase != null ? { ...b, base: overlayWorktreeBaseline(b.base, b.worktreeBase) } : b;
|
|
31
74
|
const childSpec = governance
|
|
32
75
|
? buildGovernedChildSpec(spec, effectiveBaseline(governance.baseline), governance.models, governance.caps, (notes) => ctx.log(formatResourceClampNote(notes)), governance.onNotice)
|
|
@@ -88,13 +88,60 @@ export interface WorkflowAgentRun {
|
|
|
88
88
|
/** F5/B4: total attempts this call made (set only when > 1 — stall/throttle retries happened). The record's
|
|
89
89
|
* `stats` are the FINAL attempt's; burned retry spend rolls into `run.stats`/budget (CC semantics). */
|
|
90
90
|
attempts?: number;
|
|
91
|
-
/** F5/B4: why the LAST retry happened — "stalled" (progress watchdog)
|
|
91
|
+
/** F5/B4: why the LAST retry happened — "stalled" (progress watchdog), "throttled" (degraded response),
|
|
92
|
+
* or (#481) "awaiting_approval": the watchdog fired while this leg had an outstanding human approval, so
|
|
93
|
+
* the retry happened but was NOT charged against the stall budget. Kept a free-form `string` (it always
|
|
94
|
+
* was) — a consumer that switched on the first two words must treat an unknown word as "some retry
|
|
95
|
+
* happened", which is what the field has always meant. */
|
|
92
96
|
lastAttemptReason?: string;
|
|
97
|
+
/** #481 — the leg's outstanding human approvals as the DEPLOYMENT's approval plane last reported them
|
|
98
|
+
* (verbatim from {@link TaskResult.pendingApproval}: core mints no ask ids and estimates nothing). Written
|
|
99
|
+
* LAST-WINS on every attempt terminal and on the leg's own terminal, and REMOVED when a later report
|
|
100
|
+
* carries none — so a settled leg never keeps advertising a wait that has since been decided. Absent
|
|
101
|
+
* whenever the deployment does not report it, which is the ordinary world. */
|
|
102
|
+
pendingApproval?: {
|
|
103
|
+
askIds: readonly string[];
|
|
104
|
+
oldestCreatedAtMs: number;
|
|
105
|
+
};
|
|
106
|
+
/** #485 — how long this leg spent BLOCKED on human approvals, in ms, as the deployment's approval plane
|
|
107
|
+
* measured it ({@link TaskResult.approvalWaitedMs}, carried verbatim). The engine starts no approval
|
|
108
|
+
* clock and does no arithmetic on this: a receipt's figure is already that leg's cumulative wait, so a
|
|
109
|
+
* later receipt SUPERSEDES an earlier one (last-wins) and is never added to it — summing successive
|
|
110
|
+
* reports of one cumulative counter double-counts. Absent when the deployment does not report it.
|
|
111
|
+
*
|
|
112
|
+
* A REPLAYED leg ({@link replayed}) never carries this, nor {@link pendingApproval}, even when the
|
|
113
|
+
* journaled result it was minted from did. The wait was paid by the run that actually made the call; a
|
|
114
|
+
* resume that served the answer from cache waited for nobody, and copying the figure forward would let
|
|
115
|
+
* this run's total-timeout disclosure report a wait it never experienced. */
|
|
116
|
+
approvalWaitedMs?: number;
|
|
93
117
|
/** design/114 — the session id of this agent's run (= its `TaskResult.sessionId`). Surfaces the conversation
|
|
94
118
|
* handle to the observation layer so an external initiator can WARM-resume a failed/timed-out agent
|
|
95
119
|
* (`fork(sessionId)→runTask`, or `runTask({sessionId})`) instead of a fresh re-run. An opaque id (not
|
|
96
120
|
* content) → scope-gated via `getWorkflowRun`, no redaction needed. Absent for a stub run that minted none. */
|
|
97
121
|
sessionId?: string;
|
|
122
|
+
/**
|
|
123
|
+
* #499 — the ENGINE-minted identity of the task run behind this record (= its `TaskResult.runId`).
|
|
124
|
+
*
|
|
125
|
+
* Named `taskRunId`, NOT `runId`: in this file and in every `WorkflowEvent`, `runId` already means
|
|
126
|
+
* the WORKFLOW run's id, and two different runs wearing one field name on sibling objects is the
|
|
127
|
+
* "同名异形" hazard {@link WorkflowRun} documents elsewhere. This one names the CHILD leg's engine run.
|
|
128
|
+
*
|
|
129
|
+
* Why it is not covered by {@link sessionId}: retry attempts of one leg REUSE the session
|
|
130
|
+
* (the attempt spec carries the same `sessionId`), so a record whose {@link attempts} > 1 cannot be
|
|
131
|
+
* attributed to a specific attempt by session — and the last-wins seats beside it
|
|
132
|
+
* ({@link pendingApproval} / {@link approvalWaitedMs}) are reported per attempt. This is the axis
|
|
133
|
+
* that says WHICH run produced the values on this row.
|
|
134
|
+
*
|
|
135
|
+
* Written LAST-WINS at four folds: the leg's terminal, the salvaged stall-exhaustion tail, the
|
|
136
|
+
* REPLAYED path (where — like the session handle beside it — it names the ORIGINAL run that produced
|
|
137
|
+
* the cached result, a true and joinable fact about where those bytes came from), and each RETRY
|
|
138
|
+
* ATTEMPT's report. The last one is load-bearing rather than tidy: the attempt fold writes
|
|
139
|
+
* {@link pendingApproval} / {@link approvalWaitedMs} from a specific attempt and persists the row
|
|
140
|
+
* immediately, so while an approval is outstanding the durable row holds one run's figures — and
|
|
141
|
+
* without this it held them under no id able to name that run. Absent whenever the underlying result
|
|
142
|
+
* carried none (a stub run, or a leg whose prepare was never entered).
|
|
143
|
+
*/
|
|
144
|
+
taskRunId?: string;
|
|
98
145
|
/** RB-393① — the WORKTREE directory this agent ran in, present ONLY for an
|
|
99
146
|
* `isolation: "worktree"` agent whose worktree the engine actually minted (reported by the runner's
|
|
100
147
|
* workspace-observation seam and folded on while the agent is still RUNNING, so an interrupted run's
|
|
@@ -192,6 +239,70 @@ export interface WorkflowRun {
|
|
|
192
239
|
spentTokens: number;
|
|
193
240
|
unsettledTokens?: number;
|
|
194
241
|
};
|
|
242
|
+
/** The run's TERMINAL total-timeout interruption — present ONLY when the `totalTimeoutMs` deadline had
|
|
243
|
+
* FIRED by the time this run reached its (failed) terminal, absent on every other run: one that finished
|
|
244
|
+
* inside its deadline, one that failed for its own reason before the deadline, and one that armed no
|
|
245
|
+
* deadline at all. Same ADDITIVE-observation contract as {@link agentFailures} and
|
|
246
|
+
* {@link budgetOvershoot} (never a gate input, never re-read by the engine, status enums untouched).
|
|
247
|
+
*
|
|
248
|
+
* WHY IT EXISTS: the deadline abandons the script body, so the run finalizes `failed` carrying the
|
|
249
|
+
* deadline's own message — and that is ALL any downstream face had to go on. A run whose agents had in
|
|
250
|
+
* fact answered (their outputs already on the record, inlined as `partial_results` on a poll) read as a
|
|
251
|
+
* total loss. This seat is the missing distinction: it states that the deadline is what ended the run,
|
|
252
|
+
* and what the run had ACHIEVED at that moment. Extending {@link WorkflowRunStatus} with a separate
|
|
253
|
+
* terminal word would say the same thing at the cost of a wire contract every consumer switches on, so
|
|
254
|
+
* the fact rides an optional member instead.
|
|
255
|
+
*
|
|
256
|
+
* `timeoutMs` is the deadline that fired (the run's configured `totalTimeoutMs`), so a reader can raise
|
|
257
|
+
* it deliberately rather than guess it.
|
|
258
|
+
*
|
|
259
|
+
* ⚠️ THE COUNTS ARE A POINT-IN-TIME SNAPSHOT, taken BEFORE the terminal fold that closes abandoned
|
|
260
|
+
* agents. That is the whole point (it reports what the deadline INTERRUPTED), and it is also why
|
|
261
|
+
* `agentsFailed` here is NOT {@link agentFailures}: the terminal fold marks every still-running agent
|
|
262
|
+
* `failed`, so the record's tally counts them and this member does not. The two are different questions
|
|
263
|
+
* about the same run — "how many had failed on their own when the clock ran out" versus "how many agent
|
|
264
|
+
* records ended failed" — and a consumer that swaps one for the other reports abandoned work as failed
|
|
265
|
+
* work. `agentsInFlight` counts records still `running` at that moment, which includes calls still
|
|
266
|
+
* QUEUED for a concurrency slot (they had not been refused, they had not finished).
|
|
267
|
+
*
|
|
268
|
+
* The seat is an OBJECT rather than a flat pair of members so a later disclosure about the same terminal
|
|
269
|
+
* (how much of the deadline was spent waiting on something outside the run, say) joins it additively with
|
|
270
|
+
* no shape change on any of the three faces. `approvalWaitedMs` is that later disclosure, landed:
|
|
271
|
+
*
|
|
272
|
+
* #481/#485 `approvalWaitedMs` — the LARGEST single leg's `WorkflowAgentRun.approvalWaitedMs`, i.e. the
|
|
273
|
+
* longest any one agent spent blocked on a human approval, as the deployment's approval plane itself
|
|
274
|
+
* measured it. It answers the one question the four counts above cannot: was this run SLOW, or was it
|
|
275
|
+
* WAITING? A value near `timeoutMs` says the window went on a person, not on work — raise the deadline
|
|
276
|
+
* and you buy nothing; get the approval answered faster and you buy the whole run.
|
|
277
|
+
*
|
|
278
|
+
* Read it as a LOWER BOUND on the window spent blocked, never as a total. It is deliberately the MAX and
|
|
279
|
+
* never a SUM: legs wait in parallel, so summing would report more waiting than the window contains and
|
|
280
|
+
* break the only comparison the number is for. Every figure is the deployment's own — core starts no
|
|
281
|
+
* approval clock and does no arithmetic on the values it is handed (see
|
|
282
|
+
* {@link WorkflowAgentRun.approvalWaitedMs}).
|
|
283
|
+
*
|
|
284
|
+
* ABSENT when no leg reported one, which includes every deployment that does not report at all. Absence
|
|
285
|
+
* is NOT zero: "this deployment does not supply the fact" and "nobody waited" are different states and a
|
|
286
|
+
* zero would collapse them.
|
|
287
|
+
*
|
|
288
|
+
* ⚠️ SAME POINT-IN-TIME DISCIPLINE AS THE COUNTS, and it has a real edge worth stating plainly: only
|
|
289
|
+
* figures ALREADY ON the agent records when the deadline fired can contribute. A leg still IN FLIGHT at
|
|
290
|
+
* that moment is abandoned — its final receipt reaches the finalized-run guard and is never folded — so
|
|
291
|
+
* it contributes the LAST figure it reported, not its closing one. In practice a leg that is waiting
|
|
292
|
+
* reports once per stall window (each aborted attempt's receipt folds, see
|
|
293
|
+
* `WorkflowAgentRun.approvalWaitedMs`), so the seat carries a figure that lags by at most one window;
|
|
294
|
+
* a deployment with the progress watchdog disabled, or a deadline that fires inside the first window,
|
|
295
|
+
* can leave it absent while a person was in fact being waited on. This is why the member is documented
|
|
296
|
+
* as a LOWER BOUND and never as an accounting of the window. Folding an abandoned leg's closing receipt
|
|
297
|
+
* instead would mean the deadline waiting on the very work it exists to abandon, and would make these
|
|
298
|
+
* five members describe a moment that never existed. */
|
|
299
|
+
timeoutInterruption?: {
|
|
300
|
+
timeoutMs: number;
|
|
301
|
+
agentsCompleted: number;
|
|
302
|
+
agentsFailed: number;
|
|
303
|
+
agentsInFlight: number;
|
|
304
|
+
approvalWaitedMs?: number;
|
|
305
|
+
};
|
|
195
306
|
phases: WorkflowPhase[];
|
|
196
307
|
agents: WorkflowAgentRun[];
|
|
197
308
|
/** design/97 CORE-3: nested `ctx.workflow` sub-groups (the persisted group tree). Empty when the script
|
|
@@ -67,11 +67,11 @@ export class WorkflowMaxAgentsError extends Error {
|
|
|
67
67
|
`Add a hard iteration cap to the loop, or pass a token budget.`
|
|
68
68
|
: spentTokens !== undefined && spentTokens > budgetTotal
|
|
69
69
|
? `Workflow agent() call cap reached (${max}), and the token budget is ALREADY EXCEEDED ` +
|
|
70
|
-
`(${spentTokens.toLocaleString()} spent / ${budgetTotal.toLocaleString()}
|
|
70
|
+
`(${spentTokens.toLocaleString()} spent / ${budgetTotal.toLocaleString()} total tokens): agents already in flight when ` +
|
|
71
71
|
`the ceiling was reached are not bound by the per-call gate, so their spend landed on top of it. BOTH bounds are ` +
|
|
72
72
|
`binding — raising maxAgents alone would only buy more overshoot. Fan out over fewer items, or lower concurrency ` +
|
|
73
73
|
`(it bounds the overshoot) and raise the token budget deliberately.`
|
|
74
|
-
: `Workflow agent() call cap reached (${max}). A token budget IS set (${budgetTotal.toLocaleString()}
|
|
74
|
+
: `Workflow agent() call cap reached (${max}). A token budget IS set (${budgetTotal.toLocaleString()} total tokens), ` +
|
|
75
75
|
`so this is the CALL-COUNT cap, not the token ceiling: the script asked for more than ${max} agent() calls. ` +
|
|
76
76
|
`Fan out over fewer items, or raise maxAgents.`);
|
|
77
77
|
this.max = max;
|
|
@@ -386,6 +386,26 @@ export interface RunWorkflowOptions {
|
|
|
386
386
|
* spec) so a root's per-run posture override governs its workflow children too. Set by the
|
|
387
387
|
* `run_workflow` tool wiring from `ToolExecuteContext.interactionPosture`. */
|
|
388
388
|
interactionPosture?: "interactive" | "headless";
|
|
389
|
+
/**
|
|
390
|
+
* design/383 §2.5 (rescan post-6.0.0-RC) — the HOST session's capture opt-out state, read AT
|
|
391
|
+
* EACH SPAWN (call-time getter, deliberately not a launch snapshot: `optedOut` is live — a
|
|
392
|
+
* mid-run `optOutMemoryCapture` flip must floor agents spawned after it). Threaded onto both
|
|
393
|
+
* spawn legs' internals as the same floor/indeterminate/queryDir/ancestor seats the subagent
|
|
394
|
+
* lane forwards, so a workflow child of an opted-out host commits nothing — the design's ruled
|
|
395
|
+
* disposition for the workflow HOST lane. Set by the `run_workflow` tool wiring (enriched ctx
|
|
396
|
+
* getters, else the auto-mount dep). Absent ⇒ no capture seat rides (a direct `runWorkflow`
|
|
397
|
+
* caller with no memory-mounted host — `originatingSessionId` still rides as `parentSessionId`
|
|
398
|
+
* for the record-query leg).
|
|
399
|
+
*/
|
|
400
|
+
parentMemoryCaptureState?: () => {
|
|
401
|
+
optedOut: boolean;
|
|
402
|
+
indeterminate: boolean;
|
|
403
|
+
controlDir?: string;
|
|
404
|
+
ancestors?: ReadonlyArray<{
|
|
405
|
+
sessionId: string;
|
|
406
|
+
controlDir?: string;
|
|
407
|
+
}>;
|
|
408
|
+
};
|
|
389
409
|
/** Call-time getter for the HOST run's RESOLVED Model object. A spawned agent whose
|
|
390
410
|
* fold chain (script spec → agentType → governance baseline) produced NO model inherits the
|
|
391
411
|
* parent's full object — baseUrl/key routing included — instead of falling to a string/role
|