@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
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mid-run live capture — the harness-agnostic glue that turns a
|
|
3
|
-
* delta
|
|
4
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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).
|
|
27
|
-
*
|
|
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
|
-
|
|
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 (
|
|
44
|
-
* that runs regardless of persist frequency. Env-tunable for
|
|
45
|
-
* deployments; defaults to 2s (feels live without hammering
|
|
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
|
|
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}
|
|
58
|
-
* `files_changed
|
|
59
|
-
*
|
|
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
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
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
|
|
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
|
|
3
|
-
* delta
|
|
4
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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).
|
|
27
|
-
*
|
|
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 (
|
|
47
|
-
* that runs regardless of persist frequency. Env-tunable for
|
|
48
|
-
* deployments; defaults to 2s (feels live without hammering
|
|
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
|
|
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}
|
|
70
|
-
* `files_changed
|
|
71
|
-
*
|
|
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:
|
|
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
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
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
|
|
129
|
-
if (!
|
|
130
|
-
return;
|
|
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":"AAAA
|
|
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
|
|
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"}
|