@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.
Files changed (83) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/capture-flow.d.ts +26 -15
  3. package/dist/activities/execute-cursor/capture-flow.js +56 -18
  4. package/dist/activities/execute-cursor/capture-flow.js.map +1 -1
  5. package/dist/activities/execute-cursor/command-provenance.d.ts +11 -25
  6. package/dist/activities/execute-cursor/command-provenance.js +25 -115
  7. package/dist/activities/execute-cursor/command-provenance.js.map +1 -1
  8. package/dist/activities/execute-cursor/index.js +296 -477
  9. package/dist/activities/execute-cursor/index.js.map +1 -1
  10. package/dist/activities/execute-cursor/todo-tracker.d.ts +6 -1
  11. package/dist/activities/execute-cursor/todo-tracker.js +15 -43
  12. package/dist/activities/execute-cursor/todo-tracker.js.map +1 -1
  13. package/dist/activities/execute-cursor/turn-stream.d.ts +141 -0
  14. package/dist/activities/execute-cursor/turn-stream.js +249 -0
  15. package/dist/activities/execute-cursor/turn-stream.js.map +1 -0
  16. package/dist/activities/execute-deep-agent/command-provenance.d.ts +61 -0
  17. package/dist/activities/execute-deep-agent/command-provenance.js +72 -0
  18. package/dist/activities/execute-deep-agent/command-provenance.js.map +1 -0
  19. package/dist/activities/execute-deep-agent/index.js +60 -18
  20. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  21. package/dist/activities/execute-deep-agent/status-builder.js +8 -1
  22. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  23. package/dist/activities/execute-deep-agent/v3-status-builder.js +12 -1
  24. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  25. package/dist/otel.js +10 -0
  26. package/dist/otel.js.map +1 -1
  27. package/dist/shared/filereview/cas-progress.d.ts +63 -0
  28. package/dist/shared/filereview/cas-progress.js +128 -0
  29. package/dist/shared/filereview/cas-progress.js.map +1 -0
  30. package/dist/shared/filereview/cas-substrate.d.ts +29 -0
  31. package/dist/shared/filereview/cas-substrate.js +46 -21
  32. package/dist/shared/filereview/cas-substrate.js.map +1 -1
  33. package/dist/shared/filereview/command-provenance.d.ts +93 -0
  34. package/dist/shared/filereview/command-provenance.js +132 -0
  35. package/dist/shared/filereview/command-provenance.js.map +1 -0
  36. package/dist/shared/filereview/git-substrate.d.ts +9 -3
  37. package/dist/shared/filereview/git-substrate.js.map +1 -1
  38. package/dist/shared/filereview/index.d.ts +4 -3
  39. package/dist/shared/filereview/index.js +3 -2
  40. package/dist/shared/filereview/index.js.map +1 -1
  41. package/dist/shared/filereview/progress.d.ts +105 -34
  42. package/dist/shared/filereview/progress.js +96 -34
  43. package/dist/shared/filereview/progress.js.map +1 -1
  44. package/dist/shared/plan-mode-prompt.d.ts +9 -0
  45. package/dist/shared/plan-mode-prompt.js +18 -0
  46. package/dist/shared/plan-mode-prompt.js.map +1 -1
  47. package/dist/shared/todos.d.ts +56 -0
  48. package/dist/shared/todos.js +98 -0
  49. package/dist/shared/todos.js.map +1 -0
  50. package/dist/shared/tool-row.d.ts +16 -0
  51. package/dist/shared/tool-row.js +31 -0
  52. package/dist/shared/tool-row.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/__tests__/otel-turn-span.test.ts +61 -0
  55. package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +169 -0
  56. package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +349 -0
  57. package/src/activities/execute-cursor/capture-flow.ts +71 -25
  58. package/src/activities/execute-cursor/command-provenance.ts +25 -120
  59. package/src/activities/execute-cursor/index.ts +344 -504
  60. package/src/activities/execute-cursor/todo-tracker.ts +17 -59
  61. package/src/activities/execute-cursor/turn-stream.ts +418 -0
  62. package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +252 -0
  63. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +78 -0
  64. package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +105 -1
  65. package/src/activities/execute-deep-agent/command-provenance.ts +102 -0
  66. package/src/activities/execute-deep-agent/index.ts +74 -18
  67. package/src/activities/execute-deep-agent/status-builder.ts +9 -0
  68. package/src/activities/execute-deep-agent/v3-status-builder.ts +13 -0
  69. package/src/otel.ts +8 -0
  70. package/src/shared/__tests__/todos.test.ts +216 -0
  71. package/src/shared/filereview/__tests__/cas-progress.test.ts +228 -0
  72. package/src/shared/filereview/__tests__/cas-substrate.test.ts +66 -0
  73. package/src/shared/filereview/__tests__/command-provenance.test.ts +252 -0
  74. package/src/shared/filereview/__tests__/progress.test.ts +112 -10
  75. package/src/shared/filereview/cas-progress.ts +170 -0
  76. package/src/shared/filereview/cas-substrate.ts +69 -24
  77. package/src/shared/filereview/command-provenance.ts +180 -0
  78. package/src/shared/filereview/git-substrate.ts +9 -3
  79. package/src/shared/filereview/index.ts +15 -1
  80. package/src/shared/filereview/progress.ts +171 -47
  81. package/src/shared/plan-mode-prompt.ts +18 -0
  82. package/src/shared/todos.ts +126 -0
  83. package/src/shared/tool-row.ts +34 -0
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Mid-run live capture — the harness-agnostic glue that turns a content-free git
3
- * delta ({@link ./git-substrate.js} `captureProgressDelta`) into the transient
4
- * `AgentExecutionStatus.file_change_progress` snapshot the "N files changed so
5
- * far" strip renders (DD-32).
2
+ * Mid-run live capture — the harness-agnostic glue that turns a per-turn
3
+ * workspace delta into the transient `AgentExecutionStatus.file_change_progress`
4
+ * snapshot the "N files changed so far" strip renders (DD-32 / DD-33).
6
5
  *
7
6
  * THE MODEL
8
7
  * ---------
@@ -13,36 +12,89 @@
13
12
  * `file_change_sets`) remains the single authoritative, reviewable diff; a mid-run
14
13
  * snapshot is no more authoritative than a streamed tool-call arg.
15
14
  *
16
- * Both harnesses drive this through {@link captureFileChangeProgress} from their
17
- * per-persist loop, each passing its OWN `excludePaths` (the runner-owned gate
18
- * files it writes into the workspace), so progress and the turn-boundary candidate
19
- * agree on what is "the agent's change". Capture is throttled by a floor +
20
- * tree-sha short-circuit so a quiet turn is nearly free.
15
+ * THE SUBSTRATE ABSTRACTION (DD-33)
16
+ * ---------------------------------
17
+ * Where the delta comes from differs by workspace: a git tree diffs cheaply with
18
+ * `git --numstat` (the git substrate), a non-git / gitignored workspace reads the
19
+ * CAS observer (the cas substrate, {@link ./cas-progress.js}), and a git tree
20
+ * with gitignored writes composes both (the hybrid substrate). A
21
+ * {@link ProgressSubstrate} hides that behind one `capture()` so the floor + attach
22
+ * logic below is written once. Each substrate is a per-turn object owning its own
23
+ * short-circuit cache; `capture()` always returns the FULL cumulative turn delta
24
+ * plus a `changed` flag, so the hybrid can merge both slices even when only one
25
+ * moved (a `ProgressDelta | undefined` would drop the unchanged slice).
21
26
  *
22
27
  * SECRET SAFETY
23
28
  * -------------
24
29
  * No file bodies are ever carried. A secret-like path ({@link isSecretLikePath})
25
30
  * is still surfaced (path visible) but with its line counts ZEROED — the same
26
- * "path visible, content withheld" rule the ledger uses (DD-12). Nothing new can
27
- * leak through this field.
31
+ * "path visible, content withheld" rule the ledger uses (DD-12). The cas
32
+ * substrate additionally excludes gate-blocked secrets up front
33
+ * ({@link partitionIgnoredPathsBySecret}); this zeroing is the belt-and-suspenders
34
+ * backstop. Nothing new can leak through this field.
28
35
  *
29
- * @since File-Change HITL Redesign (mid-run live capture / DD-32)
36
+ * @since File-Change HITL Redesign (mid-run live capture / DD-32; non-git + hybrid / DD-33)
30
37
  */
31
38
  import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
39
+ import type { FileChangeKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
32
40
  import type { FileChangeProgress } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
33
- import { type ProgressDelta } from "./git-substrate.js";
41
+ /**
42
+ * One file's slim, content-free progress row — the substrate-neutral input to
43
+ * {@link buildFileChangeProgress}. Carries the proto {@link FileChangeKind}
44
+ * directly (git and cas substrates both normalize to it), so no side re-maps.
45
+ * `linesAdded`/`linesRemoved` are 0 when uncountable (binary / oversized /
46
+ * secret-withheld).
47
+ */
48
+ export interface ProgressEntry {
49
+ /** Path before the change (workspace-relative). Empty for ADD. */
50
+ readonly pathBefore: string;
51
+ /** Path after the change (workspace-relative). Empty for DELETE. */
52
+ readonly pathAfter: string;
53
+ readonly kind: FileChangeKind;
54
+ readonly linesAdded: number;
55
+ readonly linesRemoved: number;
56
+ }
57
+ /**
58
+ * The substrate-neutral progress delta the git/cas/hybrid substrates emit.
59
+ *
60
+ * `totalFilesChanged` is the honest count of changed files this turn, which may
61
+ * EXCEED `entries.length` when a substrate caps how many files it reads (the cas
62
+ * substrate reads only a bounded prefix — DD-33). Undefined means "the entries
63
+ * ARE every changed file" (the git substrate, whose numstat is free), so
64
+ * {@link buildFileChangeProgress} falls back to `entries.length`.
65
+ */
66
+ export interface ProgressDelta {
67
+ readonly entries: readonly ProgressEntry[];
68
+ readonly totalFilesChanged?: number;
69
+ }
70
+ /** One mid-run capture: the FULL cumulative turn delta + whether it changed. */
71
+ export interface ProgressCapture {
72
+ readonly delta: ProgressDelta;
73
+ /** False ⇒ nothing moved since the last capture; the caller skips re-attach. */
74
+ readonly changed: boolean;
75
+ }
76
+ /**
77
+ * A per-turn source of the mid-run delta. Implementations own their own
78
+ * short-circuit cache and ALWAYS return the full cumulative delta (never a
79
+ * bare "unchanged" sentinel), so {@link createHybridProgressSubstrate} can merge
80
+ * a changed slice with an unchanged one without losing the latter.
81
+ */
82
+ export interface ProgressSubstrate {
83
+ capture(): Promise<ProgressCapture>;
84
+ }
34
85
  /**
35
86
  * Cap the per-file entry list so a turn touching thousands of files does not
36
87
  * bloat the persisted status (which rides Temporal / the store). `files_changed`
37
88
  * and the aggregate counts stay honest over ALL files; the strip shows "… and K
38
- * more" when the list is capped.
89
+ * more" when the list is capped. Also the cas substrate's read budget — it reads
90
+ * after-bytes for at most this many files per capture (DD-33).
39
91
  */
40
92
  export declare const PROGRESS_MAX_ENTRIES = 200;
41
93
  /**
42
94
  * Minimum wall-clock gap between two mid-run captures, in ms. A floor on cost:
43
- * every capture stages the working tree (`write-tree`), so we bound how often
44
- * that runs regardless of persist frequency. Env-tunable for large-repo
45
- * deployments; defaults to 2s (feels live without hammering git).
95
+ * every capture stages the working tree (git) or reads the touched set (cas), so
96
+ * we bound how often that runs regardless of persist frequency. Env-tunable for
97
+ * large-repo deployments; defaults to 2s (feels live without hammering the disk).
46
98
  */
47
99
  export declare const PROGRESS_CAPTURE_MIN_INTERVAL_MS: number;
48
100
  /**
@@ -52,31 +104,31 @@ export declare const PROGRESS_CAPTURE_MIN_INTERVAL_MS: number;
52
104
  */
53
105
  export declare function shouldCaptureProgress(lastAtMs: number, nowMs: number, minIntervalMs?: number): boolean;
54
106
  /**
55
- * Build the transient {@link FileChangeProgress} message from a content-free git
107
+ * Build the transient {@link FileChangeProgress} message from a substrate-neutral
56
108
  * delta. Zeroes counts for secret-like paths (path visible, magnitude withheld),
57
- * caps the entry list at {@link PROGRESS_MAX_ENTRIES} while keeping
58
- * `files_changed`/aggregate counts honest over every file, and stamps
59
- * `captured_at`. Pure and exported for direct testing.
109
+ * caps the entry list at {@link PROGRESS_MAX_ENTRIES}, and reports the honest
110
+ * `files_changed` (`delta.totalFilesChanged` when a substrate capped its reads,
111
+ * else the entry count). Aggregate counts sum over the emitted entries. Pure and
112
+ * exported for direct testing.
60
113
  */
61
114
  export declare function buildFileChangeProgress(delta: ProgressDelta, changeSetId: string): FileChangeProgress;
62
115
  /**
63
- * Mutable per-turn state the caller threads across persists: the last captured
64
- * tree sha (for the short-circuit) and the last capture time (for the floor).
65
- * A fresh turn starts a fresh state (`{ lastAtMs: 0 }`).
116
+ * Mutable per-turn state the caller threads across persists: the last capture
117
+ * time (the floor). Substrate-specific short-circuit state (git tree sha, cas
118
+ * signature) lives inside the {@link ProgressSubstrate}, not here. A fresh turn
119
+ * starts a fresh state (`{ lastAtMs: 0 }`).
66
120
  */
67
121
  export interface ProgressCaptureState {
68
- lastTreeSha?: string;
69
122
  lastAtMs: number;
70
123
  }
71
124
  /** A fresh per-turn progress state. */
72
125
  export declare function newProgressCaptureState(): ProgressCaptureState;
73
126
  /**
74
127
  * Capture-and-attach the live progress snapshot onto `status.file_change_progress`,
75
- * throttled by the floor + tree-sha short-circuit. Mutates `status` and `state`
76
- * in place; a no-op when the floor has not elapsed or the working tree is
77
- * unchanged since the last capture. Content-free and secret-safe (see
78
- * {@link buildFileChangeProgress}). Called by both harnesses from their per-persist
79
- * loop; each passes its own `excludePaths`.
128
+ * throttled by the floor. Mutates `status` and `state` in place; a no-op when the
129
+ * floor has not elapsed or the substrate reports nothing changed. Content-free and
130
+ * secret-safe (see {@link buildFileChangeProgress}). Called by both harnesses from
131
+ * their per-persist loop, each passing a substrate built for its workspace shape.
80
132
  *
81
133
  * The field is set even when the delta is now EMPTY (the agent reverted its own
82
134
  * edits) so the strip reflects the reversion (it hides at zero) rather than
@@ -85,12 +137,31 @@ export declare function newProgressCaptureState(): ProgressCaptureState;
85
137
  */
86
138
  export declare function captureFileChangeProgress(opts: {
87
139
  readonly status: AgentExecutionStatus;
88
- readonly gitRoot: string;
89
- readonly executionId: string;
90
140
  readonly changeSetId: string;
91
- readonly baselineTree: string;
92
- readonly excludePaths?: readonly string[];
141
+ readonly substrate: ProgressSubstrate;
93
142
  readonly state: ProgressCaptureState;
94
143
  /** Injectable clock for tests; defaults to `Date.now()`. */
95
144
  readonly nowMs?: number;
96
145
  }): Promise<void>;
146
+ /**
147
+ * The git substrate: the working-tree `--numstat` delta against the pinned
148
+ * baseline. `git add -A` honors `.gitignore`, so this covers exactly the
149
+ * git-TRACKED changes (from any source — tool or shell), disjoint from the cas
150
+ * substrate's gitignored set. Preserves the tree-sha short-circuit: on an
151
+ * unchanged tree it returns the cached full delta with `changed:false` (the
152
+ * caller skips the re-attach), byte-identical to the pre-DD-33 behavior.
153
+ */
154
+ export declare function createGitProgressSubstrate(opts: {
155
+ readonly workspaceRoot: string;
156
+ readonly executionId: string;
157
+ readonly baselineTree: string;
158
+ readonly excludePaths?: readonly string[];
159
+ }): ProgressSubstrate;
160
+ /**
161
+ * The hybrid substrate: a git tree whose gitignored writes are captured via CAS.
162
+ * Concatenates both slices (disjoint by the `.gitignore` boundary — numstat sees
163
+ * only tracked paths, the observer only gitignored ones) and sums the honest
164
+ * totals. `changed` is true when EITHER slice moved; because each child returns
165
+ * its full cumulative delta, the merged delta always carries both slices.
166
+ */
167
+ export declare function createHybridProgressSubstrate(git: ProgressSubstrate, cas: ProgressSubstrate): ProgressSubstrate;
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Mid-run live capture — the harness-agnostic glue that turns a content-free git
3
- * delta ({@link ./git-substrate.js} `captureProgressDelta`) into the transient
4
- * `AgentExecutionStatus.file_change_progress` snapshot the "N files changed so
5
- * far" strip renders (DD-32).
2
+ * Mid-run live capture — the harness-agnostic glue that turns a per-turn
3
+ * workspace delta into the transient `AgentExecutionStatus.file_change_progress`
4
+ * snapshot the "N files changed so far" strip renders (DD-32 / DD-33).
6
5
  *
7
6
  * THE MODEL
8
7
  * ---------
@@ -13,39 +12,48 @@
13
12
  * `file_change_sets`) remains the single authoritative, reviewable diff; a mid-run
14
13
  * snapshot is no more authoritative than a streamed tool-call arg.
15
14
  *
16
- * Both harnesses drive this through {@link captureFileChangeProgress} from their
17
- * per-persist loop, each passing its OWN `excludePaths` (the runner-owned gate
18
- * files it writes into the workspace), so progress and the turn-boundary candidate
19
- * agree on what is "the agent's change". Capture is throttled by a floor +
20
- * tree-sha short-circuit so a quiet turn is nearly free.
15
+ * THE SUBSTRATE ABSTRACTION (DD-33)
16
+ * ---------------------------------
17
+ * Where the delta comes from differs by workspace: a git tree diffs cheaply with
18
+ * `git --numstat` (the git substrate), a non-git / gitignored workspace reads the
19
+ * CAS observer (the cas substrate, {@link ./cas-progress.js}), and a git tree
20
+ * with gitignored writes composes both (the hybrid substrate). A
21
+ * {@link ProgressSubstrate} hides that behind one `capture()` so the floor + attach
22
+ * logic below is written once. Each substrate is a per-turn object owning its own
23
+ * short-circuit cache; `capture()` always returns the FULL cumulative turn delta
24
+ * plus a `changed` flag, so the hybrid can merge both slices even when only one
25
+ * moved (a `ProgressDelta | undefined` would drop the unchanged slice).
21
26
  *
22
27
  * SECRET SAFETY
23
28
  * -------------
24
29
  * No file bodies are ever carried. A secret-like path ({@link isSecretLikePath})
25
30
  * is still surfaced (path visible) but with its line counts ZEROED — the same
26
- * "path visible, content withheld" rule the ledger uses (DD-12). Nothing new can
27
- * leak through this field.
31
+ * "path visible, content withheld" rule the ledger uses (DD-12). The cas
32
+ * substrate additionally excludes gate-blocked secrets up front
33
+ * ({@link partitionIgnoredPathsBySecret}); this zeroing is the belt-and-suspenders
34
+ * backstop. Nothing new can leak through this field.
28
35
  *
29
- * @since File-Change HITL Redesign (mid-run live capture / DD-32)
36
+ * @since File-Change HITL Redesign (mid-run live capture / DD-32; non-git + hybrid / DD-33)
30
37
  */
31
38
  import { create } from "@bufbuild/protobuf";
32
39
  import { FileChangeProgressEntrySchema, FileChangeProgressSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
33
40
  import { utcTimestamp } from "../status.js";
34
41
  import { toFileChangeKind } from "./capture.js";
35
- import { captureProgressDelta } from "./git-substrate.js";
42
+ import { captureProgressDelta, } from "./git-substrate.js";
36
43
  import { isSecretLikePath } from "./secret-paths.js";
37
44
  /**
38
45
  * Cap the per-file entry list so a turn touching thousands of files does not
39
46
  * bloat the persisted status (which rides Temporal / the store). `files_changed`
40
47
  * and the aggregate counts stay honest over ALL files; the strip shows "… and K
41
- * more" when the list is capped.
48
+ * more" when the list is capped. Also the cas substrate's read budget — it reads
49
+ * after-bytes for at most this many files per capture (DD-33).
42
50
  */
43
51
  export const PROGRESS_MAX_ENTRIES = 200;
44
52
  /**
45
53
  * Minimum wall-clock gap between two mid-run captures, in ms. A floor on cost:
46
- * every capture stages the working tree (`write-tree`), so we bound how often
47
- * that runs regardless of persist frequency. Env-tunable for large-repo
48
- * deployments; defaults to 2s (feels live without hammering git).
54
+ * every capture stages the working tree (git) or reads the touched set (cas), so
55
+ * we bound how often that runs regardless of persist frequency. Env-tunable for
56
+ * large-repo deployments; defaults to 2s (feels live without hammering the disk).
49
57
  */
50
58
  export const PROGRESS_CAPTURE_MIN_INTERVAL_MS = readMinIntervalMs();
51
59
  function readMinIntervalMs() {
@@ -64,19 +72,18 @@ export function shouldCaptureProgress(lastAtMs, nowMs, minIntervalMs = PROGRESS_
64
72
  return nowMs - lastAtMs >= minIntervalMs;
65
73
  }
66
74
  /**
67
- * Build the transient {@link FileChangeProgress} message from a content-free git
75
+ * Build the transient {@link FileChangeProgress} message from a substrate-neutral
68
76
  * delta. Zeroes counts for secret-like paths (path visible, magnitude withheld),
69
- * caps the entry list at {@link PROGRESS_MAX_ENTRIES} while keeping
70
- * `files_changed`/aggregate counts honest over every file, and stamps
71
- * `captured_at`. Pure and exported for direct testing.
77
+ * caps the entry list at {@link PROGRESS_MAX_ENTRIES}, and reports the honest
78
+ * `files_changed` (`delta.totalFilesChanged` when a substrate capped its reads,
79
+ * else the entry count). Aggregate counts sum over the emitted entries. Pure and
80
+ * exported for direct testing.
72
81
  */
73
82
  export function buildFileChangeProgress(delta, changeSetId) {
74
- let filesChanged = 0;
75
83
  let totalAdded = 0;
76
84
  let totalRemoved = 0;
77
85
  const entries = [];
78
86
  for (const entry of delta.entries) {
79
- filesChanged += 1;
80
87
  // A secret-like path is surfaced (path visible) but its magnitude is withheld
81
88
  // — counts zeroed, mirroring the ledger's "path visible, content withheld".
82
89
  const secret = isSecretLikePath(entry.pathAfter || entry.pathBefore);
@@ -88,7 +95,7 @@ export function buildFileChangeProgress(delta, changeSetId) {
88
95
  entries.push(create(FileChangeProgressEntrySchema, {
89
96
  pathBefore: entry.pathBefore,
90
97
  pathAfter: entry.pathAfter,
91
- kind: toFileChangeKind(entry.changeType),
98
+ kind: entry.kind,
92
99
  linesAdded,
93
100
  linesRemoved,
94
101
  }));
@@ -96,7 +103,7 @@ export function buildFileChangeProgress(delta, changeSetId) {
96
103
  }
97
104
  return create(FileChangeProgressSchema, {
98
105
  changeSetId,
99
- filesChanged,
106
+ filesChanged: delta.totalFilesChanged ?? delta.entries.length,
100
107
  linesAdded: totalAdded,
101
108
  linesRemoved: totalRemoved,
102
109
  entries,
@@ -109,11 +116,10 @@ export function newProgressCaptureState() {
109
116
  }
110
117
  /**
111
118
  * Capture-and-attach the live progress snapshot onto `status.file_change_progress`,
112
- * throttled by the floor + tree-sha short-circuit. Mutates `status` and `state`
113
- * in place; a no-op when the floor has not elapsed or the working tree is
114
- * unchanged since the last capture. Content-free and secret-safe (see
115
- * {@link buildFileChangeProgress}). Called by both harnesses from their per-persist
116
- * loop; each passes its own `excludePaths`.
119
+ * throttled by the floor. Mutates `status` and `state` in place; a no-op when the
120
+ * floor has not elapsed or the substrate reports nothing changed. Content-free and
121
+ * secret-safe (see {@link buildFileChangeProgress}). Called by both harnesses from
122
+ * their per-persist loop, each passing a substrate built for its workspace shape.
117
123
  *
118
124
  * The field is set even when the delta is now EMPTY (the agent reverted its own
119
125
  * edits) so the strip reflects the reversion (it hides at zero) rather than
@@ -125,10 +131,66 @@ export async function captureFileChangeProgress(opts) {
125
131
  if (!shouldCaptureProgress(opts.state.lastAtMs, now))
126
132
  return;
127
133
  opts.state.lastAtMs = now;
128
- const delta = await captureProgressDelta(opts.gitRoot, opts.executionId, opts.baselineTree, opts.excludePaths, opts.state.lastTreeSha);
129
- if (!delta)
130
- return; // working tree unchanged since the last capture
131
- opts.state.lastTreeSha = delta.afterTree;
134
+ const { delta, changed } = await opts.substrate.capture();
135
+ if (!changed)
136
+ return;
132
137
  opts.status.fileChangeProgress = buildFileChangeProgress(delta, opts.changeSetId);
133
138
  }
139
+ // ---------------------------------------------------------------------------
140
+ // Substrates
141
+ // ---------------------------------------------------------------------------
142
+ function gitEntryToProgressEntry(e) {
143
+ return {
144
+ pathBefore: e.pathBefore,
145
+ pathAfter: e.pathAfter,
146
+ kind: toFileChangeKind(e.changeType),
147
+ linesAdded: e.linesAdded,
148
+ linesRemoved: e.linesRemoved,
149
+ };
150
+ }
151
+ /**
152
+ * The git substrate: the working-tree `--numstat` delta against the pinned
153
+ * baseline. `git add -A` honors `.gitignore`, so this covers exactly the
154
+ * git-TRACKED changes (from any source — tool or shell), disjoint from the cas
155
+ * substrate's gitignored set. Preserves the tree-sha short-circuit: on an
156
+ * unchanged tree it returns the cached full delta with `changed:false` (the
157
+ * caller skips the re-attach), byte-identical to the pre-DD-33 behavior.
158
+ */
159
+ export function createGitProgressSubstrate(opts) {
160
+ let lastTreeSha;
161
+ let cachedFull = { entries: [] };
162
+ return {
163
+ async capture() {
164
+ const gitDelta = await captureProgressDelta(opts.workspaceRoot, opts.executionId, opts.baselineTree, opts.excludePaths, lastTreeSha);
165
+ if (gitDelta === undefined) {
166
+ // Working tree unchanged since the last capture — reuse the cache so the
167
+ // hybrid can still merge this (unchanged) slice with a changed cas slice.
168
+ return { delta: cachedFull, changed: false };
169
+ }
170
+ lastTreeSha = gitDelta.afterTree;
171
+ cachedFull = { entries: gitDelta.entries.map(gitEntryToProgressEntry) };
172
+ return { delta: cachedFull, changed: true };
173
+ },
174
+ };
175
+ }
176
+ /**
177
+ * The hybrid substrate: a git tree whose gitignored writes are captured via CAS.
178
+ * Concatenates both slices (disjoint by the `.gitignore` boundary — numstat sees
179
+ * only tracked paths, the observer only gitignored ones) and sums the honest
180
+ * totals. `changed` is true when EITHER slice moved; because each child returns
181
+ * its full cumulative delta, the merged delta always carries both slices.
182
+ */
183
+ export function createHybridProgressSubstrate(git, cas) {
184
+ return {
185
+ async capture() {
186
+ const [g, c] = await Promise.all([git.capture(), cas.capture()]);
187
+ const delta = {
188
+ entries: [...g.delta.entries, ...c.delta.entries],
189
+ totalFilesChanged: (g.delta.totalFilesChanged ?? g.delta.entries.length) +
190
+ (c.delta.totalFilesChanged ?? c.delta.entries.length),
191
+ };
192
+ return { delta, changed: g.changed || c.changed };
193
+ },
194
+ };
195
+ }
134
196
  //# sourceMappingURL=progress.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../../src/shared/filereview/progress.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../../src/shared/filereview/progress.ts"],"names":[],"mappings":"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"}
@@ -28,5 +28,14 @@
28
28
  * message is published verbatim as a plan document (whose filename is derived
29
29
  * from the leading `#` title), so a fenced or chat-suffixed plan degrades the
30
30
  * reviewable document the user sees.
31
+ *
32
+ * Fence hygiene inside the document is part of the same contract. A plan
33
+ * frequently quotes file content that itself contains fenced code blocks
34
+ * ("insert this section into README.md"); with a same-length outer fence the
35
+ * inner block's closer terminates the outer fence early and corrupts the
36
+ * rendered document — on every client (the react SDK and the terminal
37
+ * renderer alike). And because the plan viewers render top-level ```mermaid
38
+ * fences as diagrams, the directive steers diagrams into the plan body proper
39
+ * rather than leaving them buried, unrendered, inside quoted file content.
31
40
  */
32
41
  export declare const PLAN_MODE_DIRECTIVE: string;
@@ -28,6 +28,15 @@
28
28
  * message is published verbatim as a plan document (whose filename is derived
29
29
  * from the leading `#` title), so a fenced or chat-suffixed plan degrades the
30
30
  * reviewable document the user sees.
31
+ *
32
+ * Fence hygiene inside the document is part of the same contract. A plan
33
+ * frequently quotes file content that itself contains fenced code blocks
34
+ * ("insert this section into README.md"); with a same-length outer fence the
35
+ * inner block's closer terminates the outer fence early and corrupts the
36
+ * rendered document — on every client (the react SDK and the terminal
37
+ * renderer alike). And because the plan viewers render top-level ```mermaid
38
+ * fences as diagrams, the directive steers diagrams into the plan body proper
39
+ * rather than leaving them buried, unrendered, inside quoted file content.
31
40
  */
32
41
  export const PLAN_MODE_DIRECTIVE = [
33
42
  "IMPORTANT: You are in Plan mode — a read-only analysis turn whose " +
@@ -49,6 +58,15 @@ export const PLAN_MODE_DIRECTIVE = [
49
58
  "- Reference concrete file paths and describe the specific changes " +
50
59
  "planned for each.",
51
60
  "- Do NOT wrap the document in a code fence.",
61
+ "- When quoting content that itself contains fenced code blocks (e.g. a " +
62
+ "proposed file section with a code sample inside), open the outer fence " +
63
+ "with MORE backticks than any inner fence (four or more) — a same-length " +
64
+ "inner closer would terminate the outer fence early and corrupt the " +
65
+ "rendered document.",
66
+ "- Fenced ```mermaid blocks at the top level of the document render as " +
67
+ "diagrams in the plan viewer. When a diagram helps communicate the " +
68
+ "design (architecture, flows), include it directly in the plan body — " +
69
+ "not only inside quoted file content, where it stays unrendered source.",
52
70
  '- Do NOT end with conversational closers ("Let me know...", "Shall I ' +
53
71
  'proceed?") — the next step is the user\'s Build action, and trailing ' +
54
72
  "chat would be published as part of the document.",
@@ -1 +1 @@
1
- {"version":3,"file":"plan-mode-prompt.js","sourceRoot":"","sources":["../../src/shared/plan-mode-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,oEAAoE;QAClE,wCAAwC;IAC1C,EAAE;IACF,cAAc;IACd,6CAA6C;IAC7C,yEAAyE;IACzE,mCAAmC;IACnC,EAAE;IACF,yEAAyE;QACvE,+DAA+D;IACjE,0EAA0E;QACxE,wEAAwE;QACxE,+CAA+C;IACjD,2EAA2E;QACzE,oEAAoE;QACpE,0DAA0D;IAC5D,oEAAoE;QAClE,mBAAmB;IACrB,6CAA6C;IAC7C,uEAAuE;QACrE,uEAAuE;QACvE,kDAAkD;CACrD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"plan-mode-prompt.js","sourceRoot":"","sources":["../../src/shared/plan-mode-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,oEAAoE;QAClE,wCAAwC;IAC1C,EAAE;IACF,cAAc;IACd,6CAA6C;IAC7C,yEAAyE;IACzE,mCAAmC;IACnC,EAAE;IACF,yEAAyE;QACvE,+DAA+D;IACjE,0EAA0E;QACxE,wEAAwE;QACxE,+CAA+C;IACjD,2EAA2E;QACzE,oEAAoE;QACpE,0DAA0D;IAC5D,oEAAoE;QAClE,mBAAmB;IACrB,6CAA6C;IAC7C,yEAAyE;QACvE,yEAAyE;QACzE,0EAA0E;QAC1E,qEAAqE;QACrE,oBAAoB;IACtB,wEAAwE;QACtE,oEAAoE;QACpE,uEAAuE;QACvE,wEAAwE;IAC1E,uEAAuE;QACrE,uEAAuE;QACvE,kDAAkD;CACrD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Todo-list projection shared by every harness's status writer.
3
+ *
4
+ * All harnesses expose an agent to-do tool — the Cursor SDK emits `TodoWrite`
5
+ * (legacy) / `updateTodos` (current), the native deepagents harness emits
6
+ * `write_todos` — and each writes the SAME `AgentExecutionStatus.todos` proto
7
+ * map that the clients (React `TodoCard`, CLI) render. This module is the single
8
+ * place that maps a raw tool payload into that map, so the Cursor `TodoTracker`
9
+ * and the native v2/v3 status builders cannot drift in how they build todos.
10
+ *
11
+ * The mapping is a superset of what any one harness emits: the Cursor SDK sends
12
+ * per-item `id`/`created_at` and can send a `merge` flag and camelCase statuses;
13
+ * deepagents sends only `{ content, status }` (three statuses, no id, no merge).
14
+ * One helper handling the superset serves all three writers.
15
+ */
16
+ import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
17
+ /**
18
+ * A single raw todo as it arrives in a to-do tool call's arguments. Every field
19
+ * is optional and defensively typed: the Cursor `updateTodos` schema omits
20
+ * per-item `id`, deepagents omits `id`/`created_at`, and the values reach us as
21
+ * untyped JSON (a Cursor `args` string or a protobuf `JsonObject` value).
22
+ */
23
+ export interface RawTodoItem {
24
+ id?: string;
25
+ content?: string;
26
+ status?: string;
27
+ created_at?: string;
28
+ }
29
+ /**
30
+ * Project a to-do tool call's payload into a `status.todos` map, in place.
31
+ * Returns whether the map changed — the caller's "dirty" / force-persist signal.
32
+ *
33
+ * Semantics (identical across all three harness writers):
34
+ * - Empty or non-array `rawTodos`: a full-replace (`!merge`) clears the map and
35
+ * is a change; a merge is a no-op (nothing to merge in) and is not.
36
+ * - Otherwise a full-replace clears first, then every item is (re)written.
37
+ * - `id` is the provided id or a stable index fallback (`todo-<i>`); order is
38
+ * the array order, so index ids are stable across full-replace updates.
39
+ * - `created_at` is preserved from the prior same-id entry ONLY on merge; a
40
+ * full-replace clears first, so it becomes `raw.created_at || now`. (The
41
+ * native harness is always full-replace and sends no `created_at`, so a
42
+ * native todo's `created_at` is `now` on each write — deliberately identical
43
+ * to the Cursor non-merge path.)
44
+ * - `updated_at` is always `now`.
45
+ *
46
+ * Note on clearing: an empty full-replace clears the in-memory map, but the Go
47
+ * `update_status` activity replaces persisted todos only when the incoming map
48
+ * is non-empty, so a settled list survives in history — the clear never reaches
49
+ * the record. This is intentional and relied upon by both harnesses.
50
+ */
51
+ export declare function applyTodoUpdate(target: {
52
+ [key: string]: TodoItem;
53
+ }, rawTodos: unknown, opts: {
54
+ merge: boolean;
55
+ now?: string;
56
+ }): boolean;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Todo-list projection shared by every harness's status writer.
3
+ *
4
+ * All harnesses expose an agent to-do tool — the Cursor SDK emits `TodoWrite`
5
+ * (legacy) / `updateTodos` (current), the native deepagents harness emits
6
+ * `write_todos` — and each writes the SAME `AgentExecutionStatus.todos` proto
7
+ * map that the clients (React `TodoCard`, CLI) render. This module is the single
8
+ * place that maps a raw tool payload into that map, so the Cursor `TodoTracker`
9
+ * and the native v2/v3 status builders cannot drift in how they build todos.
10
+ *
11
+ * The mapping is a superset of what any one harness emits: the Cursor SDK sends
12
+ * per-item `id`/`created_at` and can send a `merge` flag and camelCase statuses;
13
+ * deepagents sends only `{ content, status }` (three statuses, no id, no merge).
14
+ * One helper handling the superset serves all three writers.
15
+ */
16
+ import { create } from "@bufbuild/protobuf";
17
+ import { TodoItemSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
18
+ import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
19
+ import { utcTimestamp } from "./status.js";
20
+ /**
21
+ * Raw status string -> proto `TodoStatus`. Keyed on the lowercased status so
22
+ * the current SDK's camelCase `inProgress` and the legacy snake_case
23
+ * `in_progress` both resolve. `cancelled` is only ever emitted by the Cursor
24
+ * SDK — deepagents' three-status schema never sends it — but it is kept here so
25
+ * the one shared mapping is faithful to every producer. Unknown values fall back
26
+ * to `TODO_PENDING` at the call site.
27
+ */
28
+ const STATUS_MAP = {
29
+ pending: TodoStatus.TODO_PENDING,
30
+ in_progress: TodoStatus.TODO_IN_PROGRESS,
31
+ inprogress: TodoStatus.TODO_IN_PROGRESS,
32
+ completed: TodoStatus.TODO_COMPLETED,
33
+ cancelled: TodoStatus.TODO_CANCELLED,
34
+ };
35
+ /**
36
+ * Project a to-do tool call's payload into a `status.todos` map, in place.
37
+ * Returns whether the map changed — the caller's "dirty" / force-persist signal.
38
+ *
39
+ * Semantics (identical across all three harness writers):
40
+ * - Empty or non-array `rawTodos`: a full-replace (`!merge`) clears the map and
41
+ * is a change; a merge is a no-op (nothing to merge in) and is not.
42
+ * - Otherwise a full-replace clears first, then every item is (re)written.
43
+ * - `id` is the provided id or a stable index fallback (`todo-<i>`); order is
44
+ * the array order, so index ids are stable across full-replace updates.
45
+ * - `created_at` is preserved from the prior same-id entry ONLY on merge; a
46
+ * full-replace clears first, so it becomes `raw.created_at || now`. (The
47
+ * native harness is always full-replace and sends no `created_at`, so a
48
+ * native todo's `created_at` is `now` on each write — deliberately identical
49
+ * to the Cursor non-merge path.)
50
+ * - `updated_at` is always `now`.
51
+ *
52
+ * Note on clearing: an empty full-replace clears the in-memory map, but the Go
53
+ * `update_status` activity replaces persisted todos only when the incoming map
54
+ * is non-empty, so a settled list survives in history — the clear never reaches
55
+ * the record. This is intentional and relied upon by both harnesses.
56
+ */
57
+ export function applyTodoUpdate(target, rawTodos, opts) {
58
+ const { merge } = opts;
59
+ const now = opts.now ?? utcTimestamp();
60
+ if (!Array.isArray(rawTodos) || rawTodos.length === 0) {
61
+ if (!merge) {
62
+ clearMap(target);
63
+ return true;
64
+ }
65
+ return false;
66
+ }
67
+ if (!merge) {
68
+ clearMap(target);
69
+ }
70
+ for (let i = 0; i < rawTodos.length; i++) {
71
+ const raw = coerceRawTodo(rawTodos[i]);
72
+ const id = raw.id || `todo-${i}`;
73
+ const statusStr = (raw.status ?? "pending").toLowerCase();
74
+ const status = STATUS_MAP[statusStr] ?? TodoStatus.TODO_PENDING;
75
+ // Preserve the original creation time only when merging into an existing
76
+ // entry; a full-replace cleared the map above, so `existing` is undefined.
77
+ const existing = merge ? target[id] : undefined;
78
+ target[id] = create(TodoItemSchema, {
79
+ id,
80
+ content: raw.content ?? "",
81
+ status,
82
+ createdAt: existing?.createdAt || raw.created_at || now,
83
+ updatedAt: now,
84
+ });
85
+ }
86
+ return true;
87
+ }
88
+ /** Narrow an untyped array element to a {@link RawTodoItem}; non-objects become empty. */
89
+ function coerceRawTodo(value) {
90
+ return typeof value === "object" && value !== null ? value : {};
91
+ }
92
+ /** Delete every key from the map in place (proto map fields have no `.clear()`). */
93
+ function clearMap(target) {
94
+ for (const key of Object.keys(target)) {
95
+ delete target[key];
96
+ }
97
+ }
98
+ //# sourceMappingURL=todos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"todos.js","sourceRoot":"","sources":["../../src/shared/todos.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAC;AAE9F,OAAO,EAAE,UAAU,EAAE,MAAM,8DAA8D,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAe3C;;;;;;;GAOG;AACH,MAAM,UAAU,GAA+B;IAC7C,OAAO,EAAE,UAAU,CAAC,YAAY;IAChC,WAAW,EAAE,UAAU,CAAC,gBAAgB;IACxC,UAAU,EAAE,UAAU,CAAC,gBAAgB;IACvC,SAAS,EAAE,UAAU,CAAC,cAAc;IACpC,SAAS,EAAE,UAAU,CAAC,cAAc;CACrC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAmC,EACnC,QAAiB,EACjB,IAAsC;IAEtC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACvB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,YAAY,EAAE,CAAC;IAEvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,MAAM,CAAC,CAAC;YACjB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnB,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC;QAEhE,yEAAyE;QACzE,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEhD,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,cAAc,EAAE;YAClC,EAAE;YACF,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;YAC1B,MAAM;YACN,SAAS,EAAE,QAAQ,EAAE,SAAS,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG;YACvD,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0FAA0F;AAC1F,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAE,KAAqB,CAAC,CAAC,CAAC,EAAE,CAAC;AACnF,CAAC;AAED,oFAAoF;AACpF,SAAS,QAAQ,CAAC,MAAmC;IACnD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;AACH,CAAC"}