@stigmer/runner 3.1.2 → 3.1.4

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.
Files changed (127) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/attachment-resolver.d.ts +1 -1
  3. package/dist/activities/execute-cursor/attachment-resolver.js +1 -1
  4. package/dist/activities/execute-cursor/capture-flow.d.ts +29 -0
  5. package/dist/activities/execute-cursor/capture-flow.js +59 -0
  6. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  7. package/dist/activities/execute-cursor/command-provenance.d.ts +11 -25
  8. package/dist/activities/execute-cursor/command-provenance.js +25 -115
  9. package/dist/activities/execute-cursor/command-provenance.js.map +1 -1
  10. package/dist/activities/execute-cursor/index.js +358 -557
  11. package/dist/activities/execute-cursor/index.js.map +1 -1
  12. package/dist/activities/execute-cursor/todo-tracker.d.ts +6 -1
  13. package/dist/activities/execute-cursor/todo-tracker.js +15 -43
  14. package/dist/activities/execute-cursor/todo-tracker.js.map +1 -1
  15. package/dist/activities/execute-cursor/turn-boundary.d.ts +108 -0
  16. package/dist/activities/execute-cursor/turn-boundary.js +163 -0
  17. package/dist/activities/execute-cursor/turn-boundary.js.map +1 -0
  18. package/dist/activities/execute-cursor/turn-stream.d.ts +141 -0
  19. package/dist/activities/execute-cursor/turn-stream.js +249 -0
  20. package/dist/activities/execute-cursor/turn-stream.js.map +1 -0
  21. package/dist/activities/execute-deep-agent/command-provenance.d.ts +61 -0
  22. package/dist/activities/execute-deep-agent/command-provenance.js +72 -0
  23. package/dist/activities/execute-deep-agent/command-provenance.js.map +1 -0
  24. package/dist/activities/execute-deep-agent/index.js +73 -4
  25. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  26. package/dist/activities/execute-deep-agent/status-builder.js +8 -1
  27. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  28. package/dist/activities/execute-deep-agent/streaming-v3.js +6 -1
  29. package/dist/activities/execute-deep-agent/streaming-v3.js.map +1 -1
  30. package/dist/activities/execute-deep-agent/streaming.d.ts +7 -0
  31. package/dist/activities/execute-deep-agent/streaming.js +5 -1
  32. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  33. package/dist/activities/execute-deep-agent/v3-status-builder.js +12 -1
  34. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  35. package/dist/otel.js +10 -0
  36. package/dist/otel.js.map +1 -1
  37. package/dist/shared/filereview/capture.d.ts +18 -1
  38. package/dist/shared/filereview/capture.js +105 -28
  39. package/dist/shared/filereview/capture.js.map +1 -1
  40. package/dist/shared/filereview/cas-progress.d.ts +63 -0
  41. package/dist/shared/filereview/cas-progress.js +128 -0
  42. package/dist/shared/filereview/cas-progress.js.map +1 -0
  43. package/dist/shared/filereview/cas-substrate.d.ts +38 -0
  44. package/dist/shared/filereview/cas-substrate.js +48 -15
  45. package/dist/shared/filereview/cas-substrate.js.map +1 -1
  46. package/dist/shared/filereview/command-provenance.d.ts +93 -0
  47. package/dist/shared/filereview/command-provenance.js +132 -0
  48. package/dist/shared/filereview/command-provenance.js.map +1 -0
  49. package/dist/shared/filereview/events.d.ts +16 -0
  50. package/dist/shared/filereview/events.js +37 -0
  51. package/dist/shared/filereview/events.js.map +1 -1
  52. package/dist/shared/filereview/git-substrate.d.ts +48 -0
  53. package/dist/shared/filereview/git-substrate.js +78 -0
  54. package/dist/shared/filereview/git-substrate.js.map +1 -1
  55. package/dist/shared/filereview/index.d.ts +5 -2
  56. package/dist/shared/filereview/index.js +5 -2
  57. package/dist/shared/filereview/index.js.map +1 -1
  58. package/dist/shared/filereview/line-counts.d.ts +48 -0
  59. package/dist/shared/filereview/line-counts.js +72 -0
  60. package/dist/shared/filereview/line-counts.js.map +1 -0
  61. package/dist/shared/filereview/progress.d.ts +167 -0
  62. package/dist/shared/filereview/progress.js +196 -0
  63. package/dist/shared/filereview/progress.js.map +1 -0
  64. package/dist/shared/implement-plan-prompt.d.ts +6 -4
  65. package/dist/shared/implement-plan-prompt.js +38 -6
  66. package/dist/shared/implement-plan-prompt.js.map +1 -1
  67. package/dist/shared/plan-artifact.d.ts +59 -11
  68. package/dist/shared/plan-artifact.js +144 -16
  69. package/dist/shared/plan-artifact.js.map +1 -1
  70. package/dist/shared/plan-mode-prompt.d.ts +15 -4
  71. package/dist/shared/plan-mode-prompt.js +28 -5
  72. package/dist/shared/plan-mode-prompt.js.map +1 -1
  73. package/dist/shared/todos.d.ts +56 -0
  74. package/dist/shared/todos.js +98 -0
  75. package/dist/shared/todos.js.map +1 -0
  76. package/dist/shared/tool-row.d.ts +16 -0
  77. package/dist/shared/tool-row.js +31 -0
  78. package/dist/shared/tool-row.js.map +1 -1
  79. package/package.json +3 -2
  80. package/src/__tests__/otel-turn-span.test.ts +61 -0
  81. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +7 -0
  82. package/src/activities/execute-cursor/__tests__/delta-enricher.test.ts +68 -0
  83. package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +169 -0
  84. package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +280 -0
  85. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +349 -0
  86. package/src/activities/execute-cursor/attachment-resolver.ts +1 -1
  87. package/src/activities/execute-cursor/capture-flow.ts +78 -0
  88. package/src/activities/execute-cursor/command-provenance.ts +25 -120
  89. package/src/activities/execute-cursor/index.ts +419 -602
  90. package/src/activities/execute-cursor/todo-tracker.ts +17 -59
  91. package/src/activities/execute-cursor/turn-boundary.ts +274 -0
  92. package/src/activities/execute-cursor/turn-stream.ts +418 -0
  93. package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +252 -0
  94. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +11 -0
  95. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +78 -0
  96. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +105 -1
  97. package/src/activities/execute-deep-agent/command-provenance.ts +102 -0
  98. package/src/activities/execute-deep-agent/index.ts +91 -4
  99. package/src/activities/execute-deep-agent/status-builder.ts +9 -0
  100. package/src/activities/execute-deep-agent/streaming-v3.ts +6 -1
  101. package/src/activities/execute-deep-agent/streaming.ts +12 -1
  102. package/src/activities/execute-deep-agent/v3-status-builder.ts +13 -0
  103. package/src/otel.ts +8 -0
  104. package/src/shared/__tests__/implement-plan-prompt.test.ts +40 -2
  105. package/src/shared/__tests__/plan-artifact.test.ts +142 -9
  106. package/src/shared/__tests__/todos.test.ts +216 -0
  107. package/src/shared/filereview/__tests__/capture.test.ts +182 -1
  108. package/src/shared/filereview/__tests__/cas-progress.test.ts +228 -0
  109. package/src/shared/filereview/__tests__/cas-substrate.test.ts +100 -0
  110. package/src/shared/filereview/__tests__/command-provenance.test.ts +252 -0
  111. package/src/shared/filereview/__tests__/events.test.ts +105 -0
  112. package/src/shared/filereview/__tests__/line-counts.test.ts +73 -0
  113. package/src/shared/filereview/__tests__/progress.test.ts +338 -0
  114. package/src/shared/filereview/capture.ts +134 -30
  115. package/src/shared/filereview/cas-progress.ts +170 -0
  116. package/src/shared/filereview/cas-substrate.ts +79 -18
  117. package/src/shared/filereview/command-provenance.ts +180 -0
  118. package/src/shared/filereview/events.ts +50 -0
  119. package/src/shared/filereview/git-substrate.ts +114 -0
  120. package/src/shared/filereview/index.ts +36 -1
  121. package/src/shared/filereview/line-counts.ts +83 -0
  122. package/src/shared/filereview/progress.ts +310 -0
  123. package/src/shared/implement-plan-prompt.ts +39 -8
  124. package/src/shared/plan-artifact.ts +152 -16
  125. package/src/shared/plan-mode-prompt.ts +28 -5
  126. package/src/shared/todos.ts +126 -0
  127. package/src/shared/tool-row.ts +34 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The Cursor harness's turn boundary — the single post-run pipeline that turns
3
+ * a finished agent run into the durable review surfaces:
4
+ *
5
+ * 1. read the denial ledger the preToolUse hook appended this turn;
6
+ * 2. derive the approved-command provenance (DD-28 auto-keep facts);
7
+ * 3. capture the turn's net file change set to the file_review ledger
8
+ * (CANDIDATE_CAPTURED) and stamp the flowed edit rows;
9
+ * 4. reconcile denied tool calls to WAITING_APPROVAL gate rows and redact the
10
+ * model's provisional post-denial narration.
11
+ *
12
+ * Extracted from the activity entry point (index.ts Phase 12) so it is directly
13
+ * unit-testable AND re-enterable: the poisoned-handle / transport-timeout
14
+ * recoveries re-run the agent with a fresh handle AFTER the primary boundary
15
+ * already ran, so their edits must flow through this exact pipeline again or
16
+ * they silently escape review (production case aex_01kws27q1e2esvkqjpvectttxf,
17
+ * where a Build-from-plan retry created a file with no review gate).
18
+ *
19
+ * Re-entry is safe by construction: a retry is only reachable when the primary
20
+ * boundary captured nothing (a captured change pauses the turn before
21
+ * run.wait() is ever consulted), `stampFlowedFileEditRows` skips already-stamped
22
+ * rows, and the denial ledger is per-turn append-only.
23
+ *
24
+ * The caller owns everything around the boundary: the stream epilogue
25
+ * (accumulator/enricher finalize), the WAITING_FOR_APPROVAL phase flip +
26
+ * persist, and the terminal result mapping.
27
+ */
28
+ import { collectSubAgentToolCallIds } from "../../shared/tool-row.js";
29
+ import { LocalWorkspaceBackend } from "../../shared/workspace/local-backend.js";
30
+ import { primaryToken, readDenialLedger } from "./approval-state.js";
31
+ import { deriveTurnCommandProvenance } from "./command-provenance.js";
32
+ import { captureTurnToLedger } from "./capture-flow.js";
33
+ import { clearProvisionalPostDenialNarration, reconcileDeniedToolCalls, } from "./message-translator.js";
34
+ // How long the boundary waits for the first-denial-stop's run.cancel() to
35
+ // settle before reading the final denial ledger and capturing the turn's tree.
36
+ // Long enough for the SDK's normal teardown, short enough that a wedged cancel
37
+ // cannot noticeably delay the approval pause the user is already waiting on.
38
+ const FIRST_DENIAL_CANCEL_TIMEOUT_MS = 5_000;
39
+ /**
40
+ * Run the turn boundary: author this turn's change set to the file_review
41
+ * ledger and overlay the hook's denials as approval gates. Mutates
42
+ * `opts.status` in place and reports whether the turn must pause.
43
+ *
44
+ * The hook records each denial to the ledger; we mark the corresponding tool
45
+ * calls WAITING_APPROVAL. The backend projects pending_approvals from that
46
+ * tool-call status (PendingApprovalComputer), so — exactly like the native
47
+ * harness — the approval surface is driven entirely by tool-call status. We
48
+ * deliberately do NOT set status.pendingApprovals here: any value would be
49
+ * discarded by the backend's recompute on the next updateStatus.
50
+ */
51
+ export async function runTurnBoundary(opts) {
52
+ const { status, executionId, changeSetId, hitlDir, captureMode, baselineTree, primaryWorkspaceDir, gitWorkspace, turnStartMessageIndex, approvalGrants, globalBypass, seededSubAgents, artifactStorage, mergedPolicies, denialCancelSettled, } = opts;
53
+ // The timebox keeps a wedged cancel from hanging the pause; the reconcile
54
+ // trims below remain the backstop for that degraded case.
55
+ if (denialCancelSettled) {
56
+ await Promise.race([
57
+ denialCancelSettled,
58
+ new Promise((resolve) => {
59
+ const timer = setTimeout(resolve, FIRST_DENIAL_CANCEL_TIMEOUT_MS);
60
+ timer.unref();
61
+ }),
62
+ ]);
63
+ }
64
+ const deniedLedger = await readDenialLedger(hitlDir ?? "");
65
+ // Capture mode: author the net change set to the file_review ledger as the
66
+ // CANDIDATE_CAPTURED event (projected server-side to a file_change_set
67
+ // AWAITING_REVIEW — the single review surface). The runner-owned gate files
68
+ // are excluded from the capture. The agent's edits are LEFT applied on the
69
+ // working tree (Cursor parity — the user reviews the real change; nothing is
70
+ // committed and the next turn is blocked until approval, and a reject snaps
71
+ // each file back on resume). Runs BEFORE the denial reconcile so a denied
72
+ // (gitignored) write stays on the deny-gate path while every flowed edit is
73
+ // captured to the ledger.
74
+ let capturedChangeCount = 0;
75
+ if (captureMode && baselineTree !== undefined && primaryWorkspaceDir) {
76
+ const deniedTokens = new Set(deniedLedger.map((e) => e.token));
77
+ // Approved-command turn facts (DD-28): when every mutation-capable call
78
+ // this turn was a consented shell command, attach the provenance so the
79
+ // backend can verify the cited consent rows and auto-keep the set instead
80
+ // of arming a second gate. Fail-closed: any non-qualifying turn attaches
81
+ // nothing and reviews manually exactly as before.
82
+ const commandProvenance = deriveTurnCommandProvenance({
83
+ messages: status.messages,
84
+ turnStartIndex: turnStartMessageIndex,
85
+ deniedTokens,
86
+ grantTokenToConsentId: new Map((approvalGrants ?? []).map((g) => [
87
+ primaryToken(g.key, g.salient, g.contentDigest),
88
+ g.sourceToolCallId,
89
+ ])),
90
+ globalBypass,
91
+ });
92
+ if (commandProvenance) {
93
+ console.log(`ExecuteCursor capture: turn qualifies for approved-command auto-keep ` +
94
+ `(consent rows: ${commandProvenance.consentToolCallIds.join(",") || "(auto_approve_all)"}); ` +
95
+ `attaching provenance to candidate (execution=${executionId})`);
96
+ }
97
+ const captured = await captureTurnToLedger({
98
+ status,
99
+ gitRoot: primaryWorkspaceDir,
100
+ executionId,
101
+ changeSetId,
102
+ baselineTree,
103
+ messages: status.messages,
104
+ deniedTokens,
105
+ commandProvenance,
106
+ // Scope sub-agent row stamping to this turn: the seeded prior sub-agents
107
+ // (cloned in on resume) are the "before this turn" rows to skip.
108
+ priorSubAgentToolCallIds: collectSubAgentToolCallIds(seededSubAgents),
109
+ // The CAS half: read the sidecar the hook staged this turn and compose it
110
+ // into the change set. hitlDir + storage are present when captureIgnored
111
+ // was on (a git tree's gitignored writes, or ALL writes in a non-git
112
+ // workspace). In a git tree this composes with the git diff (HYBRID); in a
113
+ // non-git workspace it IS the whole change set (CAS-only).
114
+ hitlDir,
115
+ storage: artifactStorage,
116
+ gitWorkspace,
117
+ });
118
+ capturedChangeCount = captured.length;
119
+ if (capturedChangeCount > 0) {
120
+ console.log(`ExecuteCursor capture: ${capturedChangeCount} file change(s) authored to the ` +
121
+ `file_review ledger (change_set=${changeSetId}), working tree left applied ` +
122
+ `for review (execution=${executionId})`);
123
+ }
124
+ }
125
+ // The gate reads each denied file's pre-edit `before` from the workspace the
126
+ // runner is co-located with (local FS for OSS; the sandbox in cloud), so a
127
+ // whole-file rewrite gate renders a true before/after diff. The tool was
128
+ // DENIED, so disk still holds the old content. User files are never platform
129
+ // paths, so no platformDir routing is needed here.
130
+ const gateWorkspaceBackend = new LocalWorkspaceBackend(primaryWorkspaceDir);
131
+ const deniedToolCalls = await reconcileDeniedToolCalls(status.messages, deniedLedger, mergedPolicies, gateWorkspaceBackend);
132
+ // Observability: a synthesized placeholder (id `approval:*`) means a denial
133
+ // correlated to NO streamed tool call in either the exact or the normalized
134
+ // pass. After the normalized-path fallback this should be ~0; a non-zero rate
135
+ // is the early-warning signal of a NEW identity drift (the gate would then
136
+ // show "No preview available" with no diff). Logged, not thrown — the
137
+ // synthesized gate still safely surfaces the approval.
138
+ const synthesizedGateCount = deniedToolCalls.filter((tc) => tc.id.startsWith("approval:")).length;
139
+ if (synthesizedGateCount > 0) {
140
+ console.warn(`ExecuteCursor reconcile synthesized ${synthesizedGateCount} placeholder gate(s) ` +
141
+ `with no correlated stream call (execution=${executionId}); ` +
142
+ `possible hook/stream identity drift — gate(s) will lack a diff`);
143
+ }
144
+ if (deniedToolCalls.length > 0) {
145
+ // Deterministic clean-pause: a turn that pauses for approval must read as
146
+ // the same shape the native harness produces — pre-tool text + the gated
147
+ // tool calls — never the model's provisional reaction to Cursor's deny
148
+ // (e.g. "blocked by a hook; enable it in your Cursor settings"). We blank
149
+ // that reaction in place (keeping the message count, so the finalize stays
150
+ // append-only) rather than removing it. See
151
+ // clearProvisionalPostDenialNarration for the full rationale.
152
+ const redactedNarration = clearProvisionalPostDenialNarration(status.messages, deniedToolCalls);
153
+ if (redactedNarration.length > 0) {
154
+ console.log(`ExecuteCursor redacted ${redactedNarration.length} provisional post-denial narration message(s) before pausing for approval`);
155
+ }
156
+ }
157
+ return {
158
+ waiting: deniedToolCalls.length > 0 || capturedChangeCount > 0,
159
+ capturedChangeCount,
160
+ deniedToolCallCount: deniedToolCalls.length,
161
+ };
162
+ }
163
+ //# sourceMappingURL=turn-boundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-boundary.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/turn-boundary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The Cursor harness's stream phase — the single per-event consumption loop that
3
+ * turns a live Cursor SDK run into the streamed transcript, mid-run progress, and
4
+ * the flags that decide how the turn ends.
5
+ *
6
+ * Extracted from the activity entry point (index.ts) so that BOTH the primary
7
+ * turn and the two recovery retries (poisoned-handle / transport-timeout) drive
8
+ * the exact same code. The retries previously re-implemented a stripped "bare"
9
+ * loop that dropped live persist, DD-32/DD-33 mid-run progress, sub-agent
10
+ * tracking, the first-denial early stop, and correct pause/stall handling — so a
11
+ * retry froze the UI and mis-handled a mid-retry pause. One loop removes that
12
+ * drift by construction.
13
+ *
14
+ * This mirrors the deep-agent harness's `streamExecution` seam
15
+ * (execute-deep-agent/streaming.ts): injected `heartbeat`/`isCancelled` so it
16
+ * runs without Temporal, a structural `StreamableRun` so it runs without the live
17
+ * SDK, and a result (the terminal reason) instead of internal terminal mapping.
18
+ * The one deliberate difference: this consumer REPORTS why the stream ended and
19
+ * leaves the terminal MAPPING to the activity, because Cursor's mapping is
20
+ * entangled with worker-shutdown-vs-pause disambiguation, the turn boundary,
21
+ * structured output, and recovery retries — all of which must stay in index.ts.
22
+ */
23
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
24
+ import { ExecutionControlSignal } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
25
+ import type { SDKMessage, InteractionUpdate } from "@cursor/sdk";
26
+ import { StallTimeoutError, type StallWatchdog } from "../../shared/stall-watchdog.js";
27
+ import { type ProgressCaptureState, type ProgressSubstrate } from "../../shared/filereview/progress.js";
28
+ import type { MessageAccumulator } from "./message-translator.js";
29
+ import type { DeltaEnricher } from "./delta-enricher.js";
30
+ import type { TodoTracker } from "./todo-tracker.js";
31
+ import type { StreamingUpdateScheduler } from "../../shared/streaming-scheduler.js";
32
+ import type { UsageAccumulator } from "./usage-accumulator.js";
33
+ import type { createCursorEventRecorder } from "./cursor-event-recorder.js";
34
+ /**
35
+ * The subset of the Cursor SDK `Run` the stream phase consumes. Kept structural
36
+ * so the loop is unit-testable with a mock run — no live SDK, no Temporal.
37
+ */
38
+ export interface StreamableRun {
39
+ stream(): AsyncIterable<SDKMessage>;
40
+ supports?(feature: string): boolean;
41
+ cancel(): Promise<void>;
42
+ }
43
+ export type CursorTurnEventRecorder = ReturnType<typeof createCursorEventRecorder>;
44
+ /**
45
+ * Why the turn's stream ended. The activity maps each reason to a terminal
46
+ * outcome (see resolvePreBoundaryTerminal in index.ts): `completed` and
47
+ * `first-denial` proceed to the turn boundary; the rest are pre-boundary
48
+ * terminals that return or throw.
49
+ */
50
+ export type TurnStreamReason = "completed" | "paused" | "stalled" | "first-denial" | "platform-stop";
51
+ /**
52
+ * Single owner for every flag the turn's stream produces. Before this, these
53
+ * eleven fields lived as scattered `let`s co-written by FOUR concurrent
54
+ * producers — the SDK `onDelta`, the fs denial-watcher, the stall-watchdog, and
55
+ * the loop — the classic "who writes this field?" hazard. Consolidating them into
56
+ * one object (created once per turn, shared by every producer, the epilogue, and
57
+ * the outer catch/finally) makes ownership explicit and lets the primary turn and
58
+ * both recovery retries drive the identical stream code.
59
+ *
60
+ * Field ownership is annotated per field: exactly one primary writer, plus any
61
+ * documented producer/consumer handshakes.
62
+ */
63
+ export interface TurnStreamState {
64
+ /** Written by: onDelta (heartbeat cancel) + the loop's cancellation check. Read by: the loop, epilogue, outer catch. */
65
+ pauseDetected: boolean;
66
+ /** Written by: the stall-watchdog callback. Read by: the loop + epilogue. */
67
+ stallDetected: boolean;
68
+ stallError: StallTimeoutError | undefined;
69
+ /** Written by: the loop (on the first ledger entry). Read by: the loop, epilogue, settleRetryTurn. */
70
+ firstDenialDetected: boolean;
71
+ /** Written by: the fs denial-watcher (→ true) + the loop (→ false after a read). Read by: the loop. */
72
+ denialLedgerDirty: boolean;
73
+ /** Written by: the loop (the first-denial run.cancel()). Read by: the turn boundary. */
74
+ denialCancelSettled: Promise<void> | undefined;
75
+ /** Written by: the loop (a platform STOP signal from persist). Read by: the loop + epilogue. */
76
+ platformStopSignaled: boolean;
77
+ /** Written by: the loop (a stream ERROR status event) + the retry setup (reset). Read by: error classification. */
78
+ streamErrorMessage: string | undefined;
79
+ /** Written by: the loop (each tool_call). Read by: the stall-watchdog (message enrichment). */
80
+ lastToolName: string | undefined;
81
+ /** Written by: the loop. Read by: the event recorder, the persist cadence, and logs. */
82
+ eventCount: number;
83
+ /** Written + read by: onDelta (log the first turn's context attribution exactly once). */
84
+ firstTurnAttributionLogged: boolean;
85
+ /** The stall watchdog for the CURRENT stream, armed by consumeCursorTurnStream; held so onDelta can reset it and the activity's finally can stop it. */
86
+ stallWatchdog: StallWatchdog | undefined;
87
+ }
88
+ export declare function newTurnStreamState(): TurnStreamState;
89
+ /**
90
+ * The subset of collaborators the shared onDelta needs. Broken out from
91
+ * CursorTurnStreamDeps because the primary send happens BEFORE the accumulator
92
+ * exists — onDelta only touches usage, the enricher, the heartbeat, and state.
93
+ */
94
+ export interface TurnOnDeltaDeps {
95
+ readonly usageAccumulator: UsageAccumulator;
96
+ readonly deltaEnricher: DeltaEnricher;
97
+ /** Temporal heartbeat. Injected so the callback is testable without Temporal. */
98
+ readonly heartbeat: () => void;
99
+ readonly promptEstimatedTokens: number;
100
+ readonly executionId: string;
101
+ readonly state: TurnStreamState;
102
+ }
103
+ export interface CursorTurnStreamDeps extends TurnOnDeltaDeps {
104
+ readonly status: AgentExecutionStatus;
105
+ readonly accumulator: MessageAccumulator;
106
+ readonly todoTracker: TodoTracker;
107
+ readonly eventRecorder: CursorTurnEventRecorder | undefined;
108
+ readonly scheduler: StreamingUpdateScheduler;
109
+ readonly progressSubstrate: ProgressSubstrate | undefined;
110
+ readonly progressState: ProgressCaptureState;
111
+ readonly changeSetId: string;
112
+ /** Session HITL dir holding the denial ledger; undefined → no gate installed → no first-denial stop. */
113
+ readonly hitlDir: string | undefined;
114
+ readonly stallTimeoutMs: number;
115
+ /** The single size-bounded persist chokepoint. Returns the platform control signal. */
116
+ readonly persist: (status: AgentExecutionStatus) => Promise<ExecutionControlSignal>;
117
+ /** Temporal cancellation check. Injected so the loop is testable without Temporal. */
118
+ readonly isCancelled: () => boolean;
119
+ }
120
+ /**
121
+ * Build the shared onDelta callback. The Cursor SDK's fine-grained delta channel
122
+ * carries token usage, live shell output, and precise tool-call timings; it also
123
+ * fires far more often than discrete stream events, so it is where the stall
124
+ * timer is reset during a long model generation. A heartbeat CancelledFailure
125
+ * here flags the pause so the loop and epilogue treat it as a user pause (the
126
+ * bare retry loops swallowed this — the cause of the mid-retry mislabel).
127
+ */
128
+ export declare function makeCursorTurnOnDelta(deps: TurnOnDeltaDeps): (event: {
129
+ update: InteractionUpdate;
130
+ }) => void;
131
+ /**
132
+ * Consume a Cursor SDK run to completion (or to a clean early stop), driving the
133
+ * transcript, todos, sub-agent tracking, live persist, and DD-32/DD-33 mid-run
134
+ * progress, and reporting WHY the stream ended.
135
+ *
136
+ * Arms + stops its own stall watchdog (stored on `state.stallWatchdog` so the
137
+ * shared onDelta can reset it and the activity's finally can stop it as a
138
+ * backstop). Mutates `deps.status` + `deps.state` in place. The caller owns the
139
+ * post-stream finalize and the terminal mapping.
140
+ */
141
+ export declare function consumeCursorTurnStream(run: StreamableRun, deps: CursorTurnStreamDeps): Promise<TurnStreamReason>;
@@ -0,0 +1,249 @@
1
+ /**
2
+ * The Cursor harness's stream phase — the single per-event consumption loop that
3
+ * turns a live Cursor SDK run into the streamed transcript, mid-run progress, and
4
+ * the flags that decide how the turn ends.
5
+ *
6
+ * Extracted from the activity entry point (index.ts) so that BOTH the primary
7
+ * turn and the two recovery retries (poisoned-handle / transport-timeout) drive
8
+ * the exact same code. The retries previously re-implemented a stripped "bare"
9
+ * loop that dropped live persist, DD-32/DD-33 mid-run progress, sub-agent
10
+ * tracking, the first-denial early stop, and correct pause/stall handling — so a
11
+ * retry froze the UI and mis-handled a mid-retry pause. One loop removes that
12
+ * drift by construction.
13
+ *
14
+ * This mirrors the deep-agent harness's `streamExecution` seam
15
+ * (execute-deep-agent/streaming.ts): injected `heartbeat`/`isCancelled` so it
16
+ * runs without Temporal, a structural `StreamableRun` so it runs without the live
17
+ * SDK, and a result (the terminal reason) instead of internal terminal mapping.
18
+ * The one deliberate difference: this consumer REPORTS why the stream ended and
19
+ * leaves the terminal MAPPING to the activity, because Cursor's mapping is
20
+ * entangled with worker-shutdown-vs-pause disambiguation, the turn boundary,
21
+ * structured output, and recovery retries — all of which must stay in index.ts.
22
+ */
23
+ import { create } from "@bufbuild/protobuf";
24
+ import { CancelledFailure } from "@temporalio/activity";
25
+ import { ExecutionControlSignal } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
26
+ import { StreamingUsageSummarySchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/usage_pb";
27
+ import { startStallWatchdog, StallTimeoutError, } from "../../shared/stall-watchdog.js";
28
+ import { shouldPersistStreamingStatus } from "./persist-decision.js";
29
+ import { readDenialLedger } from "./approval-state.js";
30
+ import { captureFileChangeProgress, } from "../../shared/filereview/progress.js";
31
+ export function newTurnStreamState() {
32
+ return {
33
+ pauseDetected: false,
34
+ stallDetected: false,
35
+ stallError: undefined,
36
+ firstDenialDetected: false,
37
+ denialLedgerDirty: false,
38
+ denialCancelSettled: undefined,
39
+ platformStopSignaled: false,
40
+ streamErrorMessage: undefined,
41
+ lastToolName: undefined,
42
+ eventCount: 0,
43
+ firstTurnAttributionLogged: false,
44
+ stallWatchdog: undefined,
45
+ };
46
+ }
47
+ /**
48
+ * Build the shared onDelta callback. The Cursor SDK's fine-grained delta channel
49
+ * carries token usage, live shell output, and precise tool-call timings; it also
50
+ * fires far more often than discrete stream events, so it is where the stall
51
+ * timer is reset during a long model generation. A heartbeat CancelledFailure
52
+ * here flags the pause so the loop and epilogue treat it as a user pause (the
53
+ * bare retry loops swallowed this — the cause of the mid-retry mislabel).
54
+ */
55
+ export function makeCursorTurnOnDelta(deps) {
56
+ const { usageAccumulator, deltaEnricher, heartbeat, promptEstimatedTokens, executionId, state } = deps;
57
+ return ({ update }) => {
58
+ // Reset the stall timer on the delta channel too: a long model generation
59
+ // emits token deltas but few discrete stream events, so resetting only in the
60
+ // stream loop would false-positive a stall.
61
+ state.stallWatchdog?.recordActivity();
62
+ if (update.type === "turn-ended" && update.usage) {
63
+ usageAccumulator.addTurn(update.usage);
64
+ if (!state.firstTurnAttributionLogged) {
65
+ state.firstTurnAttributionLogged = true;
66
+ const sdkInputTokens = update.usage.inputTokens ?? 0;
67
+ const cursorOverhead = Math.max(0, sdkInputTokens - promptEstimatedTokens);
68
+ console.log(`ExecuteCursor context attribution (first turn): execution=${executionId}, ` +
69
+ `sdkInputTokens=${sdkInputTokens}, stigmerPreamble=${promptEstimatedTokens}, ` +
70
+ `cursorOverhead=${cursorOverhead} (estimated)`);
71
+ }
72
+ }
73
+ deltaEnricher.processDelta(update);
74
+ try {
75
+ heartbeat();
76
+ }
77
+ catch (hbErr) {
78
+ if (hbErr instanceof CancelledFailure) {
79
+ state.pauseDetected = true;
80
+ return;
81
+ }
82
+ throw hbErr;
83
+ }
84
+ };
85
+ }
86
+ /**
87
+ * Consume a Cursor SDK run to completion (or to a clean early stop), driving the
88
+ * transcript, todos, sub-agent tracking, live persist, and DD-32/DD-33 mid-run
89
+ * progress, and reporting WHY the stream ended.
90
+ *
91
+ * Arms + stops its own stall watchdog (stored on `state.stallWatchdog` so the
92
+ * shared onDelta can reset it and the activity's finally can stop it as a
93
+ * backstop). Mutates `deps.status` + `deps.state` in place. The caller owns the
94
+ * post-stream finalize and the terminal mapping.
95
+ */
96
+ export async function consumeCursorTurnStream(run, deps) {
97
+ const { status, accumulator, todoTracker, deltaEnricher, eventRecorder, scheduler, usageAccumulator, progressSubstrate, progressState, changeSetId, hitlDir, executionId, stallTimeoutMs, persist, heartbeat, isCancelled, state, } = deps;
98
+ // Arm the stall watchdog now that the run exists. The activity's periodic
99
+ // heartbeat proves the process is alive, not that the agent is progressing: if
100
+ // the stream wedges (a tool call or model connection that never returns), no
101
+ // event/delta arrives and the turn would hang forever. On stall we end the run
102
+ // cleanly via the SDK's run.cancel() (guarded by supports("cancel")), which
103
+ // unblocks the for-await; the caller then reports EXECUTION_FAILED with a
104
+ // recognizable, actionable message.
105
+ state.stallWatchdog = startStallWatchdog(stallTimeoutMs, (idleMs) => {
106
+ state.stallDetected = true;
107
+ state.stallError = new StallTimeoutError(idleMs, state.lastToolName ? `last tool: ${state.lastToolName}` : undefined);
108
+ console.warn(`ExecuteCursor stall detected: execution=${executionId}, idleMs=${idleMs}, lastTool=${state.lastToolName ?? "none"}`);
109
+ if (run.supports?.("cancel")) {
110
+ void run.cancel().catch((cancelErr) => {
111
+ console.warn(`ExecuteCursor run.cancel() after stall failed (non-fatal): execution=${executionId}, ` +
112
+ `error=${cancelErr instanceof Error ? cancelErr.message : cancelErr}`);
113
+ });
114
+ }
115
+ });
116
+ try {
117
+ for await (const event of run.stream()) {
118
+ if (state.pauseDetected || isCancelled()) {
119
+ state.pauseDetected = true;
120
+ break;
121
+ }
122
+ if (state.stallDetected)
123
+ break;
124
+ // Progress: reset the stall timer on every stream event.
125
+ state.stallWatchdog.recordActivity();
126
+ if (event.type === "tool_call" && typeof event.name === "string") {
127
+ state.lastToolName = event.name;
128
+ }
129
+ eventRecorder?.record(event, state.eventCount);
130
+ accumulator.processEvent(event);
131
+ todoTracker.processEvent(event);
132
+ if (event.type === "tool_call" && event.name === "task") {
133
+ accumulator.trackSubAgentExecution(event);
134
+ }
135
+ // First-denial stop (HITL clean pause). In CAPTURE mode this fires only for
136
+ // an IRREVERSIBLE tool the hook still gates (shell, MCP, or a gitignored
137
+ // write/delete) — file edits flow freely and are captured at the turn
138
+ // boundary, so they never enter the ledger. In the deny-gate FALLBACK
139
+ // (non-git workspace) it fires for every gated file edit too. Either way:
140
+ // the preToolUse hook appends to the denial ledger the instant it gates a
141
+ // tool — before Cursor surfaces the failure to the model — and the fs
142
+ // watcher flips denialLedgerDirty the moment that write lands. Confirming
143
+ // the flag with a read on the very next event (of ANY type — thinking
144
+ // deltas arrive within milliseconds) ends the turn before the model's
145
+ // reaction can persist: waiting for the next tool_call event let the full
146
+ // post-denial reaction (thinking, narration, a workaround shell) stream and
147
+ // persist live (production case aex_01kwj07f7g23c3wp9sn8496z5g). The
148
+ // tool_call-event read stays as the backstop for platforms where fs.watch
149
+ // is unreliable; the current event was already accumulated above, so the
150
+ // anchor's own row is always present for the turn-boundary gate overlay.
151
+ if (!state.firstDenialDetected && hitlDir && (state.denialLedgerDirty || event.type === "tool_call")) {
152
+ state.denialLedgerDirty = false;
153
+ const denials = await readDenialLedger(hitlDir);
154
+ if (denials.length > 0) {
155
+ state.firstDenialDetected = true;
156
+ console.log(`ExecuteCursor first denial detected (${denials.length} ledger ` +
157
+ `entr${denials.length === 1 ? "y" : "ies"}); stopping turn to pause ` +
158
+ `cleanly for approval: execution=${executionId}`);
159
+ if (run.supports?.("cancel")) {
160
+ // Kept (not fire-and-forget): awaited timeboxed by the turn boundary
161
+ // so the ledger read and tree capture see a stopped agent.
162
+ state.denialCancelSettled = run.cancel().then(() => { }, (cancelErr) => {
163
+ console.warn(`ExecuteCursor run.cancel() after first denial failed (non-fatal): ` +
164
+ `execution=${executionId}, ` +
165
+ `error=${cancelErr instanceof Error ? cancelErr.message : cancelErr}`);
166
+ });
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ deltaEnricher.applyEnrichments(status.messages);
172
+ state.eventCount++;
173
+ if (event.type === "status") {
174
+ console.log(`ExecuteCursor stream status: execution=${executionId}, status=${JSON.stringify(event)}`);
175
+ const statusEvent = event;
176
+ if (statusEvent.status === "ERROR" && statusEvent.message) {
177
+ state.streamErrorMessage = statusEvent.message;
178
+ }
179
+ }
180
+ const shouldPersist = shouldPersistStreamingStatus({
181
+ deltaEnricherDirty: deltaEnricher.isDirty,
182
+ todosDirty: todoTracker.isDirty,
183
+ contentDirty: accumulator.isDirty,
184
+ }, scheduler, state.eventCount);
185
+ if (usageAccumulator.hasTurns) {
186
+ status.streamingUsage = create(StreamingUsageSummarySchema, usageAccumulator.snapshot());
187
+ }
188
+ if (shouldPersist) {
189
+ // Sync sub-agent executions into status before every persist so the live
190
+ // UI reflects delegation (including the IN_PROGRESS state) while the
191
+ // parent is still running — matching the native harness.
192
+ status.subAgentExecutions = accumulator.subAgentExecutions;
193
+ // Mid-run live capture (DD-32 / DD-33): attach the "N files changed so
194
+ // far" snapshot onto status.file_change_progress, throttled internally by
195
+ // the floor. Never authoritative — the turn-boundary candidate remains
196
+ // the reviewed diff.
197
+ if (progressSubstrate) {
198
+ await captureFileChangeProgress({
199
+ status,
200
+ changeSetId,
201
+ substrate: progressSubstrate,
202
+ state: progressState,
203
+ });
204
+ }
205
+ const signal = await persist(status);
206
+ deltaEnricher.markPersisted();
207
+ todoTracker.markPersisted();
208
+ accumulator.markPersisted();
209
+ scheduler.markUpdateSent(state.eventCount);
210
+ heartbeat();
211
+ if (signal === ExecutionControlSignal.STOP) {
212
+ state.platformStopSignaled = true;
213
+ console.warn(`ExecuteCursor platform stop signal received: execution=${executionId}`);
214
+ }
215
+ }
216
+ if (state.platformStopSignaled) {
217
+ console.log(`ExecuteCursor stopping stream due to platform stop signal: execution=${executionId}`);
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ catch (streamErr) {
223
+ // run.cancel() — from the stall watchdog or the first-denial stop — can make
224
+ // the stream iterator reject as it tears down; that is the expected teardown
225
+ // for both, so swallow it and fall through. Anything else is a genuine stream
226
+ // failure — rethrow it to the activity's error handler.
227
+ if (!state.stallDetected && !state.firstDenialDetected)
228
+ throw streamErr;
229
+ console.warn(`ExecuteCursor stream ended via cancel: execution=${executionId}, ` +
230
+ `stall=${state.stallDetected}, firstDenial=${state.firstDenialDetected}`);
231
+ }
232
+ finally {
233
+ state.stallWatchdog.stop();
234
+ }
235
+ // Report why the stream ended, in the same precedence the activity's epilogue
236
+ // applies: a stall (an EXECUTION_FAILED terminal) outranks everything; a
237
+ // platform stop and a first denial are distinct proceed/return outcomes; a
238
+ // pause is the fallback for a cancellation with no other cause.
239
+ if (state.stallDetected)
240
+ return "stalled";
241
+ if (state.platformStopSignaled)
242
+ return "platform-stop";
243
+ if (state.firstDenialDetected)
244
+ return "first-denial";
245
+ if (state.pauseDetected || isCancelled())
246
+ return "paused";
247
+ return "completed";
248
+ }
249
+ //# sourceMappingURL=turn-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-stream.js","sourceRoot":"","sources":["../../../src/activities/execute-cursor/turn-stream.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Approved-command turn provenance (DD-28) — the DEEP-AGENT (native) harness
3
+ * adapter over the shared qualification rule ({@link qualifyTurnCommandProvenance}).
4
+ *
5
+ * The deep-agent's consent model differs from Cursor's in exactly two ways, and
6
+ * this adapter is precisely those two differences:
7
+ *
8
+ * 1. IDENTITY turn scoping (not positional). Cursor scopes a turn by the message
9
+ * index its stream started at. The deep-agent cannot: an approved command
10
+ * executes IN PLACE at its seeded transcript position (it was proposed in a
11
+ * prior segment, seeded into this segment, and updated to COMPLETED by
12
+ * `StatusBuilder.findResumableSeededToolCall`). So "this turn's calls" are
13
+ * those whose id is absent from a pre-stream {@link collectSettledToolCallIds}
14
+ * snapshot, and "executed" is a COMPLETED status.
15
+ *
16
+ * 2. SAME-ROW direct consent (not a grant token). A gated deep-agent tool pauses
17
+ * on a LangGraph interrupt; SubmitApproval writes `approval_action` onto that
18
+ * same row, and the row keeps its id + `approval_action` when it executes on
19
+ * resume. So the executed command IS its own consent row — cite its own id.
20
+ * (The server's PreserveApprovalFields keeps that `approval_action` on the row
21
+ * across the status write regardless of its new status, so the backend's
22
+ * verification finds it.)
23
+ *
24
+ * A turn that delegated ANY sub-agent fails closed (DD-28 D1: "delegated zero
25
+ * sub-agents"). A sub-agent's writes fold into the parent turn's change set
26
+ * (DD-19) but are not attributable to a consented top-level command. The
27
+ * top-level `task` call (a SUBAGENT kind) already trips the shared rule's
28
+ * `!== SHELL` fail-closed; the explicit sub-agent-novelty guard below also covers
29
+ * the (currently-impossible under the replace-per-turn model) case of a sub-agent
30
+ * that mutates across a turn boundary without a fresh top-level `task` row.
31
+ *
32
+ * (Trust boundary + fail-closed contract: see the shared module.)
33
+ */
34
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
35
+ import type { TurnCommandProvenance } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
36
+ export interface DeepAgentCommandProvenanceInputs {
37
+ /** The status at capture time — top-level messages + sub-agent executions. */
38
+ readonly status: AgentExecutionStatus;
39
+ /**
40
+ * The top-level tool-call ids that had already SETTLED before this turn's
41
+ * stream (from {@link collectSettledToolCallIds}). A top-level call whose id is
42
+ * absent is this-turn's; the shared rule then keeps only the COMPLETED ones.
43
+ */
44
+ readonly priorSettledToolCallIds: ReadonlySet<string>;
45
+ /**
46
+ * The sub-agent tool-call ids that existed before this turn's stream (from
47
+ * {@link collectSubAgentToolCallIds}). Any growth means a sub-agent acted this
48
+ * turn → fail closed.
49
+ */
50
+ readonly priorSubAgentToolCallIds: ReadonlySet<string>;
51
+ /** True when the pre-armed spec.auto_approve_all bypassed the gate. */
52
+ readonly globalBypass: boolean;
53
+ }
54
+ /**
55
+ * Derive the {@link TurnCommandProvenance} for a deep-agent turn, or undefined
56
+ * when it does not qualify. Scopes this turn's top-level calls by id-novelty,
57
+ * resolves consent from the row's own server-authored `approval_action`, fails
58
+ * closed on any sub-agent activity, and delegates the DD-28 rule to
59
+ * {@link qualifyTurnCommandProvenance}.
60
+ */
61
+ export declare function deriveTurnCommandProvenance(inputs: DeepAgentCommandProvenanceInputs): TurnCommandProvenance | undefined;