@stigmer/runner 3.1.3 → 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.
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +26 -15
- package/dist/activities/execute-cursor/capture-flow.js +56 -18
- package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
- package/dist/activities/execute-cursor/command-provenance.d.ts +11 -25
- package/dist/activities/execute-cursor/command-provenance.js +25 -115
- package/dist/activities/execute-cursor/command-provenance.js.map +1 -1
- package/dist/activities/execute-cursor/index.js +296 -477
- package/dist/activities/execute-cursor/index.js.map +1 -1
- package/dist/activities/execute-cursor/todo-tracker.d.ts +6 -1
- package/dist/activities/execute-cursor/todo-tracker.js +15 -43
- package/dist/activities/execute-cursor/todo-tracker.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.d.ts +141 -0
- package/dist/activities/execute-cursor/turn-stream.js +249 -0
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -0
- package/dist/activities/execute-deep-agent/command-provenance.d.ts +61 -0
- package/dist/activities/execute-deep-agent/command-provenance.js +72 -0
- package/dist/activities/execute-deep-agent/command-provenance.js.map +1 -0
- package/dist/activities/execute-deep-agent/index.js +60 -18
- package/dist/activities/execute-deep-agent/index.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder.js +8 -1
- package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js +12 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/otel.js +10 -0
- package/dist/otel.js.map +1 -1
- package/dist/shared/filereview/cas-progress.d.ts +63 -0
- package/dist/shared/filereview/cas-progress.js +128 -0
- package/dist/shared/filereview/cas-progress.js.map +1 -0
- package/dist/shared/filereview/cas-substrate.d.ts +29 -0
- package/dist/shared/filereview/cas-substrate.js +46 -21
- package/dist/shared/filereview/cas-substrate.js.map +1 -1
- package/dist/shared/filereview/command-provenance.d.ts +93 -0
- package/dist/shared/filereview/command-provenance.js +132 -0
- package/dist/shared/filereview/command-provenance.js.map +1 -0
- package/dist/shared/filereview/git-substrate.d.ts +9 -3
- package/dist/shared/filereview/git-substrate.js.map +1 -1
- package/dist/shared/filereview/index.d.ts +4 -3
- package/dist/shared/filereview/index.js +3 -2
- package/dist/shared/filereview/index.js.map +1 -1
- package/dist/shared/filereview/progress.d.ts +105 -34
- package/dist/shared/filereview/progress.js +96 -34
- package/dist/shared/filereview/progress.js.map +1 -1
- package/dist/shared/plan-mode-prompt.d.ts +9 -0
- package/dist/shared/plan-mode-prompt.js +18 -0
- package/dist/shared/plan-mode-prompt.js.map +1 -1
- package/dist/shared/todos.d.ts +56 -0
- package/dist/shared/todos.js +98 -0
- package/dist/shared/todos.js.map +1 -0
- package/dist/shared/tool-row.d.ts +16 -0
- package/dist/shared/tool-row.js +31 -0
- package/dist/shared/tool-row.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/otel-turn-span.test.ts +61 -0
- package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +169 -0
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +349 -0
- package/src/activities/execute-cursor/capture-flow.ts +71 -25
- package/src/activities/execute-cursor/command-provenance.ts +25 -120
- package/src/activities/execute-cursor/index.ts +344 -504
- package/src/activities/execute-cursor/todo-tracker.ts +17 -59
- package/src/activities/execute-cursor/turn-stream.ts +418 -0
- package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +252 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +78 -0
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +105 -1
- package/src/activities/execute-deep-agent/command-provenance.ts +102 -0
- package/src/activities/execute-deep-agent/index.ts +74 -18
- package/src/activities/execute-deep-agent/status-builder.ts +9 -0
- package/src/activities/execute-deep-agent/v3-status-builder.ts +13 -0
- package/src/otel.ts +8 -0
- package/src/shared/__tests__/todos.test.ts +216 -0
- package/src/shared/filereview/__tests__/cas-progress.test.ts +228 -0
- package/src/shared/filereview/__tests__/cas-substrate.test.ts +66 -0
- package/src/shared/filereview/__tests__/command-provenance.test.ts +252 -0
- package/src/shared/filereview/__tests__/progress.test.ts +112 -10
- package/src/shared/filereview/cas-progress.ts +170 -0
- package/src/shared/filereview/cas-substrate.ts +69 -24
- package/src/shared/filereview/command-provenance.ts +180 -0
- package/src/shared/filereview/git-substrate.ts +9 -3
- package/src/shared/filereview/index.ts +15 -1
- package/src/shared/filereview/progress.ts +171 -47
- package/src/shared/plan-mode-prompt.ts +18 -0
- package/src/shared/todos.ts +126 -0
- package/src/shared/tool-row.ts +34 -0
|
@@ -36,9 +36,15 @@ import {
|
|
|
36
36
|
type CaptureResumeResult,
|
|
37
37
|
} from "../../shared/filereview/capture.js";
|
|
38
38
|
import {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
createGitProgressSubstrate,
|
|
40
|
+
createHybridProgressSubstrate,
|
|
41
|
+
type ProgressSubstrate,
|
|
41
42
|
} from "../../shared/filereview/progress.js";
|
|
43
|
+
import {
|
|
44
|
+
createCasProgressSubstrate,
|
|
45
|
+
type CasTouchedReader,
|
|
46
|
+
type CasTouchedSnapshot,
|
|
47
|
+
} from "../../shared/filereview/cas-progress.js";
|
|
42
48
|
import { hasCandidateCaptured } from "../../shared/filereview/events.js";
|
|
43
49
|
import { partitionIgnoredPathsBySecret } from "../../shared/filereview/secret-paths.js";
|
|
44
50
|
import { casBlobReader, type CasPathCapture } from "../../shared/filereview/cas-substrate.js";
|
|
@@ -202,31 +208,71 @@ export async function captureTurnToLedger(opts: {
|
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
211
|
+
* Choose the mid-run progress substrate for this turn's workspace shape (DD-32 /
|
|
212
|
+
* DD-33), returning `undefined` when there is nothing to observe (not capture
|
|
213
|
+
* mode, or no workspace). Built ONCE per turn — the substrate owns its own
|
|
214
|
+
* short-circuit cache across the loop's persists; the floor lives in the caller's
|
|
215
|
+
* `ProgressCaptureState`. The live count and the turn-boundary reviewed set agree
|
|
216
|
+
* by construction: the git slice binds the SAME `CURSOR_RUNNER_OWNED_PATHS`
|
|
217
|
+
* exclusion {@link captureTurnToLedger} uses, and the CAS slice reads the SAME
|
|
218
|
+
* hook sidecar ({@link readCasObservations}) the boundary reads.
|
|
219
|
+
*
|
|
220
|
+
* - git tree, no storage -> git substrate only (tracked-file `--numstat`);
|
|
221
|
+
* - git tree, storage (HYBRID) -> git + CAS (the gitignored writes the hook staged);
|
|
222
|
+
* - non-git workspace -> CAS only (every tool-mediated write is CAS-staged).
|
|
223
|
+
*
|
|
224
|
+
* The CAS slice needs the hook sidecar, which exists only when `captureIgnored`
|
|
225
|
+
* is on (storage configured). A non-git capture already required storage
|
|
226
|
+
* (`deriveCaptureMode`), so its sidecar is always present; a git tree with no
|
|
227
|
+
* storage simply gets the git-only substrate (parity with the pre-DD-33 behavior).
|
|
212
228
|
*/
|
|
213
|
-
export
|
|
214
|
-
readonly
|
|
215
|
-
readonly
|
|
229
|
+
export function buildCursorProgressSubstrate(opts: {
|
|
230
|
+
readonly captureMode: boolean;
|
|
231
|
+
readonly gitWorkspace: boolean;
|
|
232
|
+
readonly workspaceRoot: string | undefined;
|
|
233
|
+
readonly baselineTree: string | undefined;
|
|
216
234
|
readonly executionId: string;
|
|
217
|
-
readonly
|
|
218
|
-
readonly
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
readonly hitlDir: string | undefined;
|
|
236
|
+
readonly storage: ArtifactStorage | undefined;
|
|
237
|
+
}): ProgressSubstrate | undefined {
|
|
238
|
+
const { captureMode, gitWorkspace, workspaceRoot, baselineTree, executionId, hitlDir, storage } = opts;
|
|
239
|
+
if (!captureMode || !workspaceRoot) return undefined;
|
|
240
|
+
|
|
241
|
+
const casReader: CasTouchedReader | undefined =
|
|
242
|
+
hitlDir && storage ? createSidecarTouchedReader(hitlDir) : undefined;
|
|
243
|
+
|
|
244
|
+
if (gitWorkspace) {
|
|
245
|
+
if (!baselineTree) return undefined;
|
|
246
|
+
const git = createGitProgressSubstrate({
|
|
247
|
+
workspaceRoot,
|
|
248
|
+
executionId,
|
|
249
|
+
baselineTree,
|
|
250
|
+
excludePaths: CURSOR_RUNNER_OWNED_PATHS,
|
|
251
|
+
});
|
|
252
|
+
return casReader
|
|
253
|
+
? createHybridProgressSubstrate(git, createCasProgressSubstrate({ workspaceRoot, read: casReader }))
|
|
254
|
+
: git;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Non-git: every tool-mediated write is CAS-staged; there is no git slice.
|
|
258
|
+
return casReader ? createCasProgressSubstrate({ workspaceRoot, read: casReader }) : undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* A {@link CasTouchedReader} over the Cursor hook's on-disk sidecar. Each capture
|
|
263
|
+
* reads the current observations ({@link readCasObservations}) and maps them to
|
|
264
|
+
* the substrate's snapshot shape. Reads are bounded by the caller's capture floor
|
|
265
|
+
* (default 2s), and the sidecar is small (one small marker + before-blob per
|
|
266
|
+
* touched gitignored path), so re-reading per capture is cheap and always
|
|
267
|
+
* reflects sub-agent writes staged since the last one.
|
|
268
|
+
*/
|
|
269
|
+
function createSidecarTouchedReader(hitlDir: string): CasTouchedReader {
|
|
270
|
+
return async (): Promise<CasTouchedSnapshot> => {
|
|
271
|
+
const { captured, secretPaths } = await readCasObservations(hitlDir);
|
|
272
|
+
const before = new Map<string, Uint8Array | null>();
|
|
273
|
+
for (const c of captured) before.set(c.path, c.before);
|
|
274
|
+
return { before, blockedSecretPaths: new Set(secretPaths) };
|
|
275
|
+
};
|
|
230
276
|
}
|
|
231
277
|
|
|
232
278
|
/**
|
|
@@ -1,54 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Approved-command turn provenance (DD-28)
|
|
3
|
-
*
|
|
2
|
+
* Approved-command turn provenance (DD-28) — the CURSOR harness adapter over the
|
|
3
|
+
* shared qualification rule ({@link qualifyTurnCommandProvenance}).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* derives the facts here and attaches them to the CANDIDATE_CAPTURED event as
|
|
11
|
-
* {@link TurnCommandProvenance}.
|
|
5
|
+
* The Cursor harness scopes a turn POSITIONALLY (messages from the turn's first
|
|
6
|
+
* streamed index onward) and maps an executed command to its consent row through
|
|
7
|
+
* the reinvocation grant tokens the deny-gate minted. Both mappings are Cursor's
|
|
8
|
+
* own; the DD-28 rule they feed lives in `shared/filereview/command-provenance.ts`
|
|
9
|
+
* so the Cursor and deep-agent qualifications cannot drift.
|
|
12
10
|
*
|
|
13
|
-
*
|
|
14
|
-
* the same trust level as the captured bytes themselves. It never asserts
|
|
15
|
-
* CONSENT: `consentToolCallIds` merely POINT at transcript rows whose
|
|
16
|
-
* `approval_action` was authored by the server's SubmitApproval (and is
|
|
17
|
-
* preserved against runner writes) — the backend re-verifies every claimed row
|
|
18
|
-
* against that server-owned record before authoring the policy decision, so a
|
|
19
|
-
* runner cannot mint authorization it was never given.
|
|
20
|
-
*
|
|
21
|
-
* FAIL-CLOSED. Every uncertainty disqualifies (returns undefined → the set
|
|
22
|
-
* reviews manually, exactly as before DD-28): a file-tool call, an MCP tool, a
|
|
23
|
-
* sub-agent delegation, an unrecognized tool name, or an executed shell command
|
|
24
|
-
* with no provable consent source. Being conservative here costs only an extra
|
|
25
|
-
* review; being permissive would silently waive one.
|
|
11
|
+
* (Trust boundary + fail-closed contract: see the shared module.)
|
|
26
12
|
*/
|
|
27
13
|
|
|
28
|
-
import { create } from "@bufbuild/protobuf";
|
|
29
14
|
import type { AgentMessage } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
30
|
-
import { TurnCommandProvenanceSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
31
15
|
import type { TurnCommandProvenance } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
32
|
-
import {
|
|
33
|
-
import { classifyTool, toolApprovalCategory } from "../../shared/tool-kind.js";
|
|
34
|
-
import { isToolCallRowHidden } from "../../shared/tool-row.js";
|
|
16
|
+
import { qualifyTurnCommandProvenance } from "../../shared/filereview/command-provenance.js";
|
|
35
17
|
import { toolCallIdentityToken } from "./message-translator.js";
|
|
36
18
|
|
|
37
|
-
/**
|
|
38
|
-
* Tool kinds that can never mutate the workspace: their presence in a turn is
|
|
39
|
-
* irrelevant to change-set provenance. Everything OUTSIDE this set is either a
|
|
40
|
-
* consent-mapped shell command or a disqualifier — never silently ignored.
|
|
41
|
-
*/
|
|
42
|
-
const NON_MUTATING_KINDS: ReadonlySet<ToolKind> = new Set([
|
|
43
|
-
ToolKind.FILE_READ,
|
|
44
|
-
ToolKind.SEARCH,
|
|
45
|
-
ToolKind.LIST,
|
|
46
|
-
ToolKind.FETCH,
|
|
47
|
-
ToolKind.WEB_SEARCH,
|
|
48
|
-
ToolKind.THINK,
|
|
49
|
-
ToolKind.TODO,
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
19
|
export interface CommandProvenanceInputs {
|
|
53
20
|
/**
|
|
54
21
|
* The full transcript at capture time. Turn scoping is positional: messages
|
|
@@ -79,90 +46,28 @@ export interface CommandProvenanceInputs {
|
|
|
79
46
|
|
|
80
47
|
/**
|
|
81
48
|
* Derive the {@link TurnCommandProvenance} for the turn, or undefined when the
|
|
82
|
-
* turn does not qualify
|
|
83
|
-
*
|
|
84
|
-
*
|
|
49
|
+
* turn does not qualify. Scopes this turn's tool calls positionally, resolves
|
|
50
|
+
* consent through the deny-gate grant tokens, and delegates the DD-28 rule to
|
|
51
|
+
* {@link qualifyTurnCommandProvenance}.
|
|
85
52
|
*/
|
|
86
53
|
export function deriveTurnCommandProvenance(
|
|
87
54
|
inputs: CommandProvenanceInputs,
|
|
88
55
|
): TurnCommandProvenance | undefined {
|
|
89
56
|
const { messages, turnStartIndex, deniedTokens, grantTokenToConsentId, globalBypass } = inputs;
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
for (let i = turnStartIndex; i < messages.length; i++) {
|
|
96
|
-
for (const tc of messages[i].toolCalls) {
|
|
97
|
-
// A hidden row is a collapsed twin/reaction — it never executed.
|
|
98
|
-
if (isToolCallRowHidden(tc)) continue;
|
|
99
|
-
|
|
100
|
-
const kind = classifyTool(tc.name, tc.mcpServerSlug);
|
|
101
|
-
if (NON_MUTATING_KINDS.has(kind)) continue;
|
|
102
|
-
|
|
103
|
-
if (kind !== ToolKind.SHELL) {
|
|
104
|
-
// A file tool (mixed turn), a sub-agent (unattributable mutations), an
|
|
105
|
-
// MCP tool (opaque side effects), or an unrecognized name — the turn's
|
|
106
|
-
// mutations cannot be attributed to consented commands. Fail closed.
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
58
|
+
// This turn's tool calls are the positional tail from the first message this
|
|
59
|
+
// turn's stream produced; everything before was seeded from prior turns.
|
|
60
|
+
const turnToolCalls = messages.slice(turnStartIndex).flatMap((m) => m.toolCalls);
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const leaseConsentId = findLeaseConsentId(messages, tc.name);
|
|
123
|
-
if (leaseConsentId) {
|
|
124
|
-
consentIds.add(leaseConsentId);
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (globalBypass) {
|
|
128
|
-
authorizedByAutoApproveAll = true;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
// An executed shell command with no grant, no lease, and no bypass should
|
|
132
|
-
// be impossible (the hook gates every un-consented shell); if it ever
|
|
133
|
-
// happens, the honest answer is a manual review, not a waived one.
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// A turn that executed no consented command has nothing to attribute the
|
|
139
|
-
// change set to — whatever changed came from somewhere else. Manual review.
|
|
140
|
-
if (executedCommandCount === 0) return undefined;
|
|
141
|
-
|
|
142
|
-
return create(TurnCommandProvenanceSchema, {
|
|
143
|
-
consentToolCallIds: [...consentIds],
|
|
144
|
-
authorizedByAutoApproveAll,
|
|
62
|
+
return qualifyTurnCommandProvenance({
|
|
63
|
+
turnToolCalls,
|
|
64
|
+
messages,
|
|
65
|
+
// A shell whose identity token is in the denial ledger was denied by the
|
|
66
|
+
// hook — this turn's pending gate, never executed.
|
|
67
|
+
isExecutedCommand: (tc) => !deniedTokens.has(toolCallIdentityToken(tc)),
|
|
68
|
+
// A grant token maps back to the consent row (the row carrying the
|
|
69
|
+
// server-authored approval_action) that authorized this reinvocation.
|
|
70
|
+
resolveDirectConsent: (tc) => grantTokenToConsentId.get(toolCallIdentityToken(tc)),
|
|
71
|
+
globalBypass,
|
|
145
72
|
});
|
|
146
73
|
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Find the transcript row whose APPROVE_ALL authored the run-lifetime lease
|
|
150
|
-
* covering `toolName`'s category — the consent row a lease-executed command
|
|
151
|
-
* cites. Searched across the whole transcript (a lease usually originates in a
|
|
152
|
-
* prior turn); the first (earliest) APPROVE_ALL of the category is the lease's
|
|
153
|
-
* origin. Returns undefined when no lease row exists for the category.
|
|
154
|
-
*/
|
|
155
|
-
function findLeaseConsentId(
|
|
156
|
-
messages: readonly AgentMessage[],
|
|
157
|
-
toolName: string,
|
|
158
|
-
): string | undefined {
|
|
159
|
-
const category = toolApprovalCategory(toolName);
|
|
160
|
-
if (!category) return undefined;
|
|
161
|
-
for (const msg of messages) {
|
|
162
|
-
for (const tc of msg.toolCalls) {
|
|
163
|
-
if (tc.approvalAction !== ApprovalAction.APPROVE_ALL) continue;
|
|
164
|
-
if (toolApprovalCategory(tc.name) === category) return tc.id;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|