@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
|
@@ -45,7 +45,7 @@ import type { WorkspaceBackend } from "../../../shared/workspace/types.js";
|
|
|
45
45
|
import type { ModelPricing } from "../../../shared/model-pricing.js";
|
|
46
46
|
import { CasCaptureObserver } from "../cas-capture-observer.js";
|
|
47
47
|
import type { DeepAgentEngine, DeepAgentGateState, DeepAgentGraphInput, DeepAgentWorkspace } from "../turn-setup.js";
|
|
48
|
-
import { consumeDeepAgentStream, createDeepAgentTranscript, type StreamableRun } from "../turn-stream.js";
|
|
48
|
+
import { consumeDeepAgentStream, createDeepAgentTranscript, type DeepAgentTranscript, type StreamableRun } from "../turn-stream.js";
|
|
49
49
|
import type { V3ProtocolEvent } from "../v3-event-recorder.js";
|
|
50
50
|
import {
|
|
51
51
|
makeMessageFinish,
|
|
@@ -129,6 +129,8 @@ interface Harness {
|
|
|
129
129
|
readonly engine: DeepAgentEngine;
|
|
130
130
|
readonly streamEvents: ReturnType<typeof vi.fn>;
|
|
131
131
|
readonly blobs: Map<string, Buffer>;
|
|
132
|
+
/** The transcript the last `run` streamed into; its side effects hold the turn's publishes. */
|
|
133
|
+
readonly transcript: () => DeepAgentTranscript;
|
|
132
134
|
run(run: StreamableRun): ReturnType<typeof consumeDeepAgentStream>;
|
|
133
135
|
}
|
|
134
136
|
|
|
@@ -150,14 +152,19 @@ function harness(options: { gate?: DeepAgentGateState; files?: Record<string, st
|
|
|
150
152
|
};
|
|
151
153
|
const workspace = workspaceOver(options.files ?? {});
|
|
152
154
|
const graphInput: DeepAgentGraphInput = { kind: "message", input: { messages: [{ role: "user", content: "Say hello." }] } };
|
|
155
|
+
let transcript: DeepAgentTranscript | undefined;
|
|
153
156
|
return {
|
|
154
157
|
sink,
|
|
155
158
|
engine,
|
|
156
159
|
streamEvents,
|
|
157
160
|
blobs,
|
|
161
|
+
transcript: () => {
|
|
162
|
+
if (transcript === undefined) throw new Error("harness: no run yet");
|
|
163
|
+
return transcript;
|
|
164
|
+
},
|
|
158
165
|
run(run) {
|
|
159
166
|
streamEvents.mockResolvedValue(run);
|
|
160
|
-
|
|
167
|
+
transcript = createDeepAgentTranscript(input, sink, engine, workspace);
|
|
161
168
|
return consumeDeepAgentStream({ input, sink, engine, graphInput, transcript });
|
|
162
169
|
},
|
|
163
170
|
};
|
|
@@ -245,7 +252,7 @@ describe("consumeDeepAgentStream — what each event becomes", () => {
|
|
|
245
252
|
|
|
246
253
|
it("publishes a file-modifying tool's target as an artifact on the status when the call finishes", async () => {
|
|
247
254
|
const h = harness({ files: { "src/main.ts": "console.log('hi');" } });
|
|
248
|
-
|
|
255
|
+
await h.run(
|
|
249
256
|
scriptedRun([
|
|
250
257
|
...textTurn("run-1", "Writing."),
|
|
251
258
|
makeToolStarted("toolu_w", "write_file", { file_path: "src/main.ts", content: "console.log('hi');" }),
|
|
@@ -253,8 +260,9 @@ describe("consumeDeepAgentStream — what each event becomes", () => {
|
|
|
253
260
|
]),
|
|
254
261
|
);
|
|
255
262
|
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
const { sideEffects } = h.transcript();
|
|
264
|
+
expect(sideEffects.pendingPublishPromises, "one publish fired mid-turn, held by the transcript for the drain").toHaveLength(1);
|
|
265
|
+
await sideEffects.drainPublishes();
|
|
258
266
|
expect(h.sink.status.artifacts.map((a) => a.sandboxPath)).toEqual(["src/main.ts"]);
|
|
259
267
|
expect([...h.blobs.keys()], "the bytes were uploaded under the execution's artifact key").toEqual(["artifacts/aex_fixture_0001/main.ts"]);
|
|
260
268
|
});
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* Designed as a fire-and-forget callback: exceptions are logged and
|
|
9
9
|
* swallowed so the streaming loop is never interrupted.
|
|
10
10
|
*
|
|
11
|
+
* A file is uploaded only when its bytes differ from the row the execution's
|
|
12
|
+
* status already lists for its path. The status is the one home of that fact:
|
|
13
|
+
* the builder writes every row into it, and a reinvocation's seed carries the
|
|
14
|
+
* earlier turns' rows onto it before this publisher exists. So a completed
|
|
15
|
+
* turn after a gate, whose safety net walks the seeded history again, re-reads
|
|
16
|
+
* each earlier file but uploads none that did not change (stigmer/stigmer#1448).
|
|
17
|
+
* The re-read is kept on purpose: a shell command may have rewritten an earlier
|
|
18
|
+
* turn's file, and only its bytes can say so.
|
|
19
|
+
*
|
|
11
20
|
* DD-7: No skill-aware directory publishing. Individual files only.
|
|
12
21
|
*/
|
|
13
22
|
|
|
@@ -35,6 +44,16 @@ export interface ArtifactSink {
|
|
|
35
44
|
addArtifact(artifact: ExecutionArtifact): void;
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The execution status's artifact rows, read at every publish: the status the
|
|
49
|
+
* {@link ArtifactSink} builds into (`TurnSink.status` in production), so a row
|
|
50
|
+
* registered here or seeded by a reinvocation is visible on the next check.
|
|
51
|
+
* Read, never copied, because the builder keeps writing it.
|
|
52
|
+
*/
|
|
53
|
+
export interface ArtifactRecord {
|
|
54
|
+
readonly artifacts: readonly ExecutionArtifact[];
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
export class InlinePublisher {
|
|
39
58
|
private readonly workspaceBackend: WorkspaceBackend;
|
|
40
59
|
/**
|
|
@@ -45,33 +64,46 @@ export class InlinePublisher {
|
|
|
45
64
|
*/
|
|
46
65
|
private readonly artifactStorage: ArtifactStorage | undefined;
|
|
47
66
|
private readonly artifacts: ArtifactSink;
|
|
67
|
+
private readonly record: ArtifactRecord;
|
|
48
68
|
private readonly executionId: string;
|
|
49
69
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Paths this publisher brought up to date in this turn, uploaded or found
|
|
72
|
+
* unchanged, so the post-stream safety net does not read them again. Per
|
|
73
|
+
* turn by design: an earlier turn's path is absent until this turn checks
|
|
74
|
+
* it, so a file rewritten outside a write tool is still caught.
|
|
75
|
+
*/
|
|
76
|
+
private readonly published = new Set<string>();
|
|
52
77
|
|
|
53
78
|
constructor(opts: {
|
|
54
79
|
workspaceBackend: WorkspaceBackend;
|
|
55
80
|
artifactStorage: ArtifactStorage | undefined;
|
|
56
81
|
artifacts: ArtifactSink;
|
|
82
|
+
record: ArtifactRecord;
|
|
57
83
|
executionId: string;
|
|
58
84
|
}) {
|
|
59
85
|
this.workspaceBackend = opts.workspaceBackend;
|
|
60
86
|
this.artifactStorage = opts.artifactStorage;
|
|
61
87
|
this.artifacts = opts.artifacts;
|
|
88
|
+
this.record = opts.record;
|
|
62
89
|
this.executionId = opts.executionId;
|
|
63
90
|
}
|
|
64
91
|
|
|
65
|
-
/** Set of sandbox paths
|
|
92
|
+
/** Set of sandbox paths brought up to date this turn (for auto-publish dedup). */
|
|
66
93
|
get publishedPaths(): ReadonlySet<string> {
|
|
67
|
-
return new Set(this.published
|
|
94
|
+
return new Set(this.published);
|
|
68
95
|
}
|
|
69
96
|
|
|
70
97
|
/**
|
|
71
98
|
* Upload the file at `path` to artifact storage and register it on the
|
|
72
99
|
* status builder. Fire-and-forget: errors are logged and swallowed.
|
|
100
|
+
*
|
|
101
|
+
* The artifact's `createdAt` is read here, before the first await: the
|
|
102
|
+
* stream asks for the publish in the step that folds the write's finish, so
|
|
103
|
+
* the stamp is the write's, never the moment a slow upload returned.
|
|
73
104
|
*/
|
|
74
105
|
async publish(path: string): Promise<void> {
|
|
106
|
+
const createdAt = utcTimestamp();
|
|
75
107
|
// No artifact store (proxy misconfig): nothing to upload to. Skip silently —
|
|
76
108
|
// this is a best-effort UI publisher and the operator was warned at setup.
|
|
77
109
|
if (!this.artifactStorage) return;
|
|
@@ -97,7 +129,8 @@ export class InlinePublisher {
|
|
|
97
129
|
const contentBuffer = Buffer.from(content, "utf-8");
|
|
98
130
|
const contentHash = sha256(contentBuffer);
|
|
99
131
|
|
|
100
|
-
if (this.
|
|
132
|
+
if (this.isRecorded(sandboxPath, contentHash)) {
|
|
133
|
+
this.published.add(sandboxPath);
|
|
101
134
|
return;
|
|
102
135
|
}
|
|
103
136
|
|
|
@@ -112,12 +145,12 @@ export class InlinePublisher {
|
|
|
112
145
|
kind: ExecutionArtifactKind.FILE,
|
|
113
146
|
sizeBytes: BigInt(contentBuffer.length),
|
|
114
147
|
storageKey,
|
|
115
|
-
createdAt
|
|
148
|
+
createdAt,
|
|
116
149
|
contentHash,
|
|
117
150
|
});
|
|
118
151
|
|
|
119
152
|
this.artifacts.addArtifact(artifact);
|
|
120
|
-
this.published.
|
|
153
|
+
this.published.add(sandboxPath);
|
|
121
154
|
|
|
122
155
|
console.log(
|
|
123
156
|
`[InlinePublisher] execution=${this.executionId} — published '${sandboxPath}' ` +
|
|
@@ -130,6 +163,13 @@ export class InlinePublisher {
|
|
|
130
163
|
);
|
|
131
164
|
}
|
|
132
165
|
}
|
|
166
|
+
|
|
167
|
+
/** Whether the status already lists these exact bytes at this path. */
|
|
168
|
+
private isRecorded(sandboxPath: string, contentHash: string): boolean {
|
|
169
|
+
return this.record.artifacts.some(
|
|
170
|
+
(a) => a.sandboxPath === sandboxPath && a.contentHash === contentHash,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
133
173
|
}
|
|
134
174
|
|
|
135
175
|
function normalizePath(path: string): string {
|
|
@@ -30,9 +30,18 @@
|
|
|
30
30
|
*
|
|
31
31
|
* WHEN IT IS MOUNTED
|
|
32
32
|
* ------------------
|
|
33
|
-
* On
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* On every turn: `.stigmer/` is the platform's name whether or not the turn
|
|
34
|
+
* mounted anything, so a write there never reaches the workspace. Before
|
|
35
|
+
* #1123 the route existed only on linked turns, and a write under `.stigmer/`
|
|
36
|
+
* on any other turn made a real directory in the user's tree that the next
|
|
37
|
+
* link displaced. What the route exposes follows the link, so the file tools
|
|
38
|
+
* reach exactly what the shell reaches on the same turn:
|
|
39
|
+
* - on a turn whose skill or attachment phase made the link, the platform dir
|
|
40
|
+
* ({@link PlatformContentBackend});
|
|
41
|
+
* - on any other turn, nothing ({@link NoPlatformContentBackend}): the dir
|
|
42
|
+
* still holds earlier turns' skills and inputs, which this turn's shell
|
|
43
|
+
* cannot reach, so the file tools must not either. The root listing then
|
|
44
|
+
* leaves `.stigmer/` out, as the tree does.
|
|
36
45
|
* The link itself stays: shell commands (a skill's own scripts among them)
|
|
37
46
|
* run in the workspace and resolve `.stigmer/…` through it.
|
|
38
47
|
*/
|
|
@@ -40,10 +49,16 @@
|
|
|
40
49
|
import { CompositeBackend, FilesystemBackend } from "deepagents";
|
|
41
50
|
import type {
|
|
42
51
|
AnyBackendProtocol,
|
|
52
|
+
BackendProtocolV2,
|
|
43
53
|
DeleteResult,
|
|
44
54
|
EditResult,
|
|
55
|
+
FileDownloadResponse,
|
|
45
56
|
FileUploadResponse,
|
|
57
|
+
GlobResult,
|
|
58
|
+
GrepResult,
|
|
46
59
|
LsResult,
|
|
60
|
+
ReadRawResult,
|
|
61
|
+
ReadResult,
|
|
47
62
|
WriteResult,
|
|
48
63
|
} from "deepagents";
|
|
49
64
|
import { STIGMER_LOCAL_STATE_DIR, stigmerSymlinkPointsAt } from "../../shared/workspace/stigmer-link.js";
|
|
@@ -51,14 +66,25 @@ import { STIGMER_LOCAL_STATE_DIR, stigmerSymlinkPointsAt } from "../../shared/wo
|
|
|
51
66
|
/** The route prefix, with the trailing slash `CompositeBackend` needs to match `/.stigmer/x` but never `/.stigmerx`. */
|
|
52
67
|
export const PLATFORM_ROUTE_PREFIX = `/${STIGMER_LOCAL_STATE_DIR}/`;
|
|
53
68
|
|
|
69
|
+
/** The path as the agent named it: the route hands its backend the path with the prefix stripped. */
|
|
70
|
+
function agentPath(routedPath: string): string {
|
|
71
|
+
return `${PLATFORM_ROUTE_PREFIX.slice(0, -1)}${routedPath}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
function readOnly(routedPath: string): string {
|
|
55
|
-
const agentPath = `${PLATFORM_ROUTE_PREFIX.slice(0, -1)}${routedPath}`;
|
|
56
75
|
return (
|
|
57
|
-
`Error: '${agentPath}' is platform content (skills, attached inputs, the approved plan) ` +
|
|
76
|
+
`Error: '${agentPath(routedPath)}' is platform content (skills, attached inputs, the approved plan) ` +
|
|
58
77
|
"and is read-only; write your own files in the workspace"
|
|
59
78
|
);
|
|
60
79
|
}
|
|
61
80
|
|
|
81
|
+
function noPlatformContent(routedPath: string): string {
|
|
82
|
+
return (
|
|
83
|
+
`Error: '${agentPath(routedPath)}' does not exist: this turn has no platform content ` +
|
|
84
|
+
"(no skills or attached inputs)"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
/** The platform dir, virtual-rooted like every native backend, refusing every mutation. */
|
|
63
89
|
export class PlatformContentBackend extends FilesystemBackend {
|
|
64
90
|
constructor(platformDir: string) {
|
|
@@ -83,17 +109,80 @@ export class PlatformContentBackend extends FilesystemBackend {
|
|
|
83
109
|
}
|
|
84
110
|
|
|
85
111
|
/**
|
|
86
|
-
* The
|
|
112
|
+
* The `.stigmer/` route on a turn that exposes no platform content: nothing
|
|
113
|
+
* to read, list or find, and every mutation refused with the same message
|
|
114
|
+
* the platform dir's route gives. It stands on no filesystem, so it cannot
|
|
115
|
+
* create or reveal anything, and `ls` answers "does not exist" rather than
|
|
116
|
+
* an empty listing because the root listing leaves `.stigmer/` out.
|
|
117
|
+
*/
|
|
118
|
+
export class NoPlatformContentBackend implements BackendProtocolV2 {
|
|
119
|
+
ls(path: string): LsResult {
|
|
120
|
+
return { error: noPlatformContent(path) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
read(filePath: string): ReadResult {
|
|
124
|
+
return { error: noPlatformContent(filePath) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
readRaw(filePath: string): ReadRawResult {
|
|
128
|
+
return { error: noPlatformContent(filePath) };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
grep(): GrepResult {
|
|
132
|
+
return { matches: [] };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
glob(): GlobResult {
|
|
136
|
+
return { files: [] };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
write(filePath: string): WriteResult {
|
|
140
|
+
return { error: readOnly(filePath) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
edit(filePath: string): EditResult {
|
|
144
|
+
return { error: readOnly(filePath) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
delete(filePath: string): DeleteResult {
|
|
148
|
+
return { error: readOnly(filePath) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
uploadFiles(files: Array<[string, Uint8Array]>): FileUploadResponse[] {
|
|
152
|
+
return files.map(([path]) => ({ path, error: "permission_denied" }));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
downloadFiles(paths: string[]): FileDownloadResponse[] {
|
|
156
|
+
return paths.map((path) => ({ path, content: null, error: "file_not_found" }));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The workspace backend with `.stigmer/` routed to {@link PLATFORM_ROUTE_PREFIX}'s
|
|
162
|
+
* backend for this turn.
|
|
87
163
|
*
|
|
88
164
|
* One behaviour differs from the stock router: listing `/`. The composite
|
|
89
|
-
* adds each route to the root listing
|
|
90
|
-
*
|
|
91
|
-
*
|
|
165
|
+
* adds each route to the root listing whatever the route holds. With the
|
|
166
|
+
* platform exposed, the workspace backend also lists `.stigmer/` by following
|
|
167
|
+
* the link, so the stock listing names the same directory twice and is
|
|
168
|
+
* de-duplicated by path. With nothing exposed, `.stigmer/` is left out of the
|
|
169
|
+
* root listing altogether: the route has nothing to show, and a real
|
|
170
|
+
* `.stigmer` the workspace may hold is unreachable behind it.
|
|
92
171
|
*/
|
|
93
172
|
export class PlatformRoutedBackend extends CompositeBackend {
|
|
173
|
+
private readonly exposesPlatform: boolean;
|
|
174
|
+
|
|
175
|
+
constructor(backend: AnyBackendProtocol, platform: PlatformContentBackend | NoPlatformContentBackend) {
|
|
176
|
+
super(backend, { [PLATFORM_ROUTE_PREFIX]: platform });
|
|
177
|
+
this.exposesPlatform = platform instanceof PlatformContentBackend;
|
|
178
|
+
}
|
|
179
|
+
|
|
94
180
|
override async ls(path: string): Promise<LsResult> {
|
|
95
181
|
const result = await super.ls(path);
|
|
96
182
|
if (path !== "/" || !result.files) return result;
|
|
183
|
+
if (!this.exposesPlatform) {
|
|
184
|
+
return { ...result, files: result.files.filter((entry) => entry.path !== PLATFORM_ROUTE_PREFIX) };
|
|
185
|
+
}
|
|
97
186
|
const seen = new Set<string>();
|
|
98
187
|
return {
|
|
99
188
|
...result,
|
|
@@ -114,21 +203,21 @@ export interface PlatformRouteOptions {
|
|
|
114
203
|
}
|
|
115
204
|
|
|
116
205
|
/**
|
|
117
|
-
* Mount the
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* a
|
|
206
|
+
* Mount the `.stigmer/` route over `backend` for this turn: the platform dir
|
|
207
|
+
* when the workspace link points at it, the empty route otherwise. Only a
|
|
208
|
+
* session without a platform dir gets `backend` back unchanged. `execute`
|
|
209
|
+
* and the sandbox identity stay the workspace backend's (`CompositeBackend`
|
|
210
|
+
* delegates both to its default), so a shell-capable graph keeps its
|
|
211
|
+
* `execute` tool and a plan-mode graph stays without one.
|
|
122
212
|
*/
|
|
123
213
|
export async function mountPlatformRoute(
|
|
124
214
|
backend: AnyBackendProtocol,
|
|
125
215
|
options: PlatformRouteOptions,
|
|
126
216
|
): Promise<AnyBackendProtocol> {
|
|
127
217
|
const { workspaceDir, platformDir } = options;
|
|
128
|
-
if (platformDir === undefined
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
218
|
+
if (platformDir === undefined) return backend;
|
|
219
|
+
const platform = (await stigmerSymlinkPointsAt(workspaceDir, platformDir))
|
|
220
|
+
? new PlatformContentBackend(platformDir)
|
|
221
|
+
: new NoPlatformContentBackend();
|
|
222
|
+
return new PlatformRoutedBackend(backend, platform);
|
|
134
223
|
}
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The native harness's system prompt
|
|
3
|
-
* `systemPrompt`,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The native harness's prompt: the system prompt `createDeepAgent` receives
|
|
3
|
+
* as `systemPrompt`, and the turn's user message.
|
|
4
|
+
*
|
|
5
|
+
* The system prompt is rebuilt on EVERY invocation (never checkpointed with
|
|
6
|
+
* the message history), so every standing fact is injected on every turn by
|
|
7
|
+
* design — a first-turn-only injection would vanish from turn 2 onward. It is
|
|
8
|
+
* a function of the session and of the execution's settings (its mode), never
|
|
9
|
+
* of the turn. The provider caches tools, system and messages as one prefix,
|
|
10
|
+
* so a system byte that follows the message re-writes the cached prompt and
|
|
11
|
+
* the whole conversation after it, on every turn, at the cache-write price.
|
|
12
|
+
* What belongs to one turn — its input files, the workspace paths it
|
|
13
|
+
* references, the conversation catchup — rides that turn's user message
|
|
14
|
+
* (`composeUserMessage`), which enters the checkpointer with the turn and
|
|
15
|
+
* stays in history. That is the Cursor harness's resumed-turn placement
|
|
16
|
+
* (`execute-cursor/prompt-builder.ts` `buildPrompt`) and the runner's rule
|
|
17
|
+
* that user content goes in user messages, never the system prompt.
|
|
6
18
|
*
|
|
7
19
|
* Pure functions: no side effects, no I/O. `turn-setup.ts` gathers the
|
|
8
20
|
* inputs from the runtime's resolved record and this module renders them.
|
|
9
21
|
*
|
|
10
22
|
* What is this harness's and what is shared (since #1096):
|
|
11
23
|
* - Shared through `shared/prompt-sections.ts`: WHICH standing sections
|
|
12
|
-
* render and in WHAT order (`standingContextSections`)
|
|
13
|
-
* bullet and disclosure lines (`inputFileLines`)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
24
|
+
* render and in WHAT order (`standingContextSections`) and the
|
|
25
|
+
* input-files bullet and disclosure lines (`inputFileLines`). Their WORDS
|
|
26
|
+
* come from each fact's own module; this builder only frames them. Every
|
|
27
|
+
* mounted skill is described on every turn, as the Cursor harness does:
|
|
28
|
+
* choosing skills by the message made the system prompt follow the
|
|
29
|
+
* message.
|
|
17
30
|
* - This harness's: the framing — `## Heading` markdown sections, the house
|
|
18
31
|
* style for a system prompt — and every text that quotes this engine's
|
|
19
32
|
* tools: the `## Skills` activation protocol, the response rules, the
|
|
@@ -24,7 +37,8 @@
|
|
|
24
37
|
*
|
|
25
38
|
* The rendered bytes are load-bearing for the hermetic tests: `ScriptedModel`
|
|
26
39
|
* tells its scripted roles apart by the system prompt's text, so this
|
|
27
|
-
* module's whole output
|
|
40
|
+
* module's whole output — the system prompt and the turn's message — is
|
|
41
|
+
* pinned by `__tests__/prompt-goldens.test.ts`.
|
|
28
42
|
*/
|
|
29
43
|
|
|
30
44
|
import { relative } from "node:path";
|
|
@@ -43,10 +57,7 @@ import {
|
|
|
43
57
|
} from "../../shared/prompt-sections.js";
|
|
44
58
|
import { effectiveInstructions } from "../../shared/builtin-assistant-prompt.js";
|
|
45
59
|
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
46
|
-
import {
|
|
47
|
-
buildImplementPlanDirective,
|
|
48
|
-
findApprovedPlanPath,
|
|
49
|
-
} from "../../shared/implement-plan-prompt.js";
|
|
60
|
+
import { buildImplementPlanDirective } from "../../shared/implement-plan-prompt.js";
|
|
50
61
|
import type { ResolvedAttachment } from "../../shared/attachment-resolver.js";
|
|
51
62
|
import type { SkillMetadata } from "../../shared/skill-resolver.js";
|
|
52
63
|
import { STIGMER_LOCAL_STATE_DIR } from "../../shared/workspace/stigmer-link.js";
|
|
@@ -131,22 +142,6 @@ export interface PromptBuilderInput {
|
|
|
131
142
|
* when the agent serves no proactive channel or nothing is sendable.
|
|
132
143
|
*/
|
|
133
144
|
channelTemplatesPromptSection?: string;
|
|
134
|
-
workspaceFileRefs: string[];
|
|
135
|
-
workspaceRoot: string;
|
|
136
|
-
/** The turn's resolved input files, as the runtime's attachment phase returns them. */
|
|
137
|
-
inputFiles: readonly ResolvedAttachment[];
|
|
138
|
-
/**
|
|
139
|
-
* Vision facts about this turn's attachments (T04): which images the model
|
|
140
|
-
* sees inline in the user message and which degraded to path-only.
|
|
141
|
-
* Rendered inside the Input Files section.
|
|
142
|
-
*/
|
|
143
|
-
vision?: VisionPromptInfo;
|
|
144
|
-
/**
|
|
145
|
-
* What kind of URL the turn's storage backend mints (issue #532) — keys
|
|
146
|
-
* the Input Files section's hand-off wording (attachment-download-urls.ts).
|
|
147
|
-
* One turn-level fact: all attachments ride the one configured storage.
|
|
148
|
-
*/
|
|
149
|
-
downloadUrlKind?: DownloadUrlKind;
|
|
150
145
|
/**
|
|
151
146
|
* The execution's interaction mode. PLAN appends the shared plan-mode
|
|
152
147
|
* directive so the model knows the turn's deliverable is a plan document.
|
|
@@ -163,6 +158,14 @@ export interface PromptBuilderInput {
|
|
|
163
158
|
* message itself is just a short label ("Build from plan").
|
|
164
159
|
*/
|
|
165
160
|
buildFromPlan?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* The approved plan's path among the turn's input files
|
|
163
|
+
* (`implement-plan-prompt.ts` `findApprovedPlanPath`), read only under
|
|
164
|
+
* `buildFromPlan`. Absent when the plan did not materialize, which selects
|
|
165
|
+
* the directive's conversation-plan variant. The Input Files section
|
|
166
|
+
* itself rides the turn's message (`composeUserMessage`).
|
|
167
|
+
*/
|
|
168
|
+
approvedPlanPath?: string;
|
|
166
169
|
/**
|
|
167
170
|
* Rollover context bridge (cloud DD-013): a digest of the previous
|
|
168
171
|
* session's conversation, read from `SessionSpec.metadata`. Injected on
|
|
@@ -252,22 +255,6 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
252
255
|
prompt += "\n\n" + input.channelTemplatesPromptSection;
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
if (input.workspaceFileRefs.length > 0) {
|
|
256
|
-
const refSection = buildReferencedFilesSection(
|
|
257
|
-
input.workspaceFileRefs,
|
|
258
|
-
input.workspaceRoot,
|
|
259
|
-
);
|
|
260
|
-
if (refSection) {
|
|
261
|
-
prompt += refSection;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (input.inputFiles.length > 0) {
|
|
266
|
-
prompt += buildInputFilesSection(
|
|
267
|
-
input.inputFiles, input.vision, input.downloadUrlKind,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
258
|
// The standing context, in the shared order (the doctrine is stated once,
|
|
272
259
|
// on `standingContextSections`); this harness only frames each section.
|
|
273
260
|
for (const section of standingContextSections(input)) {
|
|
@@ -296,34 +283,67 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
296
283
|
}
|
|
297
284
|
|
|
298
285
|
if (input.buildFromPlan) {
|
|
299
|
-
const planPath = findApprovedPlanPath(
|
|
300
|
-
input.inputFiles.map((f) => f.relativePath),
|
|
301
|
-
);
|
|
302
286
|
prompt +=
|
|
303
287
|
"\n\n## Implement the approved plan\n\n" +
|
|
304
|
-
buildImplementPlanDirective(
|
|
288
|
+
buildImplementPlanDirective(input.approvedPlanPath);
|
|
305
289
|
}
|
|
306
290
|
|
|
307
291
|
return prompt;
|
|
308
292
|
}
|
|
309
293
|
|
|
294
|
+
/** The turn's own content, composed around what the user typed. */
|
|
295
|
+
export interface TurnMessageInput {
|
|
296
|
+
/** What the user typed (`spec.message`); never mutated. */
|
|
297
|
+
readonly message: string;
|
|
298
|
+
/** The conversation catchup digest (cloud DD-006), when the turn carries one. */
|
|
299
|
+
readonly conversationCatchup?: string;
|
|
300
|
+
/** This turn's resolved input files, as the runtime's attachment phase returns them. */
|
|
301
|
+
readonly inputFiles: readonly ResolvedAttachment[];
|
|
302
|
+
/**
|
|
303
|
+
* Vision facts about this turn's attachments (T04): which images the model
|
|
304
|
+
* sees inline in this same message and which degraded to path-only.
|
|
305
|
+
* Rendered inside the Input Files section.
|
|
306
|
+
*/
|
|
307
|
+
readonly vision?: VisionPromptInfo;
|
|
308
|
+
/**
|
|
309
|
+
* What kind of URL the turn's storage backend mints (issue #532) — keys
|
|
310
|
+
* the Input Files section's hand-off wording (attachment-download-urls.ts).
|
|
311
|
+
* One turn-level fact: all attachments ride the one configured storage.
|
|
312
|
+
*/
|
|
313
|
+
readonly downloadUrlKind?: DownloadUrlKind;
|
|
314
|
+
/** The workspace paths the user highlighted for this message (`spec.workspace_file_refs`). */
|
|
315
|
+
readonly workspaceFileRefs: readonly string[];
|
|
316
|
+
}
|
|
317
|
+
|
|
310
318
|
/**
|
|
311
|
-
* Compose the turn's USER MESSAGE for the graph invocation:
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
+
* Compose the turn's USER MESSAGE for the graph invocation: this turn's
|
|
320
|
+
* payload (its input files, then the workspace paths it references), then the
|
|
321
|
+
* framed conversation catchup (cloud DD-006), then what the user typed, each
|
|
322
|
+
* set off by a horizontal rule. Payload first and context closest to the task
|
|
323
|
+
* is the Cursor harness's resumed-turn order.
|
|
324
|
+
*
|
|
325
|
+
* In the user message and never the system prompt (A27, and the module
|
|
326
|
+
* header): the system prompt is rebuilt per invocation, so a turn's content
|
|
327
|
+
* there would vanish one turn later and, while it lasted, change the cached
|
|
328
|
+
* prefix; a message enters the checkpointer with the turn and persists in
|
|
329
|
+
* history, so a later turn still knows which files an earlier one carried.
|
|
330
|
+
* An approval resume sends no new message and needs none: the payload is
|
|
331
|
+
* already in the checkpointed one. The caller's `spec.message` is never
|
|
332
|
+
* mutated; the composition exists only in the graph input.
|
|
319
333
|
*/
|
|
320
|
-
export function composeUserMessage(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
export function composeUserMessage(input: TurnMessageInput): string {
|
|
335
|
+
const parts: string[] = [];
|
|
336
|
+
if (input.inputFiles.length > 0) {
|
|
337
|
+
parts.push(buildInputFilesSection(input.inputFiles, input.vision, input.downloadUrlKind));
|
|
338
|
+
}
|
|
339
|
+
if (input.workspaceFileRefs.length > 0) {
|
|
340
|
+
parts.push(buildReferencedFilesSection(input.workspaceFileRefs));
|
|
341
|
+
}
|
|
342
|
+
if (input.conversationCatchup) {
|
|
343
|
+
parts.push(formatConversationCatchupText(input.conversationCatchup));
|
|
344
|
+
}
|
|
345
|
+
parts.push(input.message);
|
|
346
|
+
return parts.join("\n\n---\n\n");
|
|
327
347
|
}
|
|
328
348
|
|
|
329
349
|
/**
|
|
@@ -454,14 +474,14 @@ function formatEntryDescription(result: ProvisionResult): string {
|
|
|
454
474
|
);
|
|
455
475
|
}
|
|
456
476
|
|
|
477
|
+
// No commit: the clone's HEAD moves with every write-back commit, and a
|
|
478
|
+
// system prompt that names it would change on every such turn
|
|
479
|
+
// (`shared/workspace/sources/git.ts` says the same for one entry).
|
|
457
480
|
if (result.sourceType === "git_repo" && result.gitMetadata) {
|
|
458
481
|
const meta: GitMetadata = result.gitMetadata;
|
|
459
|
-
const
|
|
460
|
-
? meta.baseCommit.slice(0, 7)
|
|
461
|
-
: meta.baseCommit;
|
|
482
|
+
const branch = meta.branch ? ` (branch: ${meta.branch})` : "";
|
|
462
483
|
return (
|
|
463
|
-
`Workspace entry **${name}** was initialized from ` +
|
|
464
|
-
`${meta.repoUrl} (branch: ${meta.branch}, commit: ${shortSha}).\n` +
|
|
484
|
+
`Workspace entry **${name}** was initialized from ${meta.repoUrl}${branch}.\n` +
|
|
465
485
|
"Changes you make will be captured as artifacts when execution completes."
|
|
466
486
|
);
|
|
467
487
|
}
|
|
@@ -476,36 +496,29 @@ function formatEntryDescription(result: ProvisionResult): string {
|
|
|
476
496
|
return result.workspaceDescription;
|
|
477
497
|
}
|
|
478
498
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (workspaceFileRefs.length === 0) return "";
|
|
484
|
-
|
|
485
|
-
let section =
|
|
486
|
-
"\n\n## Referenced Files\n\n" +
|
|
499
|
+
/** The `## Referenced Files` part of the turn's message: the paths the user highlighted, one bullet each. */
|
|
500
|
+
function buildReferencedFilesSection(workspaceFileRefs: readonly string[]): string {
|
|
501
|
+
return (
|
|
502
|
+
"## Referenced Files\n\n" +
|
|
487
503
|
"The user has highlighted the following workspace paths for your " +
|
|
488
|
-
`attention. Use \`${ENGINE_TOOL.readFile}\` to access file contents.\n\n
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
section += `- \`${refPath}\`\n`;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return section;
|
|
504
|
+
`attention. Use \`${ENGINE_TOOL.readFile}\` to access file contents.\n\n` +
|
|
505
|
+
workspaceFileRefs.map((refPath) => `- \`${refPath}\``).join("\n")
|
|
506
|
+
);
|
|
495
507
|
}
|
|
496
508
|
|
|
497
509
|
/**
|
|
498
|
-
* The `## Input Files`
|
|
499
|
-
* file-reading tool) around the shared lines
|
|
500
|
-
* says what each bullet and disclosure
|
|
501
|
-
* group is set off by a blank line, the
|
|
510
|
+
* The `## Input Files` part of the turn's message: this harness's intro (it
|
|
511
|
+
* names this engine's file-reading tool) around the shared lines
|
|
512
|
+
* (`prompt-sections.ts` `inputFileLines` says what each bullet and disclosure
|
|
513
|
+
* carries and why). Each disclosure group is set off by a blank line, the
|
|
514
|
+
* markdown idiom.
|
|
502
515
|
*/
|
|
503
516
|
function buildInputFilesSection(
|
|
504
517
|
files: readonly ResolvedAttachment[],
|
|
505
518
|
vision?: VisionPromptInfo,
|
|
506
519
|
downloadUrlKind?: DownloadUrlKind,
|
|
507
520
|
): string {
|
|
508
|
-
let section = "
|
|
521
|
+
let section = "## Input Files\n\n";
|
|
509
522
|
section +=
|
|
510
523
|
"The following files have been provided as read-only reference " +
|
|
511
524
|
"material for your task. They live under `.stigmer/inputs/` and " +
|
|
@@ -526,7 +539,6 @@ function buildInputFilesSection(
|
|
|
526
539
|
section += "\n" + lines.vision.join("\n") + "\n";
|
|
527
540
|
}
|
|
528
541
|
|
|
529
|
-
//
|
|
530
|
-
// ends on its last line rather than adding a second blank (#1127).
|
|
542
|
+
// The part ends on its last line; the message's rule sets it off (#1127).
|
|
531
543
|
return section.replace(/\n+$/, "");
|
|
532
544
|
}
|