@stigmer/runner 3.1.1 → 3.1.3
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/dist/.build-fingerprint +1 -1
- package/dist/activities/execute-cursor/attachment-resolver.d.ts +49 -8
- package/dist/activities/execute-cursor/attachment-resolver.js +83 -26
- package/dist/activities/execute-cursor/attachment-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +18 -0
- package/dist/activities/execute-cursor/capture-flow.js +21 -0
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
- package/dist/activities/execute-cursor/cas-observations.d.ts +13 -0
- package/dist/activities/execute-cursor/cas-observations.js +39 -5
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
- package/dist/activities/execute-cursor/hook-script.js +19 -1
- package/dist/activities/execute-cursor/hook-script.js.map +1 -1
- package/dist/activities/execute-cursor/index.d.ts +5 -0
- package/dist/activities/execute-cursor/index.js +222 -160
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.js +10 -1
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +32 -7
- package/dist/activities/execute-cursor/prompt-builder.js +38 -20
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/skill-resolver.d.ts +1 -10
- package/dist/activities/execute-cursor/skill-resolver.js +3 -55
- package/dist/activities/execute-cursor/skill-resolver.js.map +1 -1
- package/dist/activities/execute-cursor/stigmer-link.d.ts +35 -0
- package/dist/activities/execute-cursor/stigmer-link.js +73 -0
- package/dist/activities/execute-cursor/stigmer-link.js.map +1 -0
- package/dist/activities/execute-cursor/turn-boundary.d.ts +108 -0
- package/dist/activities/execute-cursor/turn-boundary.js +163 -0
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -0
- package/dist/activities/execute-cursor/workspace-provision.d.ts +3 -2
- package/dist/activities/execute-cursor/workspace-provision.js +5 -3
- package/dist/activities/execute-cursor/workspace-provision.js.map +1 -1
- package/dist/activities/execute-deep-agent/index.js +89 -5
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +17 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js +16 -0
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/setup.js +22 -16
- package/dist/activities/execute-deep-agent/setup.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-v3.js +6 -1
- package/dist/activities/execute-deep-agent/streaming-v3.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming.d.ts +7 -0
- package/dist/activities/execute-deep-agent/streaming.js +5 -1
- package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/middleware/approval-gate.js +36 -6
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/runner-manager.d.ts +2 -0
- package/dist/runner-manager.js +2 -1
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +2 -1
- package/dist/runner.js.map +1 -1
- package/dist/shared/artifact-storage.d.ts +32 -0
- package/dist/shared/artifact-storage.js +74 -1
- package/dist/shared/artifact-storage.js.map +1 -1
- package/dist/shared/filereview/capture.d.ts +18 -1
- package/dist/shared/filereview/capture.js +105 -28
- package/dist/shared/filereview/capture.js.map +1 -1
- package/dist/shared/filereview/cas-substrate.d.ts +9 -0
- package/dist/shared/filereview/cas-substrate.js +9 -1
- package/dist/shared/filereview/cas-substrate.js.map +1 -1
- package/dist/shared/filereview/events.d.ts +16 -0
- package/dist/shared/filereview/events.js +37 -0
- package/dist/shared/filereview/events.js.map +1 -1
- package/dist/shared/filereview/git-substrate.d.ts +42 -0
- package/dist/shared/filereview/git-substrate.js +78 -0
- package/dist/shared/filereview/git-substrate.js.map +1 -1
- package/dist/shared/filereview/index.d.ts +3 -1
- package/dist/shared/filereview/index.js +3 -1
- package/dist/shared/filereview/index.js.map +1 -1
- package/dist/shared/filereview/line-counts.d.ts +48 -0
- package/dist/shared/filereview/line-counts.js +72 -0
- package/dist/shared/filereview/line-counts.js.map +1 -0
- package/dist/shared/filereview/progress.d.ts +96 -0
- package/dist/shared/filereview/progress.js +134 -0
- package/dist/shared/filereview/progress.js.map +1 -0
- package/dist/shared/implement-plan-prompt.d.ts +44 -0
- package/dist/shared/implement-plan-prompt.js +98 -0
- package/dist/shared/implement-plan-prompt.js.map +1 -0
- package/dist/shared/plan-artifact.d.ts +59 -11
- package/dist/shared/plan-artifact.js +144 -16
- package/dist/shared/plan-artifact.js.map +1 -1
- package/dist/shared/plan-mode-prompt.d.ts +32 -0
- package/dist/shared/plan-mode-prompt.js +56 -0
- package/dist/shared/plan-mode-prompt.js.map +1 -0
- package/dist/shared/status.d.ts +11 -0
- package/dist/shared/status.js +12 -2
- package/dist/shared/status.js.map +1 -1
- package/dist/shared/tool-row.d.ts +30 -1
- package/dist/shared/tool-row.js +53 -4
- package/dist/shared/tool-row.js.map +1 -1
- package/dist/shared/workspace/session-root.d.ts +35 -0
- package/dist/shared/workspace/session-root.js +53 -0
- package/dist/shared/workspace/session-root.js.map +1 -0
- package/dist/shared/workspace/workspace-lock.d.ts +86 -0
- package/dist/shared/workspace/workspace-lock.js +187 -0
- package/dist/shared/workspace/workspace-lock.js.map +1 -0
- package/package.json +8 -5
- package/src/__tests__/config.test.ts +8 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
- package/src/activities/execute-cursor/__tests__/attachment-resolver.test.ts +161 -0
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +132 -2
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +55 -0
- package/src/activities/execute-cursor/__tests__/delta-enricher.test.ts +68 -0
- package/src/activities/execute-cursor/__tests__/hitl-ledger.test.ts +28 -0
- package/src/activities/execute-cursor/__tests__/hook-script.test.ts +63 -4
- package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +280 -0
- package/src/activities/execute-cursor/__tests__/workspace-provision.test.ts +36 -2
- package/src/activities/execute-cursor/attachment-resolver.ts +116 -34
- package/src/activities/execute-cursor/capture-flow.ts +32 -0
- package/src/activities/execute-cursor/cas-observations.ts +41 -5
- package/src/activities/execute-cursor/hook-script.ts +19 -1
- package/src/activities/execute-cursor/index.ts +243 -178
- package/src/activities/execute-cursor/message-translator.ts +9 -1
- package/src/activities/execute-cursor/prompt-builder.ts +60 -22
- package/src/activities/execute-cursor/skill-resolver.ts +3 -59
- package/src/activities/execute-cursor/stigmer-link.ts +78 -0
- package/src/activities/execute-cursor/turn-boundary.ts +274 -0
- package/src/activities/execute-cursor/workspace-provision.ts +5 -3
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +7 -2
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +7 -2
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +4 -1
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +99 -0
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +7 -2
- package/src/activities/execute-deep-agent/index.ts +104 -5
- package/src/activities/execute-deep-agent/prompt-builder.ts +39 -0
- package/src/activities/execute-deep-agent/setup.ts +25 -17
- package/src/activities/execute-deep-agent/streaming-v3.ts +6 -1
- package/src/activities/execute-deep-agent/streaming.ts +12 -1
- package/src/config.ts +17 -0
- package/src/middleware/__tests__/approval-gate.test.ts +77 -12
- package/src/middleware/approval-gate.ts +39 -7
- package/src/runner-manager.ts +6 -1
- package/src/runner.ts +6 -1
- package/src/shared/__tests__/artifact-storage.test.ts +111 -1
- package/src/shared/__tests__/implement-plan-prompt.test.ts +86 -0
- package/src/shared/__tests__/plan-artifact.test.ts +142 -9
- package/src/shared/__tests__/secret-leak-scan.test.ts +105 -0
- package/src/shared/__tests__/status.test.ts +7 -2
- package/src/shared/__tests__/tool-row.test.ts +136 -1
- package/src/shared/artifact-storage.ts +83 -1
- package/src/shared/filereview/__tests__/capture.test.ts +182 -1
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +34 -0
- package/src/shared/filereview/__tests__/events.test.ts +105 -0
- package/src/shared/filereview/__tests__/line-counts.test.ts +73 -0
- package/src/shared/filereview/__tests__/progress.test.ts +236 -0
- package/src/shared/filereview/capture.ts +134 -30
- package/src/shared/filereview/cas-substrate.ts +17 -1
- package/src/shared/filereview/events.ts +50 -0
- package/src/shared/filereview/git-substrate.ts +108 -0
- package/src/shared/filereview/index.ts +22 -1
- package/src/shared/filereview/line-counts.ts +83 -0
- package/src/shared/filereview/progress.ts +186 -0
- package/src/shared/implement-plan-prompt.ts +104 -0
- package/src/shared/plan-artifact.ts +152 -16
- package/src/shared/plan-mode-prompt.ts +56 -0
- package/src/shared/status.ts +11 -1
- package/src/shared/tool-row.ts +58 -5
- package/src/shared/workspace/__tests__/session-root.test.ts +42 -0
- package/src/shared/workspace/__tests__/workspace-lock.test.ts +200 -0
- package/src/shared/workspace/session-root.ts +60 -0
- package/src/shared/workspace/workspace-lock.ts +241 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
2
3
|
import { buildEnhancedSystemPrompt } from "../prompt-builder.js";
|
|
4
|
+
import { PLAN_MODE_DIRECTIVE } from "../../../shared/plan-mode-prompt.js";
|
|
3
5
|
import { SourceType } from "../../../shared/workspace/types.js";
|
|
4
6
|
import type { ProvisionResult } from "../../../shared/workspace/types.js";
|
|
5
7
|
|
|
@@ -205,4 +207,101 @@ describe("buildEnhancedSystemPrompt", () => {
|
|
|
205
207
|
|
|
206
208
|
expect(prompt).not.toContain("## Workspace");
|
|
207
209
|
});
|
|
210
|
+
|
|
211
|
+
describe("plan mode", () => {
|
|
212
|
+
const base = {
|
|
213
|
+
instructions: "Test",
|
|
214
|
+
provisionResults: [],
|
|
215
|
+
containerRoot: "",
|
|
216
|
+
skillsPromptSection: "",
|
|
217
|
+
workspaceFileRefs: [],
|
|
218
|
+
workspaceRoot: "/workspace",
|
|
219
|
+
injectedFiles: [],
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
it("appends the shared plan-mode directive as the final section", () => {
|
|
223
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
224
|
+
...base,
|
|
225
|
+
interactionMode: InteractionMode.PLAN,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(prompt).toContain("## Plan mode");
|
|
229
|
+
expect(prompt.endsWith(PLAN_MODE_DIRECTIVE)).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("omits the directive for Agent mode", () => {
|
|
233
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
234
|
+
...base,
|
|
235
|
+
interactionMode: InteractionMode.AGENT,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(prompt).not.toContain("## Plan mode");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("omits the directive when the mode is unset", () => {
|
|
242
|
+
const prompt = buildEnhancedSystemPrompt(base);
|
|
243
|
+
|
|
244
|
+
expect(prompt).not.toContain("## Plan mode");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
describe("build from plan", () => {
|
|
249
|
+
const base = {
|
|
250
|
+
instructions: "Test",
|
|
251
|
+
provisionResults: [],
|
|
252
|
+
containerRoot: "",
|
|
253
|
+
skillsPromptSection: "",
|
|
254
|
+
workspaceFileRefs: [],
|
|
255
|
+
workspaceRoot: "/workspace",
|
|
256
|
+
injectedFiles: [],
|
|
257
|
+
};
|
|
258
|
+
const planFile = {
|
|
259
|
+
filename: "plan.md",
|
|
260
|
+
path: ".stigmer/inputs/plan.md",
|
|
261
|
+
size: 1024,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
it("appends the implement-plan directive pointing at the injected plan", () => {
|
|
265
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
266
|
+
...base,
|
|
267
|
+
buildFromPlan: true,
|
|
268
|
+
injectedFiles: [planFile],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(prompt).toContain("## Implement the approved plan");
|
|
272
|
+
expect(prompt).toContain("`.stigmer/inputs/plan.md`");
|
|
273
|
+
expect(prompt).toContain("APPROVED");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("falls back to the conversation-plan variant when the plan did not inject", () => {
|
|
277
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
278
|
+
...base,
|
|
279
|
+
buildFromPlan: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(prompt).toContain("## Implement the approved plan");
|
|
283
|
+
expect(prompt).toContain("conversation above");
|
|
284
|
+
expect(prompt).not.toContain("plan.md");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("omits the directive for an ordinary execution", () => {
|
|
288
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
289
|
+
...base,
|
|
290
|
+
injectedFiles: [planFile],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(prompt).not.toContain("## Implement the approved plan");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("carries the plan-derived progress-tracking instruction (Tier 3)", () => {
|
|
297
|
+
const prompt = buildEnhancedSystemPrompt({
|
|
298
|
+
...base,
|
|
299
|
+
buildFromPlan: true,
|
|
300
|
+
injectedFiles: [planFile],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(prompt).toContain("to-do list");
|
|
304
|
+
expect(prompt).toContain("break the plan into");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
208
307
|
});
|
|
@@ -51,7 +51,9 @@ import type { SetupResult } from "../setup.js";
|
|
|
51
51
|
vi.mock("@temporalio/activity", () => ({
|
|
52
52
|
Context: {
|
|
53
53
|
current: () => ({
|
|
54
|
-
|
|
54
|
+
// A real AbortSignal: the workspace-lock wait registers abort listeners
|
|
55
|
+
// on it, which a bare `{ aborted: false }` stub cannot satisfy.
|
|
56
|
+
cancellationSignal: new AbortController().signal,
|
|
55
57
|
heartbeat: vi.fn(),
|
|
56
58
|
}),
|
|
57
59
|
},
|
|
@@ -187,7 +189,9 @@ function fakeMemoryGateSetup(opts: {
|
|
|
187
189
|
agent: {},
|
|
188
190
|
session: { spec: { workspaceEntries: [] } },
|
|
189
191
|
workspaceBackend: {
|
|
190
|
-
|
|
192
|
+
// Unique per test file: the workspace turn lock keys on this path, and a
|
|
193
|
+
// path shared across files would serialize parallel test workers for real.
|
|
194
|
+
rootDir: "/tmp/stigmer-test-ws-seq-gate",
|
|
191
195
|
exists: vi.fn(async () => false),
|
|
192
196
|
readFile: vi.fn(async () => ""),
|
|
193
197
|
},
|
|
@@ -252,6 +256,7 @@ const memoryConfig: Config = {
|
|
|
252
256
|
checkpointerProxyEndpoint: null,
|
|
253
257
|
primaryModel: "claude-sonnet",
|
|
254
258
|
cursorStreamStallTimeoutMs: 180000,
|
|
259
|
+
workspaceLockTimeoutMs: 900000,
|
|
255
260
|
};
|
|
256
261
|
|
|
257
262
|
function toolCallIds(status: AgentExecutionStatus): string[] {
|
|
@@ -19,7 +19,13 @@ import { AgentMessageSchema, ToolCallSchema } from "@stigmer/protos/ai/stigmer/a
|
|
|
19
19
|
import { ExecutionPhase, FileCaptureClass, FileChangeSetStatus, InteractionMode, MessageType, ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
20
20
|
import { activityStarted, activityFinished } from "../../idle-watchdog.js";
|
|
21
21
|
import { normalizeActivityInput, type ExecuteActivityInput } from "../../shared/activity-input.js";
|
|
22
|
-
import { persistStatus, slimStatus, utcTimestamp } from "../../shared/status.js";
|
|
22
|
+
import { persistStatus, reportSetupProgress, slimStatus, utcTimestamp } from "../../shared/status.js";
|
|
23
|
+
import {
|
|
24
|
+
acquireWorkspaceLock,
|
|
25
|
+
WorkspaceLockCancelledError,
|
|
26
|
+
WorkspaceLockTimeoutError,
|
|
27
|
+
type ReleaseWorkspaceLock,
|
|
28
|
+
} from "../../shared/workspace/workspace-lock.js";
|
|
23
29
|
import type { ToolOutputOffloadContext } from "../../shared/status-offload.js";
|
|
24
30
|
import { publishPlanArtifact } from "../../shared/plan-artifact.js";
|
|
25
31
|
import { classifyTool } from "../../shared/tool-kind.js";
|
|
@@ -44,11 +50,15 @@ import {
|
|
|
44
50
|
captureBaselineToLedger,
|
|
45
51
|
captureCandidateToLedger,
|
|
46
52
|
} from "../../shared/filereview/capture.js";
|
|
53
|
+
import {
|
|
54
|
+
captureFileChangeProgress,
|
|
55
|
+
newProgressCaptureState,
|
|
56
|
+
} from "../../shared/filereview/progress.js";
|
|
47
57
|
import { hasCandidateCaptured } from "../../shared/filereview/events.js";
|
|
48
58
|
import { casBlobReader, type CasPathCapture } from "../../shared/filereview/cas-substrate.js";
|
|
49
59
|
import { partitionIgnoredPathsBySecret } from "../../shared/filereview/secret-paths.js";
|
|
50
60
|
import type { CasCaptureObserver } from "./cas-capture-observer.js";
|
|
51
|
-
import { collectSubAgentToolCallIds } from "../../shared/tool-row.js";
|
|
61
|
+
import { collectSubAgentToolCallIds, withholdSecretContentFromMessages } from "../../shared/tool-row.js";
|
|
52
62
|
import { stampFlowedFileEditRows, stampFlowedSubAgentFileEditRows } from "./stamp-flowed-rows.js";
|
|
53
63
|
|
|
54
64
|
/** The harness id stamped on the deep-agent's file-review ledger events. */
|
|
@@ -75,6 +85,11 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
75
85
|
const { executionId, threadId, turnSeq } = normalizeActivityInput(arg0, arg1);
|
|
76
86
|
activityStarted();
|
|
77
87
|
let setup: SetupResult | null = null;
|
|
88
|
+
// Exclusive turn lock on the workspace working tree — held across the
|
|
89
|
+
// entire tree-mutating window (decision reconcile, agent writes,
|
|
90
|
+
// candidate capture, write-back) and released in the finally. Mirrors
|
|
91
|
+
// the Cursor harness; see shared/workspace/workspace-lock.ts.
|
|
92
|
+
let releaseWorkspaceLock: ReleaseWorkspaceLock | undefined;
|
|
78
93
|
|
|
79
94
|
try {
|
|
80
95
|
console.log(`[ExecuteDeepAgent] Started for execution ${executionId}`);
|
|
@@ -163,6 +178,45 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
163
178
|
const gitRoot = setup.workspaceBackend.rootDir;
|
|
164
179
|
const changeSetId = `${executionId}:${turnSeq}`;
|
|
165
180
|
|
|
181
|
+
// Serialize this turn against every other execution sharing this
|
|
182
|
+
// working tree — an unserialized concurrent write lands inside this
|
|
183
|
+
// turn's baseline→candidate window and gets misattributed to this
|
|
184
|
+
// turn's review. Acquired before ANY tree mutation below (decision
|
|
185
|
+
// reconcile, agent writes, candidate capture, write-back); waiting
|
|
186
|
+
// surfaces a visible state and heartbeats, and a cancel aborts the
|
|
187
|
+
// wait immediately. Mirrors the Cursor harness wiring exactly.
|
|
188
|
+
try {
|
|
189
|
+
releaseWorkspaceLock = await acquireWorkspaceLock(gitRoot, {
|
|
190
|
+
onWaiting: () => reportSetupProgress(
|
|
191
|
+
client, executionId, "Waiting for workspace — in use by another session",
|
|
192
|
+
),
|
|
193
|
+
heartbeat: () => Context.current().heartbeat(),
|
|
194
|
+
signal: Context.current().cancellationSignal,
|
|
195
|
+
timeoutMs: config.workspaceLockTimeoutMs,
|
|
196
|
+
});
|
|
197
|
+
} catch (lockErr) {
|
|
198
|
+
if (lockErr instanceof WorkspaceLockCancelledError) {
|
|
199
|
+
throw new CancelledFailure("Activity cancelled while waiting for the workspace lock");
|
|
200
|
+
}
|
|
201
|
+
if (lockErr instanceof WorkspaceLockTimeoutError) {
|
|
202
|
+
const failedStatus = create(AgentExecutionStatusSchema, {
|
|
203
|
+
phase: ExecutionPhase.EXECUTION_FAILED,
|
|
204
|
+
error: lockErr.message,
|
|
205
|
+
completedAt: utcTimestamp(),
|
|
206
|
+
messages: [
|
|
207
|
+
create(AgentMessageSchema, {
|
|
208
|
+
type: MessageType.MESSAGE_SYSTEM,
|
|
209
|
+
content: `Execution failed: ${lockErr.message}`,
|
|
210
|
+
timestamp: utcTimestamp(),
|
|
211
|
+
}),
|
|
212
|
+
],
|
|
213
|
+
});
|
|
214
|
+
await persistStatus(client, executionId, failedStatus, { offload: statusOffload });
|
|
215
|
+
return slimStatus(failedStatus);
|
|
216
|
+
}
|
|
217
|
+
throw lockErr;
|
|
218
|
+
}
|
|
219
|
+
|
|
166
220
|
// (1) Capture-mode resume — reconcile any DECIDED change set FIRST (this
|
|
167
221
|
// drops the per-execution refs), before the next baseline re-pins them
|
|
168
222
|
// (refs are executionId-keyed). Then (2) short-circuit a pure file-review
|
|
@@ -269,6 +323,15 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
269
323
|
});
|
|
270
324
|
}
|
|
271
325
|
|
|
326
|
+
// Per-turn state for mid-run live capture (DD-32): the last progress tree
|
|
327
|
+
// sha (short-circuit) + last capture time (floor), threaded across the
|
|
328
|
+
// streaming loop's persists via the beforePersist hook below. The capture
|
|
329
|
+
// flags are hoisted so the deferred hook does not depend on `setup`
|
|
330
|
+
// (non-null here, but not narrowable inside a later-invoked closure).
|
|
331
|
+
const progressState = newProgressCaptureState();
|
|
332
|
+
const progressCaptureMode = setup.captureMode;
|
|
333
|
+
const progressGitWorkspace = setup.gitWorkspace;
|
|
334
|
+
|
|
272
335
|
const cancellationSignal = Context.current().cancellationSignal;
|
|
273
336
|
|
|
274
337
|
const result: StreamResult = await streamExecution({
|
|
@@ -294,6 +357,23 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
294
357
|
globalBypass: setup.globalBypass,
|
|
295
358
|
},
|
|
296
359
|
streamVersion: setup.streamVersion,
|
|
360
|
+
// Mid-run live capture (DD-32): attach file_change_progress before each
|
|
361
|
+
// scheduled persist. Git capture mode only (a pinned baseline exists);
|
|
362
|
+
// deep-agent writes no runner-owned gate files into the tree, so no
|
|
363
|
+
// excludePaths — matching its turn-boundary candidate capture.
|
|
364
|
+
beforePersist: async (status) => {
|
|
365
|
+
if (!progressCaptureMode || !progressGitWorkspace || !captureBaselineTree) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
await captureFileChangeProgress({
|
|
369
|
+
status,
|
|
370
|
+
gitRoot,
|
|
371
|
+
executionId,
|
|
372
|
+
changeSetId,
|
|
373
|
+
baselineTree: captureBaselineTree,
|
|
374
|
+
state: progressState,
|
|
375
|
+
});
|
|
376
|
+
},
|
|
297
377
|
});
|
|
298
378
|
|
|
299
379
|
await processPostStream({
|
|
@@ -308,6 +388,20 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
308
388
|
executionId,
|
|
309
389
|
});
|
|
310
390
|
|
|
391
|
+
// Never-persist-secret backstop (DD-26 #2): withhold content from any
|
|
392
|
+
// built-in write row targeting a secret-like path, across the top-level
|
|
393
|
+
// and sub-agent transcripts, before ANY persist below. On the deny-gate a
|
|
394
|
+
// secret write is hard-blocked (a COMPLETED row) or — under
|
|
395
|
+
// auto_approve_all, where no gate is installed — flowed; either way the
|
|
396
|
+
// streamed row still carries `args` from handleToolStarted, and the
|
|
397
|
+
// capture-mode stamping pass (which performs the same scrub) does not run
|
|
398
|
+
// here. Idempotent / a no-op in capture mode. This single post-stream
|
|
399
|
+
// sweep is the one choke point that precedes every downstream persist.
|
|
400
|
+
withholdSecretContentFromMessages(
|
|
401
|
+
initialStatus.messages,
|
|
402
|
+
initialStatus.subAgentExecutions,
|
|
403
|
+
);
|
|
404
|
+
|
|
311
405
|
// Turn boundary (capture mode): capture the candidate change set from the
|
|
312
406
|
// git diff and author CANDIDATE_CAPTURED, then stamp the flowed file-edit
|
|
313
407
|
// rows with the change set id — they stay visible in place as
|
|
@@ -492,9 +586,10 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
492
586
|
}
|
|
493
587
|
|
|
494
588
|
// Plan mode: the agent's final message is the plan. Publish it as a
|
|
495
|
-
// first-class plan
|
|
496
|
-
// card and a follow-up Implement run can
|
|
497
|
-
// produces no file to auto-publish, so
|
|
589
|
+
// first-class plan artifact (named from the plan's title) so the UI can
|
|
590
|
+
// render a reviewable Plan card and a follow-up Implement run can
|
|
591
|
+
// reference it. Read-only mode produces no file to auto-publish, so
|
|
592
|
+
// this is the only artifact path.
|
|
498
593
|
// Requires artifact storage; with none (proxy misconfig) the plan text
|
|
499
594
|
// still lives in the final message — only the reviewable artifact is
|
|
500
595
|
// skipped (mirrors Cursor's storage-guarded plan publish).
|
|
@@ -590,6 +685,10 @@ export function createDeepAgentActivities(config: Config) {
|
|
|
590
685
|
|
|
591
686
|
} finally {
|
|
592
687
|
await cleanup(setup);
|
|
688
|
+
// Release the workspace turn lock LAST — the next queued turn must
|
|
689
|
+
// not baseline until every mutation of this one has landed.
|
|
690
|
+
// Idempotent and non-throwing (see workspace-lock.ts).
|
|
691
|
+
await releaseWorkspaceLock?.();
|
|
593
692
|
activityFinished();
|
|
594
693
|
}
|
|
595
694
|
},
|
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { relative } from "node:path";
|
|
10
|
+
import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
10
11
|
import type { ProvisionResult, GitMetadata } from "../../shared/workspace/types.js";
|
|
11
12
|
import { SourceType } from "../../shared/workspace/types.js";
|
|
13
|
+
import { PLAN_MODE_DIRECTIVE } from "../../shared/plan-mode-prompt.js";
|
|
14
|
+
import {
|
|
15
|
+
buildImplementPlanDirective,
|
|
16
|
+
findApprovedPlanPath,
|
|
17
|
+
} from "../../shared/implement-plan-prompt.js";
|
|
12
18
|
|
|
13
19
|
const RESPONSE_RULES = `
|
|
14
20
|
|
|
@@ -84,6 +90,22 @@ export interface PromptBuilderInput {
|
|
|
84
90
|
workspaceFileRefs: string[];
|
|
85
91
|
workspaceRoot: string;
|
|
86
92
|
injectedFiles: InjectedFile[];
|
|
93
|
+
/**
|
|
94
|
+
* The execution's interaction mode. PLAN appends the shared plan-mode
|
|
95
|
+
* directive so the model knows the turn's deliverable is a plan document.
|
|
96
|
+
* Tool-level write enforcement is separate (see setup.ts permissions) —
|
|
97
|
+
* without this directive the model is silently read-only but never told
|
|
98
|
+
* to produce a plan.
|
|
99
|
+
*/
|
|
100
|
+
interactionMode?: InteractionMode;
|
|
101
|
+
/**
|
|
102
|
+
* The execution is a Build-from-plan turn (spec.execution_config
|
|
103
|
+
* .build_from_plan): appends the shared implement-plan directive, pointing
|
|
104
|
+
* the model at the injected approved plan document (or, when the plan
|
|
105
|
+
* attachment did not materialize, at the conversation's plan). The user
|
|
106
|
+
* message itself is just a short label ("Build from plan").
|
|
107
|
+
*/
|
|
108
|
+
buildFromPlan?: boolean;
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
export interface InjectedFile {
|
|
@@ -128,6 +150,23 @@ export function buildEnhancedSystemPrompt(input: PromptBuilderInput): string {
|
|
|
128
150
|
prompt += RESPONSE_RULES;
|
|
129
151
|
prompt += SUB_AGENT_RULES;
|
|
130
152
|
|
|
153
|
+
// Last sections on purpose: these per-execution directives redefine the
|
|
154
|
+
// turn's deliverable, so they must be the freshest instruction the model
|
|
155
|
+
// reads. (PLAN and build_from_plan are mutually exclusive in practice —
|
|
156
|
+
// the build turn is always an Agent-mode execution.)
|
|
157
|
+
if (input.interactionMode === InteractionMode.PLAN) {
|
|
158
|
+
prompt += "\n\n## Plan mode\n\n" + PLAN_MODE_DIRECTIVE;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (input.buildFromPlan) {
|
|
162
|
+
const planPath = findApprovedPlanPath(
|
|
163
|
+
input.injectedFiles.map((f) => f.path),
|
|
164
|
+
);
|
|
165
|
+
prompt +=
|
|
166
|
+
"\n\n## Implement the approved plan\n\n" +
|
|
167
|
+
buildImplementPlanDirective(planPath);
|
|
168
|
+
}
|
|
169
|
+
|
|
131
170
|
return prompt;
|
|
132
171
|
}
|
|
133
172
|
|
|
@@ -35,6 +35,7 @@ import { isGitWorkTree, isPathCapturable } from "../../shared/filereview/git-sub
|
|
|
35
35
|
import { deriveCaptureMode } from "../../shared/filereview/capture.js";
|
|
36
36
|
import { resolveWorkspacePath } from "../../shared/file-change.js";
|
|
37
37
|
import { ensurePlatformDir } from "../../shared/workspace/platform-dir.js";
|
|
38
|
+
import { resolveSessionWorkspaceRoot } from "../../shared/workspace/session-root.js";
|
|
38
39
|
import { buildWorkspaceFileTree } from "../../shared/workspace/file-tree.js";
|
|
39
40
|
import { reportSetupProgress } from "../../shared/status.js";
|
|
40
41
|
import { resolveEnvironment, type EnvironmentResult } from "./environment.js";
|
|
@@ -49,7 +50,7 @@ import { getDefaultModel } from "../../shared/model-registry.js";
|
|
|
49
50
|
import { buildChatModel } from "../../shared/model-client.js";
|
|
50
51
|
import {
|
|
51
52
|
loadArtifactStorageConfig,
|
|
52
|
-
|
|
53
|
+
resolveUsableArtifactStorage,
|
|
53
54
|
type ArtifactStorage,
|
|
54
55
|
} from "../../shared/artifact-storage.js";
|
|
55
56
|
import {
|
|
@@ -201,20 +202,14 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
201
202
|
await reportSetupProgress(client, executionId, "Resolving environment…");
|
|
202
203
|
const envResult: EnvironmentResult = await resolveEnvironment(client, executionId);
|
|
203
204
|
|
|
204
|
-
// Step 6:
|
|
205
|
-
// `
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch (storageErr) {
|
|
213
|
-
console.warn(
|
|
214
|
-
`[setup] artifact storage unavailable — capture degrades to the deny-gate ` +
|
|
215
|
-
`and tool-output offload is disabled: execution=${executionId}, error=${storageErr}`,
|
|
216
|
-
);
|
|
217
|
-
}
|
|
205
|
+
// Step 6: Resolve a usable artifact store (shared with the Cursor harness so
|
|
206
|
+
// both degrade identically). Returns `undefined` — never throws — when there
|
|
207
|
+
// is no working substrate: a proxy misconfig OR an unwritable local path
|
|
208
|
+
// (which `createArtifactStorage` cannot detect, since it just holds a path).
|
|
209
|
+
// An absent store flips capture mode off (deny-gate fallback below) and
|
|
210
|
+
// disables offload, instead of flowing writes then crashing at the boundary.
|
|
211
|
+
const artifactStorage: ArtifactStorage | undefined =
|
|
212
|
+
await resolveUsableArtifactStorage(loadArtifactStorageConfig(config), { executionId });
|
|
218
213
|
|
|
219
214
|
// Step 7: Provision workspace
|
|
220
215
|
await reportSetupProgress(client, executionId, "Initializing workspace…");
|
|
@@ -372,6 +367,8 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
372
367
|
workspaceFileRefs: execution.spec!.workspaceFileRefs || [],
|
|
373
368
|
workspaceRoot: workspaceBackend.rootDir,
|
|
374
369
|
injectedFiles,
|
|
370
|
+
interactionMode: execution.spec!.executionConfig?.interactionMode,
|
|
371
|
+
buildFromPlan: execution.spec!.executionConfig?.buildFromPlan,
|
|
375
372
|
});
|
|
376
373
|
|
|
377
374
|
// Step 9: Construct the LLM model. Resolution to the provider API id
|
|
@@ -631,6 +628,11 @@ export async function performSetup(deps: SetupDependencies): Promise<SetupResult
|
|
|
631
628
|
|
|
632
629
|
/**
|
|
633
630
|
* Provision workspace from session workspace entries.
|
|
631
|
+
*
|
|
632
|
+
* A session with no entries gets its own empty per-session directory (see
|
|
633
|
+
* shared/workspace/session-root.ts) — never the shared root, which would
|
|
634
|
+
* leak other sessions' files into it. Mirrors the Cursor harness's
|
|
635
|
+
* provisionCursorWorkspace exactly.
|
|
634
636
|
*/
|
|
635
637
|
async function provisionWorkspace(
|
|
636
638
|
config: Config,
|
|
@@ -639,13 +641,19 @@ async function provisionWorkspace(
|
|
|
639
641
|
sessionId: string,
|
|
640
642
|
): Promise<{ workspaceBackend: WorkspaceBackend; provisionResults: ProvisionResult[] }> {
|
|
641
643
|
const platformDir = await ensurePlatformDir(sessionId);
|
|
642
|
-
const workspaceBackend = new LocalWorkspaceBackend(config.workspaceRootDir, platformDir);
|
|
643
644
|
|
|
644
645
|
const workspaceEntries = session.spec!.workspaceEntries || [];
|
|
645
646
|
if (workspaceEntries.length === 0) {
|
|
646
|
-
|
|
647
|
+
const sessionRoot = await resolveSessionWorkspaceRoot(
|
|
648
|
+
config.workspaceRootDir, workspaceEntries, sessionId,
|
|
649
|
+
);
|
|
650
|
+
return {
|
|
651
|
+
workspaceBackend: new LocalWorkspaceBackend(sessionRoot, platformDir),
|
|
652
|
+
provisionResults: [],
|
|
653
|
+
};
|
|
647
654
|
}
|
|
648
655
|
|
|
656
|
+
const workspaceBackend = new LocalWorkspaceBackend(config.workspaceRootDir, platformDir);
|
|
649
657
|
const provisioner = new WorkspaceProvisioner();
|
|
650
658
|
const provisionResults = await provisioner.provisionAll(
|
|
651
659
|
workspaceEntries.map(entry => ({
|
|
@@ -118,10 +118,15 @@ export async function streamExecutionV3(
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
statusBuilder.syncSubAgentExecutions();
|
|
121
|
+
// Mid-run live capture (DD-32): attach file_change_progress to the live
|
|
122
|
+
// status before it is persisted. Injected so this loop stays ignorant of
|
|
123
|
+
// file-review specifics; a no-op when the hook is absent or nothing changed.
|
|
124
|
+
const statusToPersist = statusBuilder.currentStatus;
|
|
125
|
+
await deps.beforePersist?.(statusToPersist);
|
|
121
126
|
const signal = await persistStatus(
|
|
122
127
|
client,
|
|
123
128
|
executionId,
|
|
124
|
-
|
|
129
|
+
statusToPersist,
|
|
125
130
|
{ offload, retry: retryOptions },
|
|
126
131
|
);
|
|
127
132
|
scheduler.markUpdateSent(eventsProcessed);
|
|
@@ -83,6 +83,13 @@ export interface StreamDependencies {
|
|
|
83
83
|
readonly approvalProvider?: ApprovalPolicyProvider;
|
|
84
84
|
/** Streaming protocol version. Defaults to "v2" if unset. */
|
|
85
85
|
readonly streamVersion?: "v2" | "v3";
|
|
86
|
+
/**
|
|
87
|
+
* Optional hook invoked with the live status just before each scheduled
|
|
88
|
+
* persist. Used by mid-run live capture (DD-32) to attach file_change_progress.
|
|
89
|
+
* Kept as an injected callback (closing over the turn's baseline tree / change
|
|
90
|
+
* set id in index.ts) so this loop stays ignorant of file-review specifics.
|
|
91
|
+
*/
|
|
92
|
+
readonly beforePersist?: (status: AgentExecutionStatus) => Promise<void>;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
export interface StreamResult {
|
|
@@ -189,10 +196,14 @@ async function streamExecutionV2(
|
|
|
189
196
|
statusBuilder.clearForceFlag();
|
|
190
197
|
}
|
|
191
198
|
|
|
199
|
+
// Mid-run live capture (DD-32): attach file_change_progress to the live
|
|
200
|
+
// status before persisting (see streaming-v3.ts for the rationale).
|
|
201
|
+
const statusToPersist = statusBuilder.currentStatus;
|
|
202
|
+
await deps.beforePersist?.(statusToPersist);
|
|
192
203
|
const signal = await persistStatus(
|
|
193
204
|
client,
|
|
194
205
|
executionId,
|
|
195
|
-
|
|
206
|
+
statusToPersist,
|
|
196
207
|
{ offload, retry: retryOptions },
|
|
197
208
|
);
|
|
198
209
|
scheduler.markUpdateSent(eventsProcessed);
|
package/src/config.ts
CHANGED
|
@@ -37,6 +37,12 @@ import { homedir, tmpdir } from "node:os";
|
|
|
37
37
|
*/
|
|
38
38
|
export const DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS = 180_000;
|
|
39
39
|
|
|
40
|
+
// Re-exported so the runner/manager options mappers default the lock-wait
|
|
41
|
+
// bound from the same constant the lock module owns (no drift across the
|
|
42
|
+
// three construction sites — mirrors DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS).
|
|
43
|
+
export { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-lock.js";
|
|
44
|
+
import { DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS } from "./shared/workspace/workspace-lock.js";
|
|
45
|
+
|
|
40
46
|
export interface Config {
|
|
41
47
|
readonly taskQueue: string;
|
|
42
48
|
readonly temporalAddress: string;
|
|
@@ -83,6 +89,12 @@ export interface Config {
|
|
|
83
89
|
* heartbeatTimeout (process liveness) and the 30s keep-alive heartbeat.
|
|
84
90
|
*/
|
|
85
91
|
readonly cursorStreamStallTimeoutMs: number;
|
|
92
|
+
/**
|
|
93
|
+
* Max wait (milliseconds) for the per-workspace turn lock before failing
|
|
94
|
+
* the execution with a "workspace is in use by another session" error.
|
|
95
|
+
* See shared/workspace/workspace-lock.ts for the serialization model.
|
|
96
|
+
*/
|
|
97
|
+
readonly workspaceLockTimeoutMs: number;
|
|
86
98
|
/** Shared mutable token reference for dynamic token updates (manager mode). */
|
|
87
99
|
readonly stigmerTokenRef?: { current: string | null };
|
|
88
100
|
}
|
|
@@ -145,6 +157,10 @@ export function loadConfig(): Config {
|
|
|
145
157
|
? parseInt(process.env.CURSOR_STREAM_STALL_TIMEOUT_MS, 10)
|
|
146
158
|
: DEFAULT_CURSOR_STREAM_STALL_TIMEOUT_MS;
|
|
147
159
|
|
|
160
|
+
const workspaceLockTimeoutMs = process.env.WORKSPACE_LOCK_TIMEOUT_MS
|
|
161
|
+
? parseInt(process.env.WORKSPACE_LOCK_TIMEOUT_MS, 10)
|
|
162
|
+
: DEFAULT_WORKSPACE_LOCK_TIMEOUT_MS;
|
|
163
|
+
|
|
148
164
|
return {
|
|
149
165
|
taskQueue,
|
|
150
166
|
temporalAddress,
|
|
@@ -162,6 +178,7 @@ export function loadConfig(): Config {
|
|
|
162
178
|
checkpointerProxyEndpoint,
|
|
163
179
|
primaryModel,
|
|
164
180
|
cursorStreamStallTimeoutMs,
|
|
181
|
+
workspaceLockTimeoutMs,
|
|
165
182
|
};
|
|
166
183
|
}
|
|
167
184
|
|