@stigmer/runner 3.40.0 → 3.41.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/README.md +3 -1
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +2 -1
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/activities/call-agent.js +1 -1
- package/dist/activities/discover-mcp-server.d.ts +7 -1
- package/dist/activities/discover-mcp-server.js +13 -29
- package/dist/activities/discover-mcp-server.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/fold.d.ts +4 -2
- package/dist/activities/execute-cursor/__test-utils__/fold.js +3 -4
- package/dist/activities/execute-cursor/__test-utils__/fold.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +1 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/approval-policy.d.ts +1 -29
- package/dist/activities/execute-cursor/approval-policy.js +11 -35
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/boundary-rows.js +4 -4
- package/dist/activities/execute-cursor/boundary-rows.js.map +1 -1
- package/dist/activities/execute-cursor/translator.d.ts +12 -17
- package/dist/activities/execute-cursor/translator.js +13 -30
- package/dist/activities/execute-cursor/translator.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.js +4 -2
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +11 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +22 -3
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
- package/dist/activities/execute-deep-agent/inline-publisher.d.ts +33 -2
- package/dist/activities/execute-deep-agent/inline-publisher.js +33 -7
- package/dist/activities/execute-deep-agent/inline-publisher.js.map +1 -1
- package/dist/activities/execute-deep-agent/platform-route.d.ts +48 -13
- package/dist/activities/execute-deep-agent/platform-route.js +87 -19
- package/dist/activities/execute-deep-agent/platform-route.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +70 -35
- package/dist/activities/execute-deep-agent/prompt-builder.js +70 -55
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +20 -3
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +26 -4
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/translator.d.ts +5 -4
- package/dist/activities/execute-deep-agent/translator.js +5 -4
- package/dist/activities/execute-deep-agent/translator.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-settle.js +6 -6
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +18 -13
- package/dist/activities/execute-deep-agent/turn-setup.js +42 -36
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +10 -6
- package/dist/activities/execute-deep-agent/turn-stream.js +6 -16
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn.d.ts +4 -1
- package/dist/activities/execute-deep-agent/turn.js +15 -6
- package/dist/activities/execute-deep-agent/turn.js.map +1 -1
- package/dist/activities/workflow-event-activities.js +2 -0
- package/dist/activities/workflow-event-activities.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/exit-backstop.d.ts +64 -0
- package/dist/exit-backstop.js +79 -0
- package/dist/exit-backstop.js.map +1 -0
- package/dist/harness/terminal-table.d.ts +12 -5
- package/dist/harness/terminal-table.js +13 -13
- package/dist/harness/terminal-table.js.map +1 -1
- package/dist/harness/transcript/builder.d.ts +21 -0
- package/dist/harness/transcript/builder.js +53 -31
- package/dist/harness/transcript/builder.js.map +1 -1
- package/dist/harness/transcript/events.d.ts +22 -26
- package/dist/harness/transcript/events.js +6 -5
- package/dist/harness/transcript/events.js.map +1 -1
- package/dist/harness/turn-context.d.ts +3 -1
- package/dist/harness/turn-context.js +5 -3
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/main.js +10 -4
- package/dist/main.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +6 -3
- package/dist/middleware/cost-advisory.js +6 -3
- package/dist/middleware/cost-advisory.js.map +1 -1
- package/dist/middleware/execution-budget.d.ts +22 -7
- package/dist/middleware/execution-budget.js +57 -35
- package/dist/middleware/execution-budget.js.map +1 -1
- package/dist/middleware/index.d.ts +10 -7
- package/dist/middleware/index.js +10 -7
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/types.d.ts +6 -1
- package/dist/runner-manager.js +3 -2
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +25 -0
- package/dist/runner.js +41 -14
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +23 -13
- package/dist/shared/approval-policy.js +47 -14
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/caller-identity.d.ts +4 -5
- package/dist/shared/caller-identity.js +4 -5
- package/dist/shared/caller-identity.js.map +1 -1
- package/dist/shared/channel-attachment.js +3 -2
- package/dist/shared/channel-attachment.js.map +1 -1
- package/dist/shared/connect-backfill.d.ts +2 -1
- package/dist/shared/connect-backfill.js +2 -2
- package/dist/shared/connect-backfill.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +10 -1
- package/dist/shared/mcp-resolver.js +11 -2
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/memory-attachment.js +3 -2
- package/dist/shared/memory-attachment.js.map +1 -1
- package/dist/shared/model-error.d.ts +25 -3
- package/dist/shared/model-error.js +78 -10
- package/dist/shared/model-error.js.map +1 -1
- package/dist/shared/platform-server-address.d.ts +71 -0
- package/dist/shared/platform-server-address.js +103 -0
- package/dist/shared/platform-server-address.js.map +1 -0
- package/dist/shared/prompt-sections.d.ts +6 -28
- package/dist/shared/prompt-sections.js +6 -35
- package/dist/shared/prompt-sections.js.map +1 -1
- package/dist/shared/skill-resolver.d.ts +2 -3
- package/dist/shared/skill-resolver.js +2 -3
- package/dist/shared/skill-resolver.js.map +1 -1
- package/dist/shared/synthesized-attachment.d.ts +0 -5
- package/dist/shared/synthesized-attachment.js +0 -12
- package/dist/shared/synthesized-attachment.js.map +1 -1
- package/dist/shared/tool-rounds.d.ts +51 -23
- package/dist/shared/tool-rounds.js +67 -24
- package/dist/shared/tool-rounds.js.map +1 -1
- package/dist/shared/workspace/platform-dir.d.ts +15 -0
- package/dist/shared/workspace/platform-dir.js +18 -1
- package/dist/shared/workspace/platform-dir.js.map +1 -1
- package/dist/shared/workspace/sources/git.js +13 -6
- package/dist/shared/workspace/sources/git.js.map +1 -1
- package/dist/shared/workspace/stigmer-link.d.ts +21 -14
- package/dist/shared/workspace/stigmer-link.js +91 -29
- package/dist/shared/workspace/stigmer-link.js.map +1 -1
- package/dist/tools/url-guard.d.ts +5 -1
- package/dist/tools/url-guard.js +9 -2
- package/dist/tools/url-guard.js.map +1 -1
- package/dist/worker.d.ts +11 -2
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/tasks/human-input.d.ts +16 -0
- package/dist/workflow-engine/tasks/human-input.js +79 -37
- package/dist/workflow-engine/tasks/human-input.js.map +1 -1
- package/dist/workflow-engine/types.d.ts +14 -4
- package/dist/workflow-engine/types.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +13 -0
- package/dist/workflows/call-agent-orchestrator.js.map +1 -1
- package/dist/workflows/engine-core.d.ts +25 -0
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/execute-from-execution.d.ts +3 -0
- package/dist/workflows/execute-from-execution.js.map +1 -1
- package/dist/workflows/human-input-orchestrator.d.ts +9 -12
- package/dist/workflows/human-input-orchestrator.js +12 -44
- package/dist/workflows/human-input-orchestrator.js.map +1 -1
- package/package.json +5 -5
- package/src/__test-utils__/config-fixture.ts +1 -0
- package/src/__test-utils__/execution-record-fixture.ts +2 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +5 -3
- package/src/__test-utils__/test-gate.ts +8 -0
- package/src/__tests__/exit-backstop.test.ts +91 -0
- package/src/__tests__/golden-e2e.test.ts +80 -62
- package/src/__tests__/history-encryption-e2e.test.ts +51 -27
- package/src/__tests__/runner.test.ts +60 -2
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.hang.test.ts +5 -1
- package/src/activities/__tests__/discover-mcp-server.test.ts +90 -49
- package/src/activities/__tests__/workflow-event-activities.test.ts +28 -7
- package/src/activities/call-agent.ts +1 -1
- package/src/activities/discover-mcp-server.ts +27 -37
- package/src/activities/execute-cursor/__test-utils__/fold.ts +6 -4
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +1 -0
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +4 -29
- package/src/activities/execute-cursor/__tests__/hermetic/bypassed-tool-call.test.ts +120 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +5 -5
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/file-review-capture.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/resolution-error.status.json +1 -1
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.error-delta.status.json +0 -3
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.output-first.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/shell-output-delta.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/stall.status.json +0 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/unattributed-hook-block.status.json +0 -3
- package/src/activities/execute-cursor/__tests__/hermetic/shell-output-delta.test.ts +9 -7
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +6 -1
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +8 -11
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +1 -3
- package/src/activities/execute-cursor/__tests__/translator.test.ts +68 -15
- package/src/activities/execute-cursor/approval-policy.ts +11 -36
- package/src/activities/execute-cursor/boundary-rows.ts +3 -3
- package/src/activities/execute-cursor/translator.ts +15 -32
- package/src/activities/execute-cursor/turn-stream.ts +4 -1
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +33 -2
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +1 -27
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +26 -30
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +1 -26
- package/src/activities/execute-deep-agent/__tests__/goldens/user-message.everything.md +36 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +4 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +8 -73
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +1 -1
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +17 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +86 -26
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +9 -5
- package/src/activities/execute-deep-agent/__tests__/hermetic/thrown-turn-drains-publishes.test.ts +159 -0
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +84 -0
- package/src/activities/execute-deep-agent/__tests__/platform-route.test.ts +47 -10
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +127 -199
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +31 -26
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +34 -0
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +13 -5
- package/src/activities/execute-deep-agent/inline-publisher.ts +47 -7
- package/src/activities/execute-deep-agent/platform-route.ts +109 -20
- package/src/activities/execute-deep-agent/prompt-builder.ts +104 -92
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +27 -4
- package/src/activities/execute-deep-agent/translator.ts +5 -4
- package/src/activities/execute-deep-agent/turn-settle.ts +6 -6
- package/src/activities/execute-deep-agent/turn-setup.ts +43 -38
- package/src/activities/execute-deep-agent/turn-stream.ts +15 -22
- package/src/activities/execute-deep-agent/turn.ts +16 -7
- package/src/activities/workflow-event-activities.ts +2 -0
- package/src/config.ts +11 -0
- package/src/exit-backstop.ts +97 -0
- package/src/harness/README.md +5 -5
- package/src/harness/terminal-table.ts +13 -13
- package/src/harness/transcript/__tests__/builder.test.ts +93 -21
- package/src/harness/transcript/builder.ts +53 -30
- package/src/harness/transcript/events.ts +22 -27
- package/src/harness/turn-context.ts +5 -3
- package/src/main.ts +9 -5
- package/src/middleware/__tests__/execution-budget.test.ts +181 -103
- package/src/middleware/cost-advisory.ts +6 -3
- package/src/middleware/execution-budget.ts +60 -34
- package/src/middleware/index.ts +10 -7
- package/src/middleware/types.ts +6 -1
- package/src/runner-manager.ts +3 -2
- package/src/runner.ts +57 -15
- package/src/shared/__tests__/approval-policy.test.ts +41 -0
- package/src/shared/__tests__/artifact-storage.test.ts +1 -0
- package/src/shared/__tests__/connect-backfill.test.ts +15 -11
- package/src/shared/__tests__/mcp-resolver.test.ts +72 -10
- package/src/shared/__tests__/model-error.test.ts +71 -3
- package/src/shared/__tests__/platform-server-address.test.ts +122 -0
- package/src/shared/__tests__/prompt-sections.test.ts +2 -54
- package/src/shared/__tests__/tool-rounds.test.ts +64 -14
- package/src/shared/approval-policy.ts +57 -14
- package/src/shared/caller-identity.ts +4 -5
- package/src/shared/channel-attachment.ts +2 -2
- package/src/shared/connect-backfill.ts +5 -1
- package/src/shared/mcp-resolver.ts +11 -1
- package/src/shared/memory-attachment.ts +2 -2
- package/src/shared/model-error.ts +87 -9
- package/src/shared/platform-server-address.ts +123 -0
- package/src/shared/prompt-sections.ts +6 -55
- package/src/shared/skill-resolver.ts +2 -3
- package/src/shared/synthesized-attachment.ts +0 -12
- package/src/shared/tool-rounds.ts +70 -24
- package/src/shared/workspace/__tests__/git-source.test.ts +11 -5
- package/src/shared/workspace/__tests__/stigmer-link.test.ts +107 -10
- package/src/shared/workspace/platform-dir.ts +19 -1
- package/src/shared/workspace/sources/git.ts +14 -8
- package/src/shared/workspace/stigmer-link.ts +99 -29
- package/src/tools/__tests__/url-guard.test.ts +19 -1
- package/src/tools/url-guard.ts +9 -1
- package/src/worker.ts +12 -2
- package/src/workflow-engine/__tests__/golden-execution.test.ts +2 -1
- package/src/workflow-engine/__tests__/loader.test.ts +1 -1
- package/src/workflow-engine/__tests__/tasks/human-input.test.ts +133 -57
- package/src/workflow-engine/tasks/human-input.ts +92 -42
- package/src/workflow-engine/types.ts +16 -4
- package/src/workflows/call-agent-orchestrator.ts +7 -0
- package/src/workflows/engine-core.ts +18 -0
- package/src/workflows/execute-from-execution.ts +2 -1
- package/src/workflows/human-input-orchestrator.ts +15 -56
- package/dist/shared/skill-relevance.d.ts +0 -65
- package/dist/shared/skill-relevance.js +0 -175
- package/dist/shared/skill-relevance.js.map +0 -1
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +0 -146
- package/src/shared/__tests__/skill-relevance.test.ts +0 -292
- package/src/shared/skill-relevance.ts +0 -222
|
@@ -16,8 +16,17 @@
|
|
|
16
16
|
* Until #1096 the same correlation also fed the write-back
|
|
17
17
|
* coordinator's per-file commit (`onFileModified`); write-back is
|
|
18
18
|
* finalize-only now, the turn runtime's, so publishing is this trigger's one
|
|
19
|
-
* effect.
|
|
20
|
-
*
|
|
19
|
+
* effect.
|
|
20
|
+
*
|
|
21
|
+
* One instance per turn, owned by the turn's transcript
|
|
22
|
+
* (`turn-stream.ts` `createDeepAgentTranscript`) and not by the stream loop,
|
|
23
|
+
* so the in-flight publishes outlive a stream that throws: the settle
|
|
24
|
+
* drains them before its safety net, and the turn's `finally`
|
|
25
|
+
* (`turn.ts`) drains them on every exit, a thrown turn included, before the
|
|
26
|
+
* runtime writes the terminal status (stigmer#1116). Until then the loop
|
|
27
|
+
* built this and handed the list out only on its return values, so a turn
|
|
28
|
+
* that threw left its uploads to land on a status already persisted for the
|
|
29
|
+
* last time.
|
|
21
30
|
*/
|
|
22
31
|
|
|
23
32
|
import type { TranscriptEvent } from "../../harness/transcript/events.js";
|
|
@@ -33,12 +42,26 @@ export class StreamingSideEffects {
|
|
|
33
42
|
private readonly inputCache = new Map<string, CachedToolInput>();
|
|
34
43
|
private readonly inlinePublisher: InlinePublisher | undefined;
|
|
35
44
|
|
|
36
|
-
readonly
|
|
45
|
+
private readonly pending: Promise<void>[] = [];
|
|
37
46
|
|
|
38
47
|
constructor(opts: { inlinePublisher?: InlinePublisher }) {
|
|
39
48
|
this.inlinePublisher = opts.inlinePublisher;
|
|
40
49
|
}
|
|
41
50
|
|
|
51
|
+
/** The publishes fired so far, in order; each resolves (a publish logs and swallows its own failure). */
|
|
52
|
+
get pendingPublishPromises(): readonly Promise<void>[] {
|
|
53
|
+
return this.pending;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Wait for every publish fired so far to land on the status. Idempotent:
|
|
58
|
+
* a second call over settled publishes returns at once. Bounded by the
|
|
59
|
+
* artifact storage's own per-call timeouts, as every drain has been.
|
|
60
|
+
*/
|
|
61
|
+
async drainPublishes(): Promise<void> {
|
|
62
|
+
if (this.pending.length > 0) await Promise.allSettled(this.pending);
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
onEvent(event: TranscriptEvent): void {
|
|
43
66
|
if (!this.inlinePublisher) return;
|
|
44
67
|
|
|
@@ -54,7 +77,7 @@ export class StreamingSideEffects {
|
|
|
54
77
|
if (!isFileModifyingTool(cached.toolName)) return;
|
|
55
78
|
const filePath = extractFilePath(cached.input);
|
|
56
79
|
if (!filePath) return;
|
|
57
|
-
this.
|
|
80
|
+
this.pending.push(this.inlinePublisher.publish(filePath));
|
|
58
81
|
}
|
|
59
82
|
}
|
|
60
83
|
}
|
|
@@ -30,10 +30,11 @@
|
|
|
30
30
|
* mode asks the workspace's git state, asynchronously;
|
|
31
31
|
* `middleware/approval-gate.ts`), and duplicating them would be a second copy
|
|
32
32
|
* of the decision — exactly the drift one writer per fact exists to end. So
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* production never
|
|
33
|
+
* the held call reaches the transcript as `approval_proposed` from the
|
|
34
|
+
* post-stream seed, the one writer of a row's approval fields; the Cursor
|
|
35
|
+
* harness answers the same way since #1117. Until #1097 the builder carried a
|
|
36
|
+
* copy of the decision that gated MCP tools only, a path production never
|
|
37
|
+
* took.
|
|
37
38
|
*
|
|
38
39
|
* Attribution and provenance are answered for ROOT-scope calls only: a
|
|
39
40
|
* sub-agent's rows carry none on either harness today (#1133), and one
|
|
@@ -59,12 +59,12 @@ export async function settleDeepAgentTurn(deps: DeepAgentSettleDeps): Promise<Tu
|
|
|
59
59
|
const { executionId } = input;
|
|
60
60
|
const { status } = sink;
|
|
61
61
|
|
|
62
|
-
// Drain the publishes the stream fired (fire-and-forget completions)
|
|
63
|
-
// safety net
|
|
64
|
-
// on
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
// Drain the publishes the stream fired (fire-and-forget completions) BEFORE
|
|
63
|
+
// the safety net: its publisher skips a file only when the file's row is
|
|
64
|
+
// already on the status, so an upload still in flight would be sent twice.
|
|
65
|
+
// The net scans tool calls for files the stream missed and runs only on a
|
|
66
|
+
// finished turn, as it always did.
|
|
67
|
+
await transcript.sideEffects.drainPublishes();
|
|
68
68
|
if (stream.reason === "completed") {
|
|
69
69
|
try {
|
|
70
70
|
await autoPublishWrittenFiles(status, transcript.publisher);
|
|
@@ -67,10 +67,11 @@ import { buildChatModel } from "../../shared/model-client.js";
|
|
|
67
67
|
import { graphThinks, toAnthropicThinking } from "../../shared/thinking-mode.js";
|
|
68
68
|
import { isUnattendedApprovalMode, type MergedToolPolicy } from "../../shared/approval-policy.js";
|
|
69
69
|
import type { ToolApprovalCategory } from "../../shared/tool-kind.js";
|
|
70
|
-
import {
|
|
70
|
+
import { visionPromptInfoOf } from "../../shared/prompt-sections.js";
|
|
71
|
+
import { findApprovedPlanPath } from "../../shared/implement-plan-prompt.js";
|
|
71
72
|
import type { RecalledMemoriesContent } from "../../shared/recalled-memories.js";
|
|
72
73
|
import { toLangChainImageBlocks } from "../../shared/attachment-vision.js";
|
|
73
|
-
import {
|
|
74
|
+
import { backstopRecursionLimit, resolveToolRoundLimit } from "../../shared/tool-rounds.js";
|
|
74
75
|
import { jsonSchemaToZod } from "../../shared/json-schema-to-zod.js";
|
|
75
76
|
import { CasCaptureObserver } from "./cas-capture-observer.js";
|
|
76
77
|
import { createCasCaptureBackend } from "./cas-capture-backend.js";
|
|
@@ -260,25 +261,6 @@ export function readGateState(input: TurnInput, tools: DeepAgentTools): DeepAgen
|
|
|
260
261
|
};
|
|
261
262
|
}
|
|
262
263
|
|
|
263
|
-
/**
|
|
264
|
-
* The `## Skills` section of the root prompt: the skills the shared selection
|
|
265
|
-
* highlights for this turn's message (`shared/prompt-sections.ts`
|
|
266
|
-
* `selectSkillsForPrompt`: every skill below the threshold, the relevant ones
|
|
267
|
-
* at and above it), rendered with this engine's activation protocol, plus —
|
|
268
|
-
* when the selection left any out — an `### Also Available` note naming them
|
|
269
|
-
* (the sentence is the shared, tool-neutral one; the heading is this
|
|
270
|
-
* harness's markdown framing).
|
|
271
|
-
*/
|
|
272
|
-
export function renderRootSkillsSection(input: TurnInput): string {
|
|
273
|
-
const { highlighted, alsoAvailable } = selectSkillsForPrompt(input.execution.spec?.message ?? "", input.skills.root);
|
|
274
|
-
if (alsoAvailable.length === 0) return renderSkillsSection(highlighted);
|
|
275
|
-
console.log(
|
|
276
|
-
`[turn-setup] Skill relevance filter: ${highlighted.length} included, ` +
|
|
277
|
-
`${alsoAvailable.length} excluded: ${alsoAvailable.join(", ")}`,
|
|
278
|
-
);
|
|
279
|
-
return renderSkillsSection(highlighted) + "\n### Also Available\n\n" + alsoAvailableSkillsNote(alsoAvailable) + "\n";
|
|
280
|
-
}
|
|
281
|
-
|
|
282
264
|
/**
|
|
283
265
|
* The system prompt over the runtime's resolved record: every input the
|
|
284
266
|
* builder renders is read from `TurnInput` here, in one place, so the mapping
|
|
@@ -287,6 +269,12 @@ export function renderRootSkillsSection(input: TurnInput): string {
|
|
|
287
269
|
* that is not on the record is the memoized memory selection, which the
|
|
288
270
|
* caller awaits (the runtime stamps the report on the status the first time
|
|
289
271
|
* it is pulled, before this turn's first persist).
|
|
272
|
+
*
|
|
273
|
+
* Nothing here reads the turn's message or payload: the system prompt is the
|
|
274
|
+
* session's (`prompt-builder.ts` says why), so every mounted skill is
|
|
275
|
+
* described and the turn's files ride its message (`composeTurnMessage`).
|
|
276
|
+
* The approved plan's path is the one fact taken from the attachments, and
|
|
277
|
+
* only on a build-from-plan turn, where the mode already changes the prompt.
|
|
290
278
|
*/
|
|
291
279
|
export function composeSystemPrompt(input: TurnInput, recalledMemories: RecalledMemoriesContent | undefined): string {
|
|
292
280
|
const spec = input.execution.spec!;
|
|
@@ -296,19 +284,17 @@ export function composeSystemPrompt(input: TurnInput, recalledMemories: Recalled
|
|
|
296
284
|
instructions: input.blueprint.instructions,
|
|
297
285
|
provisionResults: input.workspace.provision.provisionResults,
|
|
298
286
|
containerRoot: primaryDir,
|
|
299
|
-
skillsPromptSection:
|
|
287
|
+
skillsPromptSection: renderSkillsSection(input.skills.root),
|
|
300
288
|
// "" (nothing sendable) threads as undefined: the tool alone still serves
|
|
301
289
|
// text sends inside a 24-hour window (DD-006 D6).
|
|
302
290
|
channelTemplatesPromptSection: input.mcp.channelMessaging.length > 0
|
|
303
291
|
? formatChannelTemplatesSection(input.mcp.channelMessaging) || undefined
|
|
304
292
|
: undefined,
|
|
305
|
-
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
306
|
-
workspaceRoot: primaryDir,
|
|
307
|
-
inputFiles: input.attachments.results,
|
|
308
|
-
vision: visionPromptInfoOf(input.attachments),
|
|
309
|
-
downloadUrlKind: input.artifactStorage?.downloadUrlKind,
|
|
310
293
|
interactionMode: execConfig?.interactionMode,
|
|
311
294
|
buildFromPlan: execConfig?.buildFromPlan,
|
|
295
|
+
...(execConfig?.buildFromPlan
|
|
296
|
+
? { approvedPlanPath: findApprovedPlanPath(input.attachments.results.map((f) => f.relativePath)) }
|
|
297
|
+
: {}),
|
|
312
298
|
contextBridge: input.standing.contextBridge,
|
|
313
299
|
senderIdentity: input.standing.senderIdentity,
|
|
314
300
|
sessionContext: input.standing.sessionContext,
|
|
@@ -317,6 +303,25 @@ export function composeSystemPrompt(input: TurnInput, recalledMemories: Recalled
|
|
|
317
303
|
});
|
|
318
304
|
}
|
|
319
305
|
|
|
306
|
+
/**
|
|
307
|
+
* The turn's user message over the runtime's resolved record: what the user
|
|
308
|
+
* typed, with this turn's payload (its input files and referenced paths) and
|
|
309
|
+
* the conversation catchup composed around it (`prompt-builder.ts`
|
|
310
|
+
* `composeUserMessage`). The twin of `composeSystemPrompt` for the turn's
|
|
311
|
+
* half, so the goldens pin this mapping and not a copy of it.
|
|
312
|
+
*/
|
|
313
|
+
export function composeTurnMessage(input: TurnInput): string {
|
|
314
|
+
const spec = input.execution.spec!;
|
|
315
|
+
return composeUserMessage({
|
|
316
|
+
message: spec.message,
|
|
317
|
+
conversationCatchup: input.standing.conversationCatchup,
|
|
318
|
+
inputFiles: input.attachments.results,
|
|
319
|
+
vision: visionPromptInfoOf(input.attachments),
|
|
320
|
+
downloadUrlKind: input.artifactStorage?.downloadUrlKind,
|
|
321
|
+
workspaceFileRefs: spec.workspaceFileRefs ?? [],
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
320
325
|
/**
|
|
321
326
|
* Build the engine: the model, the middleware stack with the gate, the
|
|
322
327
|
* tools, the compiled sub-agents, the system prompt and `createDeepAgent`.
|
|
@@ -431,10 +436,10 @@ export async function buildEngine(
|
|
|
431
436
|
: null;
|
|
432
437
|
|
|
433
438
|
const maxCostUsd = execConfig?.maxCostUsd ?? 0;
|
|
434
|
-
const
|
|
439
|
+
const toolRoundLimit = resolveToolRoundLimit(execConfig?.maxToolRounds);
|
|
435
440
|
const { middleware, costAdvisory } = buildMiddlewareStack({
|
|
436
441
|
loopDetection: { historySize: 20, consecutiveThreshold: 7, totalThreshold: 20 },
|
|
437
|
-
executionBudget: {
|
|
442
|
+
executionBudget: { maxToolRounds: toolRoundLimit, warningPct: 80 },
|
|
438
443
|
toolTruncation: { maxChars: execConfig?.maxToolResultChars || 30_000 },
|
|
439
444
|
costAdvisory: maxCostUsd > 0
|
|
440
445
|
? {
|
|
@@ -541,10 +546,10 @@ export async function buildEngine(
|
|
|
541
546
|
|
|
542
547
|
const langgraphConfig: Record<string, unknown> = {
|
|
543
548
|
configurable: { thread_id: input.threadId },
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
...(
|
|
549
|
+
// max_tool_rounds is enforced by the execution budget middleware, round
|
|
550
|
+
// by round; this is only its backstop against a graph that loops without
|
|
551
|
+
// calling the model (`shared/tool-rounds.ts`). Unset = unlimited = none.
|
|
552
|
+
...(toolRoundLimit !== null ? { recursionLimit: backstopRecursionLimit(toolRoundLimit) } : {}),
|
|
548
553
|
};
|
|
549
554
|
|
|
550
555
|
console.log(
|
|
@@ -577,9 +582,10 @@ export async function buildEngine(
|
|
|
577
582
|
* What the graph is invoked with. The checkpoint is read once (on the
|
|
578
583
|
* durable http saver an extra `getState` is a network round-trip): pending
|
|
579
584
|
* interrupts that the runtime's decisions answer make a `Command(resume)`;
|
|
580
|
-
* anything else is the turn's user message —
|
|
581
|
-
* framed before it (
|
|
582
|
-
* checkpointer with the turn
|
|
585
|
+
* anything else is the turn's user message — this turn's payload and the
|
|
586
|
+
* conversation catchup framed before it (`composeTurnMessage`; in the USER
|
|
587
|
+
* MESSAGE so it enters the checkpointer with the turn and the system prompt
|
|
588
|
+
* stays the session's), the structured-output contract appended, and
|
|
583
589
|
* the inline images FIRST as content blocks when the turn carries any (per
|
|
584
590
|
* Anthropic's images-before-text guidance; a plain string otherwise).
|
|
585
591
|
*/
|
|
@@ -590,8 +596,7 @@ export async function composeGraphInput(input: TurnInput, engine: DeepAgentEngin
|
|
|
590
596
|
return { kind: "resume", command: resume.graphInput };
|
|
591
597
|
}
|
|
592
598
|
|
|
593
|
-
|
|
594
|
-
let userMessage = composeUserMessage(spec.message, input.standing.conversationCatchup);
|
|
599
|
+
let userMessage = composeTurnMessage(input);
|
|
595
600
|
if (engine.hasStructuredOutput) {
|
|
596
601
|
userMessage += `\n\n---\nIMPORTANT: When your analysis is complete, provide your findings as structured output matching the required schema. The system will capture your structured response automatically.`;
|
|
597
602
|
}
|
|
@@ -89,8 +89,6 @@ export interface DeepAgentStreamResult {
|
|
|
89
89
|
readonly eventsProcessed: number;
|
|
90
90
|
/** The graph's final state (`structuredResponse` rides it); absent unless the stream completed and the output resolved in time. */
|
|
91
91
|
readonly runOutput: Record<string, unknown> | undefined;
|
|
92
|
-
/** Inline publishes fired mid-turn, drained by the settle. */
|
|
93
|
-
readonly pendingPublishPromises: readonly Promise<void>[];
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
/**
|
|
@@ -98,14 +96,19 @@ export interface DeepAgentStreamResult {
|
|
|
98
96
|
* the engine's events into canonical ones (over the gate's posture, so every
|
|
99
97
|
* tool start carries its attribution and gate answer), and the publisher
|
|
100
98
|
* that registers artifacts on the runtime's builder (`sink.transcript`, so
|
|
101
|
-
* a published artifact still dirties the next persist)
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
99
|
+
* a published artifact still dirties the next persist) and checks the rows
|
|
100
|
+
* already on `sink.status` before uploading, so an earlier turn's unchanged
|
|
101
|
+
* file is not sent again; and the side effects that fire the publisher
|
|
102
|
+
* mid-turn and hold its in-flight uploads until they are drained. The
|
|
103
|
+
* builder itself is the runtime's, one per turn, and is read from the sink
|
|
104
|
+
* where it is needed. Built by the adapter's turn once and shared by the
|
|
105
|
+
* stream, the settle and the turn's `finally`, which drains the publishes
|
|
106
|
+
* on every exit (`streaming-side-effects.ts`).
|
|
105
107
|
*/
|
|
106
108
|
export interface DeepAgentTranscript {
|
|
107
109
|
readonly translator: DeepAgentTranslator;
|
|
108
110
|
readonly publisher: InlinePublisher;
|
|
111
|
+
readonly sideEffects: StreamingSideEffects;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
export function createDeepAgentTranscript(
|
|
@@ -119,9 +122,10 @@ export function createDeepAgentTranscript(
|
|
|
119
122
|
workspaceBackend: workspace.backend,
|
|
120
123
|
artifactStorage: input.artifactStorage,
|
|
121
124
|
artifacts: sink.transcript,
|
|
125
|
+
record: sink.status,
|
|
122
126
|
executionId: input.executionId,
|
|
123
127
|
});
|
|
124
|
-
return { translator, publisher };
|
|
128
|
+
return { translator, publisher, sideEffects: new StreamingSideEffects({ inlinePublisher: publisher }) };
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
/**
|
|
@@ -166,12 +170,11 @@ export interface DeepAgentStreamDeps {
|
|
|
166
170
|
export async function consumeDeepAgentStream(deps: DeepAgentStreamDeps): Promise<DeepAgentStreamResult> {
|
|
167
171
|
const { input, sink, engine, graphInput, transcript } = deps;
|
|
168
172
|
const { executionId } = input;
|
|
169
|
-
const { translator,
|
|
173
|
+
const { translator, sideEffects } = transcript;
|
|
170
174
|
const builder = sink.transcript;
|
|
171
175
|
|
|
172
176
|
const scheduler = new StreamingUpdateScheduler(loadStreamingConfig());
|
|
173
177
|
const recorder = createV3EventRecorder(executionId, process.env.V3_EVENT_RECORD_DIR);
|
|
174
|
-
const sideEffects = new StreamingSideEffects({ inlinePublisher: publisher });
|
|
175
178
|
const abortController = new AbortController();
|
|
176
179
|
let forceAbort: ReturnType<typeof setTimeout> | undefined;
|
|
177
180
|
const abortRun = (): void => {
|
|
@@ -188,12 +191,7 @@ export async function consumeDeepAgentStream(deps: DeepAgentStreamDeps): Promise
|
|
|
188
191
|
/** The stop, taken at a boundary: abort the run now and settle. */
|
|
189
192
|
const interrupted = (): DeepAgentStreamResult => {
|
|
190
193
|
abortRun();
|
|
191
|
-
return {
|
|
192
|
-
reason: "interrupted",
|
|
193
|
-
eventsProcessed,
|
|
194
|
-
runOutput: undefined,
|
|
195
|
-
pendingPublishPromises: sideEffects.pendingPublishPromises,
|
|
196
|
-
};
|
|
194
|
+
return { reason: "interrupted", eventsProcessed, runOutput: undefined };
|
|
197
195
|
};
|
|
198
196
|
|
|
199
197
|
try {
|
|
@@ -254,18 +252,13 @@ export async function consumeDeepAgentStream(deps: DeepAgentStreamDeps): Promise
|
|
|
254
252
|
|
|
255
253
|
if (builder.awaitingApproval) {
|
|
256
254
|
console.log(`[turn-stream] execution=${executionId} stream ended at a gate; awaiting approval`);
|
|
257
|
-
return {
|
|
258
|
-
reason: "awaiting_approval",
|
|
259
|
-
eventsProcessed,
|
|
260
|
-
runOutput: undefined,
|
|
261
|
-
pendingPublishPromises: sideEffects.pendingPublishPromises,
|
|
262
|
-
};
|
|
255
|
+
return { reason: "awaiting_approval", eventsProcessed, runOutput: undefined };
|
|
263
256
|
}
|
|
264
257
|
|
|
265
258
|
console.log(`[turn-stream] execution=${executionId} stream finished — processed ${eventsProcessed} events`);
|
|
266
259
|
const runOutput = await extractRunOutput(run, executionId);
|
|
267
260
|
sink.recordActivity();
|
|
268
|
-
return { reason: "completed", eventsProcessed, runOutput
|
|
261
|
+
return { reason: "completed", eventsProcessed, runOutput };
|
|
269
262
|
} finally {
|
|
270
263
|
sink.stopSignal.removeEventListener("abort", onStop);
|
|
271
264
|
if (forceAbort !== undefined) clearTimeout(forceAbort);
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
* other exception — a provider error unwrapped from LangChain's middleware
|
|
25
25
|
* wrapper, an MCP connect failure, an empty stream — is `failed` on the
|
|
26
26
|
* `internal` surface with `describeExecutionError`'s sentence as
|
|
27
|
-
* `status.error` whole
|
|
27
|
+
* `status.error` whole, and it returns only after the inline publishes the
|
|
28
|
+
* turn already fired have landed on the status (the drain in the `finally`),
|
|
29
|
+
* so an artifact the agent wrote before the throw is on the terminal status
|
|
30
|
+
* the runtime then persists (stigmer#1116). Never branches on why `stopSignal`
|
|
28
31
|
* aborted, never writes a phase or a terminal copy, never imports
|
|
29
32
|
* `@temporalio/*` (`__tests__/adapter-is-temporal-free.test.ts`).
|
|
30
33
|
*
|
|
@@ -44,7 +47,7 @@ import type { TurnInput, TurnOutcome, TurnSink } from "../../harness/types.js";
|
|
|
44
47
|
import type { McpConnectionResult } from "../../shared/mcp-manager.js";
|
|
45
48
|
import { tryInferProvider } from "../../shared/llm-proxy.js";
|
|
46
49
|
import { describeExecutionError } from "../../shared/model-error.js";
|
|
47
|
-
import {
|
|
50
|
+
import { isToolCallBudgetStop } from "../../shared/tool-rounds.js";
|
|
48
51
|
import {
|
|
49
52
|
buildDeepAgentWorkspace,
|
|
50
53
|
buildEngine,
|
|
@@ -55,7 +58,7 @@ import {
|
|
|
55
58
|
resolveModelName,
|
|
56
59
|
type DeepAgentAdapterConfig,
|
|
57
60
|
} from "./turn-setup.js";
|
|
58
|
-
import { consumeDeepAgentStream, createDeepAgentTranscript } from "./turn-stream.js";
|
|
61
|
+
import { consumeDeepAgentStream, createDeepAgentTranscript, type DeepAgentTranscript } from "./turn-stream.js";
|
|
59
62
|
import { settleDeepAgentTurn } from "./turn-settle.js";
|
|
60
63
|
|
|
61
64
|
export async function runDeepAgentTurn(input: TurnInput, sink: TurnSink, config: DeepAgentAdapterConfig): Promise<TurnOutcome> {
|
|
@@ -64,6 +67,7 @@ export async function runDeepAgentTurn(input: TurnInput, sink: TurnSink, config:
|
|
|
64
67
|
let checkpointer: BaseCheckpointSaver | undefined;
|
|
65
68
|
let connection: McpConnectionResult | undefined;
|
|
66
69
|
let modelName: string | undefined;
|
|
70
|
+
let transcript: DeepAgentTranscript | undefined;
|
|
67
71
|
|
|
68
72
|
try {
|
|
69
73
|
modelName = await resolveModelName(input);
|
|
@@ -83,12 +87,16 @@ export async function runDeepAgentTurn(input: TurnInput, sink: TurnSink, config:
|
|
|
83
87
|
const graphInput = await composeGraphInput(input, engine);
|
|
84
88
|
sink.recordActivity();
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
transcript = createDeepAgentTranscript(input, sink, engine, workspace);
|
|
87
91
|
const stream = await consumeDeepAgentStream({ input, sink, engine, graphInput, transcript });
|
|
88
92
|
return await settleDeepAgentTurn({ input, sink, engine, transcript, stream });
|
|
89
93
|
} catch (err) {
|
|
90
94
|
return classifyThrown(err, input, config, modelName);
|
|
91
95
|
} finally {
|
|
96
|
+
// First, on every exit: the runtime persists the terminal status as soon
|
|
97
|
+
// as this returns, so an upload still in flight must land before then.
|
|
98
|
+
// A settled turn already drained in its settle; this is then a no-op.
|
|
99
|
+
await transcript?.sideEffects.drainPublishes();
|
|
92
100
|
await closeQuietly(input.executionId, "MCP connection", () => connection?.client.close());
|
|
93
101
|
// Only the durable sqlite saver holds an OS handle (an open DB file);
|
|
94
102
|
// memory/http savers have no close(), so this is duck-typed.
|
|
@@ -98,8 +106,9 @@ export async function runDeepAgentTurn(input: TurnInput, sink: TurnSink, config:
|
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
/**
|
|
101
|
-
* An exception that escaped the turn, classified. The
|
|
102
|
-
*
|
|
109
|
+
* An exception that escaped the turn, classified. The tool-call budget's stop
|
|
110
|
+
* (the execution budget's round limit, or its recursion-limit backstop;
|
|
111
|
+
* `shared/tool-rounds.ts`) is the budget's own signal, not an error:
|
|
103
112
|
* the work is checkpointed and the conversation continues on the next
|
|
104
113
|
* message. Anything else is described by `describeExecutionError` (a model
|
|
105
114
|
* error arrives MiddlewareError-wrapped with raw provider prose and is
|
|
@@ -108,7 +117,7 @@ export async function runDeepAgentTurn(input: TurnInput, sink: TurnSink, config:
|
|
|
108
117
|
* broke.
|
|
109
118
|
*/
|
|
110
119
|
function classifyThrown(err: unknown, input: TurnInput, config: DeepAgentAdapterConfig, modelName: string | undefined): TurnOutcome {
|
|
111
|
-
if (
|
|
120
|
+
if (isToolCallBudgetStop(err)) {
|
|
112
121
|
console.log(`ExecuteDeepAgent reached the tool-call limit: execution=${input.executionId}`);
|
|
113
122
|
return { kind: "tool_call_limit" };
|
|
114
123
|
}
|
|
@@ -337,6 +337,8 @@ export function toProtoEvent(desc: WorkflowEventDescriptor): WorkflowExecutionEv
|
|
|
337
337
|
: undefined,
|
|
338
338
|
comment: desc.comment,
|
|
339
339
|
waitDurationMs: BigInt(desc.waitDurationMs),
|
|
340
|
+
outcome: desc.outcome,
|
|
341
|
+
autoResolved: desc.autoResolved,
|
|
340
342
|
}),
|
|
341
343
|
};
|
|
342
344
|
break;
|
package/src/config.ts
CHANGED
|
@@ -113,6 +113,15 @@ export interface Config {
|
|
|
113
113
|
* and shared/conversation-attachment.ts.
|
|
114
114
|
*/
|
|
115
115
|
readonly mcpBridgeEndpoint: string | null;
|
|
116
|
+
/**
|
|
117
|
+
* The Stigmer server's public address (STIGMER_MCP_PUBLIC_ENDPOINT), which
|
|
118
|
+
* the server hands every sandbox it provisions or an operator names to a
|
|
119
|
+
* runner started by hand. When set, it fills a missing
|
|
120
|
+
* STIGMER_SERVER_ADDRESS for any MCP server that declares the key; null
|
|
121
|
+
* leaves a remote server's address unfilled. See
|
|
122
|
+
* shared/platform-server-address.ts.
|
|
123
|
+
*/
|
|
124
|
+
readonly mcpPublicEndpoint: string | null;
|
|
116
125
|
/**
|
|
117
126
|
* The Cursor API key for direct mode (authenticates with Cursor directly).
|
|
118
127
|
* In proxy mode this is a placeholder (`"proxy-managed"` unless the operator
|
|
@@ -232,6 +241,7 @@ export function loadConfig(): Config {
|
|
|
232
241
|
const stigmerTokenRef: TokenRef = { current: stigmerTokenValue ?? null };
|
|
233
242
|
|
|
234
243
|
const mcpBridgeEndpoint = process.env.STIGMER_MCP_BRIDGE_ENDPOINT ?? null;
|
|
244
|
+
const mcpPublicEndpoint = process.env.STIGMER_MCP_PUBLIC_ENDPOINT || null;
|
|
235
245
|
|
|
236
246
|
// In proxy mode the SDK's API key is the control-plane credential, which the
|
|
237
247
|
// Cursor adapter reads from the ref per turn (execute-cursor/turn-setup.ts);
|
|
@@ -285,6 +295,7 @@ export function loadConfig(): Config {
|
|
|
285
295
|
stigmerBackendEndpoint,
|
|
286
296
|
stigmerTokenRef,
|
|
287
297
|
mcpBridgeEndpoint,
|
|
298
|
+
mcpPublicEndpoint,
|
|
288
299
|
cursorApiKey,
|
|
289
300
|
workspaceRootDir,
|
|
290
301
|
mode,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exit backstop for static and pool modes: once the runner's own
|
|
3
|
+
* shutdown has finished (worker drained, harnesses released, Temporal
|
|
4
|
+
* connection closed, OTel flushed), the process must end within a named
|
|
5
|
+
* bound, and when it has to be MADE to end, the log names what kept it
|
|
6
|
+
* alive.
|
|
7
|
+
*
|
|
8
|
+
* Static and pool modes end when the Node event loop drains. That is the
|
|
9
|
+
* normal path and it stays the normal path: a healthy runner exits in
|
|
10
|
+
* milliseconds on its own. The failure it guards against is one leaked
|
|
11
|
+
* handle — a timer, a socket, a child — that outlives the worker and holds
|
|
12
|
+
* the process until the orchestrator's grace expires. One uncleared timer
|
|
13
|
+
* in our own code did exactly that to every pool-member roll for three
|
|
14
|
+
* months (issue #1008), and the next one may sit in a vendor SDK we do not
|
|
15
|
+
* own. A hosted sandbox pod's grace is ten minutes, so the cost of a leak
|
|
16
|
+
* is not bounded by anything the runner controls — until this module.
|
|
17
|
+
*
|
|
18
|
+
* Why these choices:
|
|
19
|
+
*
|
|
20
|
+
* - The timer is `unref()`'d, so arming it never keeps the process alive.
|
|
21
|
+
* A drained loop exits naturally and the timer dies with it; the
|
|
22
|
+
* backstop only ever fires when something else is still holding the
|
|
23
|
+
* loop. An unref'd timer is also absent from
|
|
24
|
+
* `process.getActiveResourcesInfo()`, so the line never names the
|
|
25
|
+
* backstop itself.
|
|
26
|
+
* - The bound sits above the conformance harness's slow-exit threshold
|
|
27
|
+
* (2 s, `test/conformance/src/harness/runner-process.ts`), so a leak is
|
|
28
|
+
* still reported there as a slow exit instead of being hidden under it,
|
|
29
|
+
* and far below the harness's 30 s and the hosted 10-minute graces.
|
|
30
|
+
* - A forced exit exits 0. By the time the backstop is armed the drain and
|
|
31
|
+
* the flush have completed; the lingering handle is a defect named in the
|
|
32
|
+
* log, not a failed shutdown, and a non-zero code would make Kubernetes
|
|
33
|
+
* and the desktop host report a crash for a clean stop.
|
|
34
|
+
* - It is armed only after everything the shutdown awaits. Work that
|
|
35
|
+
* nobody awaits (a fire-and-forget promise) is the only thing it can cut;
|
|
36
|
+
* if such work matters at exit, the fix is to await it, and this line
|
|
37
|
+
* names it the first time it happens.
|
|
38
|
+
*
|
|
39
|
+
* Manager mode does not use this: it is driven by its host over stdin and
|
|
40
|
+
* exits at once after its shutdown (issue #177, `main.ts`).
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** How long the process may outlive its own completed shutdown before it is made to exit. */
|
|
44
|
+
export const EXIT_BACKSTOP_MS = 5_000;
|
|
45
|
+
|
|
46
|
+
export interface ExitBackstopOptions {
|
|
47
|
+
/** The bound; defaults to {@link EXIT_BACKSTOP_MS}. */
|
|
48
|
+
readonly boundMs?: number;
|
|
49
|
+
/** Ends the process; defaults to `process.exit`. */
|
|
50
|
+
readonly exit?: (code: number) => void;
|
|
51
|
+
/** Names what still holds the loop; defaults to `process.getActiveResourcesInfo`. */
|
|
52
|
+
readonly activeResources?: () => string[];
|
|
53
|
+
/** Writes the forced-exit line; defaults to `console.warn`. */
|
|
54
|
+
readonly log?: (line: string) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Arm the backstop. Call it once, after the mode's shutdown has returned.
|
|
59
|
+
* Returns the armed timer so a test can observe that it does not hold the
|
|
60
|
+
* loop; production callers ignore it.
|
|
61
|
+
*/
|
|
62
|
+
export function armExitBackstop(
|
|
63
|
+
options: ExitBackstopOptions = {},
|
|
64
|
+
): ReturnType<typeof setTimeout> {
|
|
65
|
+
const boundMs = options.boundMs ?? EXIT_BACKSTOP_MS;
|
|
66
|
+
const exit = options.exit ?? ((code: number) => process.exit(code));
|
|
67
|
+
const activeResources = options.activeResources ?? (() => process.getActiveResourcesInfo());
|
|
68
|
+
const log = options.log ?? ((line: string) => console.warn(line));
|
|
69
|
+
|
|
70
|
+
const timer = setTimeout(() => {
|
|
71
|
+
log(
|
|
72
|
+
`[runner] Process still alive ${boundMs}ms after shutdown; forcing exit. ` +
|
|
73
|
+
`Active resources: ${describeResources(activeResources())}`,
|
|
74
|
+
);
|
|
75
|
+
exit(0);
|
|
76
|
+
}, boundMs);
|
|
77
|
+
timer.unref();
|
|
78
|
+
return timer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* `["Timeout", "TCPSocketWrap", "TCPSocketWrap"]` → `"TCPSocketWrap×2, Timeout×1"`:
|
|
83
|
+
* grouped and sorted, so the same leak reads the same on every exit.
|
|
84
|
+
*/
|
|
85
|
+
export function describeResources(resources: readonly string[]): string {
|
|
86
|
+
if (resources.length === 0) {
|
|
87
|
+
return "none reported";
|
|
88
|
+
}
|
|
89
|
+
const counts = new Map<string, number>();
|
|
90
|
+
for (const resource of resources) {
|
|
91
|
+
counts.set(resource, (counts.get(resource) ?? 0) + 1);
|
|
92
|
+
}
|
|
93
|
+
return [...counts.entries()]
|
|
94
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
95
|
+
.map(([resource, count]) => `${resource}×${count}`)
|
|
96
|
+
.join(", ");
|
|
97
|
+
}
|
package/src/harness/README.md
CHANGED
|
@@ -200,11 +200,10 @@ Every hit falls into one of three classes:
|
|
|
200
200
|
| `apis/ai/stigmer/agentic/agentexecution/v1/enum.proto` | A comment on tool-name casing per harness. |
|
|
201
201
|
| `backend/README.md` | Lists the harnesses and the SDK behind each. |
|
|
202
202
|
| `client-apps/desktop/scripts/stage-runner-slim.sh`, `client-apps/desktop/scripts/macos-codesign-tree.sh` | Stage and sign the vendor SDKs' native helpers for the desktop app; a harness whose SDK ships one touches both. |
|
|
203
|
-
| `sdk/react/src/models/harness.ts` | The SDK hub: `HarnessOption`, `HARNESS_META`, `HARNESS_LABELS`, `HARNESS_OPTIONS`, `DEFAULT_HARNESS`, `toProtoHarness`, `fromProtoHarness
|
|
204
|
-
| `sdk/react/src/models/HarnessSelector.tsx` |
|
|
205
|
-
| `sdk/react/src/models/ModelSelector.tsx` | A Cursor-only thinking rule
|
|
203
|
+
| `sdk/react/src/models/harness.ts` | The SDK hub: `HarnessOption`, `HARNESS_META`, `HARNESS_LABELS`, `HARNESS_OPTIONS`, `DEFAULT_HARNESS`, `isHarnessOption`, `toProtoHarness`, `fromProtoHarness`, all read from one option-to-enum table; its reverse is typed over the whole enum, so a new member fails the SDK's typecheck here. |
|
|
204
|
+
| `sdk/react/src/models/HarnessSelector.tsx` | The premium mark on the Cursor segment; the segments themselves come from `HARNESS_OPTIONS`. |
|
|
205
|
+
| `sdk/react/src/models/ModelSelector.tsx` | A Cursor-only thinking rule. |
|
|
206
206
|
| `sdk/react/src/identity-account/AccountPreferencesPanel.tsx` | One `HarnessOptionRow` per harness, placed by hand. |
|
|
207
|
-
| `sdk/react/src/identity-account/useAccountExecutionDefaults.ts` | Validates the preference against the two names. |
|
|
208
207
|
| `sdk/react/src/workflow/inspector/forms/AgentCallForm.tsx` | Two radio buttons, placed by hand. |
|
|
209
208
|
| `sdk/react/src/pricing-governance/BaselineEditor.tsx` | A select with one option per harness. |
|
|
210
209
|
| `site/src/components/pages/pricing/ModelPricingTable.tsx` | Harness to display label. |
|
|
@@ -234,7 +233,8 @@ Nothing to touch. Listed so a probe's hit here is a stop, not a lead.
|
|
|
234
233
|
(read the session's harness or name the activities in prose).
|
|
235
234
|
- The React SDK: `sdk/react/src/models/registry.ts`,
|
|
236
235
|
`sdk/react/src/models/useModelRegistry.ts`,
|
|
237
|
-
`sdk/react/src/models/service-tier.ts
|
|
236
|
+
`sdk/react/src/models/service-tier.ts`,
|
|
237
|
+
`sdk/react/src/identity-account/useAccountExecutionDefaults.ts`; everything under
|
|
238
238
|
`sdk/react/src/channel/`, `sdk/react/src/composer/`,
|
|
239
239
|
`sdk/react/src/execution/`, `sdk/react/src/schedule/`,
|
|
240
240
|
`sdk/react/src/session/` (typed by the option or defaulting to one value).
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* harness once kept one function per arm (`streaming-terminal.ts`, retired
|
|
12
12
|
* in #1096); since #1096 both harnesses' turns end through this one table.
|
|
13
13
|
*
|
|
14
|
+
* The `error` of a FAILED arm is the failure itself, never framed: the
|
|
15
|
+
* `Execution failed:` wording belongs to the rows and to each consumer's
|
|
16
|
+
* own copy (the console's badge, the CLI's epilogue), so a consumer meets
|
|
17
|
+
* one grammar whichever arm wrote it. The interruption arms' `Execution
|
|
18
|
+
* interrupted:` errors are a different outcome, one the user resumes.
|
|
19
|
+
*
|
|
14
20
|
* The throw-vs-return rule, unchanged from the orchestrator it replaces:
|
|
15
21
|
* - RETURN when a Temporal retry would only repeat the outcome — a stall
|
|
16
22
|
* would wedge again, an exhausted budget would burn again, a platform
|
|
@@ -293,21 +299,15 @@ export function failedArm(message: string, surface: FailureSurface): TerminalArm
|
|
|
293
299
|
|
|
294
300
|
/**
|
|
295
301
|
* An error the runtime caught itself, outside any adapter (a resolution
|
|
296
|
-
* phase, the epilogue):
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
302
|
+
* phase, the epilogue): an `internal` failure like any adapter's, described
|
|
303
|
+
* as `[<type>] <message>` (`describeExecutionError` supplies the two parts),
|
|
304
|
+
* so `status.error` reads the same whichever side caught the fault. Until
|
|
305
|
+
* stigmer#1122 this arm alone prefixed `status.error` with `Execution
|
|
306
|
+
* failed:`, and a consumer that adds its own failure copy (the CLI's
|
|
307
|
+
* epilogue) printed the words twice.
|
|
301
308
|
*/
|
|
302
309
|
export function unexpectedErrorArm(errorType: string, errorMessage: string): TerminalArm {
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
phase: ExecutionPhase.EXECUTION_FAILED,
|
|
306
|
-
error: `Execution failed: ${described}`,
|
|
307
|
-
rows: [TERMINAL_COPY.internalFailure.row, `Error details: ${described}`],
|
|
308
|
-
completes: true,
|
|
309
|
-
disposition: RETURN,
|
|
310
|
-
};
|
|
310
|
+
return failedArm(`[${errorType}] ${errorMessage}`, "internal");
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
// ── Applying an arm ─────────────────────────────────────────────────────────
|