@stigmer/sdk 3.0.9-dev.20260615153829 → 3.1.0
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/execution/__tests__/approval-provenance.test.d.ts +2 -0
- package/execution/__tests__/approval-provenance.test.d.ts.map +1 -0
- package/execution/__tests__/approval-provenance.test.js +42 -0
- package/execution/__tests__/approval-provenance.test.js.map +1 -0
- package/execution/__tests__/file-review-fold.corpus.test.d.ts +2 -0
- package/execution/__tests__/file-review-fold.corpus.test.d.ts.map +1 -0
- package/execution/__tests__/file-review-fold.corpus.test.js +82 -0
- package/execution/__tests__/file-review-fold.corpus.test.js.map +1 -0
- package/execution/__tests__/file-review-fold.test.d.ts +2 -0
- package/execution/__tests__/file-review-fold.test.d.ts.map +1 -0
- package/execution/__tests__/file-review-fold.test.js +184 -0
- package/execution/__tests__/file-review-fold.test.js.map +1 -0
- package/execution/__tests__/to-display-file-change.test.d.ts +2 -0
- package/execution/__tests__/to-display-file-change.test.d.ts.map +1 -0
- package/execution/__tests__/to-display-file-change.test.js +69 -0
- package/execution/__tests__/to-display-file-change.test.js.map +1 -0
- package/execution/__tests__/tool-view.file-changes.test.d.ts +2 -0
- package/execution/__tests__/tool-view.file-changes.test.d.ts.map +1 -0
- package/execution/__tests__/tool-view.file-changes.test.js +86 -0
- package/execution/__tests__/tool-view.file-changes.test.js.map +1 -0
- package/execution/__tests__/tool-view.fixtures.test.js +3 -0
- package/execution/__tests__/tool-view.fixtures.test.js.map +1 -1
- package/execution/__tests__/tool-view.output-ref.test.d.ts +2 -0
- package/execution/__tests__/tool-view.output-ref.test.d.ts.map +1 -0
- package/execution/__tests__/tool-view.output-ref.test.js +64 -0
- package/execution/__tests__/tool-view.output-ref.test.js.map +1 -0
- package/execution/__tests__/tool-view.search.test.d.ts +2 -0
- package/execution/__tests__/tool-view.search.test.d.ts.map +1 -0
- package/execution/__tests__/tool-view.search.test.js +200 -0
- package/execution/__tests__/tool-view.search.test.js.map +1 -0
- package/execution/__tests__/tool-view.shell.test.d.ts +2 -0
- package/execution/__tests__/tool-view.shell.test.d.ts.map +1 -0
- package/execution/__tests__/tool-view.shell.test.js +104 -0
- package/execution/__tests__/tool-view.shell.test.js.map +1 -0
- package/execution/approval-provenance.d.ts +30 -0
- package/execution/approval-provenance.d.ts.map +1 -0
- package/execution/approval-provenance.js +73 -0
- package/execution/approval-provenance.js.map +1 -0
- package/execution/execution-phases.d.ts +7 -0
- package/execution/execution-phases.d.ts.map +1 -0
- package/execution/execution-phases.js +21 -0
- package/execution/execution-phases.js.map +1 -0
- package/execution/file-review-fold.d.ts +44 -0
- package/execution/file-review-fold.d.ts.map +1 -0
- package/execution/file-review-fold.js +182 -0
- package/execution/file-review-fold.js.map +1 -0
- package/execution/to-display-file-change.d.ts +14 -0
- package/execution/to-display-file-change.d.ts.map +1 -0
- package/execution/to-display-file-change.js +57 -0
- package/execution/to-display-file-change.js.map +1 -0
- package/execution/tool-view.d.ts +8 -2
- package/execution/tool-view.d.ts.map +1 -1
- package/execution/tool-view.js +205 -21
- package/execution/tool-view.js.map +1 -1
- package/gen/agentexecution.d.ts +2 -1
- package/gen/agentexecution.d.ts.map +1 -1
- package/gen/agentexecution.js +8 -0
- package/gen/agentexecution.js.map +1 -1
- package/gen/mcpserver.d.ts +1 -0
- package/gen/mcpserver.d.ts.map +1 -1
- package/gen/mcpserver.js +1 -0
- package/gen/mcpserver.js.map +1 -1
- package/gen/workflowexecution.d.ts +2 -1
- package/gen/workflowexecution.d.ts.map +1 -1
- package/gen/workflowexecution.js +8 -0
- package/gen/workflowexecution.js.map +1 -1
- package/index.d.ts +5 -1
- package/index.d.ts.map +1 -1
- package/index.js +5 -1
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/src/execution/__tests__/approval-provenance.test.ts +60 -0
- package/src/execution/__tests__/file-review-fold.corpus.test.ts +127 -0
- package/src/execution/__tests__/file-review-fold.test.ts +281 -0
- package/src/execution/__tests__/to-display-file-change.test.ts +91 -0
- package/src/execution/__tests__/tool-view.file-changes.test.ts +100 -0
- package/src/execution/__tests__/tool-view.fixtures.test.ts +4 -0
- package/src/execution/__tests__/tool-view.output-ref.test.ts +76 -0
- package/src/execution/__tests__/tool-view.search.test.ts +264 -0
- package/src/execution/__tests__/tool-view.shell.test.ts +127 -0
- package/src/execution/approval-provenance.ts +80 -0
- package/src/execution/execution-phases.ts +23 -0
- package/src/execution/file-review-fold.ts +210 -0
- package/src/execution/to-display-file-change.ts +66 -0
- package/src/execution/tool-view.ts +261 -30
- package/src/gen/agentexecution.ts +7 -1
- package/src/gen/mcpserver.ts +2 -0
- package/src/gen/workflowexecution.ts +7 -1
- package/src/index.ts +12 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// Framework-agnostic fold of the file-review event ledger into FileChangeSets.
|
|
2
|
+
//
|
|
3
|
+
// The server persists an append-only `file_review_event_stream` ledger on the
|
|
4
|
+
// execution status and computes `file_change_sets` as its projection. That
|
|
5
|
+
// server projection is phase-aware: it is nil for a TERMINAL execution (a dead
|
|
6
|
+
// execution has no *actionable* review). Settled/historical display therefore
|
|
7
|
+
// cannot read the projection — it must fold the always-preserved ledger itself.
|
|
8
|
+
//
|
|
9
|
+
// `foldFileReviewEventStream` is the TypeScript port of the Go projector
|
|
10
|
+
// `ProjectFileChangeSets` (backend/services/stigmer-server/pkg/domain/
|
|
11
|
+
// agentexecution/filereview/project.go), and its Java mirror
|
|
12
|
+
// `FileChangeSetProjector`. It is byte-for-byte identical to them EXCEPT it does
|
|
13
|
+
// not apply the terminal-phase gate — that is the whole point of the client-side
|
|
14
|
+
// fold (it must render settled sets for a terminal execution). Every other rule
|
|
15
|
+
// (first-seen change-set ordering, last-writer-wins scalars, decision
|
|
16
|
+
// accumulation, wholesale change replacement, sticky terminal status) matches
|
|
17
|
+
// the server exactly, so live and settled views agree.
|
|
18
|
+
//
|
|
19
|
+
// Parity is not maintained by discipline: the shared cross-edition corpus at
|
|
20
|
+
// apis/testdata/hitl/file-review/*.json — already replayed by the Go and Java
|
|
21
|
+
// projector suites — is replayed against this fold too (see the corpus parity
|
|
22
|
+
// test), so a drift between TS and the backends fails a test rather than
|
|
23
|
+
// shipping.
|
|
24
|
+
//
|
|
25
|
+
// This module has no React/framework dependency (mirrors tool-view.ts) so it is
|
|
26
|
+
// shared by @stigmer/react and @stigmer/ink and mirror-able by the Go CLI.
|
|
27
|
+
|
|
28
|
+
import { create } from "@bufbuild/protobuf";
|
|
29
|
+
import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
30
|
+
import type {
|
|
31
|
+
FileChangeSet,
|
|
32
|
+
FileReviewEvent,
|
|
33
|
+
FileReviewEventStream,
|
|
34
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
35
|
+
import { FileChangeSetSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
36
|
+
import {
|
|
37
|
+
FileChangeSetStatus,
|
|
38
|
+
FileDecisionScope,
|
|
39
|
+
FileReviewEventType,
|
|
40
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Folds a file-review event ledger into its list of {@link FileChangeSet}s,
|
|
44
|
+
* grouped by `change_set_id` in first-seen order.
|
|
45
|
+
*
|
|
46
|
+
* This is the display fold: unlike the server projection it does NOT collapse to
|
|
47
|
+
* an empty list for a terminal execution, so a completed/failed/cancelled
|
|
48
|
+
* execution still yields its settled sets for read-only display. For the LIVE
|
|
49
|
+
* (actionable) view, prefer the server-computed `status.file_change_sets`
|
|
50
|
+
* projection (it is structurally shared across stream frames); this fold is the
|
|
51
|
+
* settled/terminal complement — see {@link displayFileChangeSets}.
|
|
52
|
+
*
|
|
53
|
+
* The fold is a faithful port of the Go/Java projector: scalar and snapshot
|
|
54
|
+
* fields are last-writer-wins, `decisions` accumulate, `changes` are replaced
|
|
55
|
+
* wholesale by the latest `CANDIDATE_CAPTURED`, and terminal `RECONCILED` /
|
|
56
|
+
* `FAILED` statuses are sticky (a later non-terminal event never downgrades
|
|
57
|
+
* them).
|
|
58
|
+
*
|
|
59
|
+
* @param stream - The execution's `file_review_event_stream`, or `undefined`.
|
|
60
|
+
* @returns One {@link FileChangeSet} per `change_set_id`, in first-seen order.
|
|
61
|
+
*/
|
|
62
|
+
export function foldFileReviewEventStream(
|
|
63
|
+
stream: FileReviewEventStream | undefined,
|
|
64
|
+
): FileChangeSet[] {
|
|
65
|
+
if (!stream || stream.events.length === 0) return [];
|
|
66
|
+
|
|
67
|
+
const order: string[] = [];
|
|
68
|
+
const byId = new Map<string, FileChangeSet>();
|
|
69
|
+
|
|
70
|
+
for (const ev of stream.events) {
|
|
71
|
+
const changeSetId = ev.changeSetId;
|
|
72
|
+
// An event with no correlation id cannot be attributed to a set; the server
|
|
73
|
+
// skips it, so we do too.
|
|
74
|
+
if (!changeSetId) continue;
|
|
75
|
+
|
|
76
|
+
let set = byId.get(changeSetId);
|
|
77
|
+
if (!set) {
|
|
78
|
+
set = create(FileChangeSetSchema, { id: changeSetId });
|
|
79
|
+
byId.set(changeSetId, set);
|
|
80
|
+
order.push(changeSetId);
|
|
81
|
+
}
|
|
82
|
+
applyEvent(set, ev);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return order.map((id) => byId.get(id)!);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The single read seam for displaying an execution's file change sets.
|
|
90
|
+
*
|
|
91
|
+
* Returns the server-computed `status.file_change_sets` projection when it is
|
|
92
|
+
* present (a LIVE execution — the projection is structurally shared across
|
|
93
|
+
* stream frames, so consumers keep stable references and streaming re-renders
|
|
94
|
+
* stay scoped to the actively-changing card), and otherwise folds the ledger
|
|
95
|
+
* (a TERMINAL execution — the server projects nil there, but the ledger is
|
|
96
|
+
* preserved). The fold therefore never runs on the streaming hot path; it only
|
|
97
|
+
* reconstructs the settled sets a dead execution would otherwise not display.
|
|
98
|
+
*
|
|
99
|
+
* The two branches are equivalent by construction for a non-terminal execution
|
|
100
|
+
* (the fold minus its terminal gate IS the server projection — locked by the
|
|
101
|
+
* corpus parity test), so this accessor is a single consistent source whichever
|
|
102
|
+
* branch it takes.
|
|
103
|
+
*
|
|
104
|
+
* @param status - The execution status, or `undefined`.
|
|
105
|
+
* @returns The change sets to display, or an empty array when there are none.
|
|
106
|
+
*/
|
|
107
|
+
export function displayFileChangeSets(
|
|
108
|
+
status: AgentExecutionStatus | undefined,
|
|
109
|
+
): FileChangeSet[] {
|
|
110
|
+
const projected = status?.fileChangeSets;
|
|
111
|
+
if (projected && projected.length > 0) return projected;
|
|
112
|
+
return foldFileReviewEventStream(status?.fileReviewEventStream);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Applies one ledger event to its change-set accumulator, mirroring the Go
|
|
117
|
+
* projector's `applyEvent`. The switch is keyed on `event_type` (the coarse
|
|
118
|
+
* bucket the server keys on); the typed payload is read defensively so a
|
|
119
|
+
* malformed event degrades to zero values exactly as the Go nil-getters do,
|
|
120
|
+
* rather than throwing.
|
|
121
|
+
*/
|
|
122
|
+
function applyEvent(set: FileChangeSet, ev: FileReviewEvent): void {
|
|
123
|
+
switch (ev.eventType) {
|
|
124
|
+
case FileReviewEventType.BASELINE_CAPTURED: {
|
|
125
|
+
const b =
|
|
126
|
+
ev.payload.case === "baselineCaptured" ? ev.payload.value : undefined;
|
|
127
|
+
set.turnId = b?.turnId ?? "";
|
|
128
|
+
set.harnessId = b?.harnessId ?? "";
|
|
129
|
+
set.baselineSnapshot = b?.baselineSnapshot;
|
|
130
|
+
set.status = FileChangeSetStatus.CAPTURING;
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
case FileReviewEventType.CANDIDATE_CAPTURED: {
|
|
135
|
+
const c =
|
|
136
|
+
ev.payload.case === "candidateCaptured" ? ev.payload.value : undefined;
|
|
137
|
+
set.candidateSnapshot = c?.candidateSnapshot;
|
|
138
|
+
set.changes = c?.changes ?? [];
|
|
139
|
+
set.aggregateDigest = c?.aggregateDigest ?? "";
|
|
140
|
+
set.diffCompleteness = c?.diffCompleteness ?? set.diffCompleteness;
|
|
141
|
+
set.status = deriveStatusAfterCandidate(set);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case FileReviewEventType.FILE_DECIDED: {
|
|
146
|
+
if (ev.payload.case === "fileDecided") {
|
|
147
|
+
set.decisions.push(ev.payload.value);
|
|
148
|
+
}
|
|
149
|
+
set.status = deriveStatusAfterCandidate(set);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case FileReviewEventType.RECONCILED: {
|
|
154
|
+
const r = ev.payload.case === "reconciled" ? ev.payload.value : undefined;
|
|
155
|
+
set.approvedSnapshot = r?.approvedSnapshot;
|
|
156
|
+
set.status = FileChangeSetStatus.RECONCILED;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case FileReviewEventType.FAILED: {
|
|
161
|
+
set.status = FileChangeSetStatus.FAILED;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// UNSPECIFIED / any future type: ignored, matching the Go projector.
|
|
166
|
+
default:
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Derives a change set's status after a candidate or decision event, mirroring
|
|
173
|
+
* the Go projector's `deriveStatusAfterCandidate`. Terminal statuses are sticky:
|
|
174
|
+
* a `FILE_DECIDED` arriving after `RECONCILED`/`FAILED` never reverts the set to
|
|
175
|
+
* an actionable state.
|
|
176
|
+
*/
|
|
177
|
+
function deriveStatusAfterCandidate(set: FileChangeSet): FileChangeSetStatus {
|
|
178
|
+
if (
|
|
179
|
+
set.status === FileChangeSetStatus.RECONCILED ||
|
|
180
|
+
set.status === FileChangeSetStatus.FAILED
|
|
181
|
+
) {
|
|
182
|
+
return set.status;
|
|
183
|
+
}
|
|
184
|
+
return isFullyDecided(set)
|
|
185
|
+
? FileChangeSetStatus.DECIDED
|
|
186
|
+
: FileChangeSetStatus.AWAITING_REVIEW;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Whether every captured change in the set has a verdict, mirroring the Go
|
|
191
|
+
* projector's `isFullyDecided`: a set with no captured changes is never fully
|
|
192
|
+
* decided; any CHANGE_SET-scoped decision covers all files; otherwise every
|
|
193
|
+
* change id must appear among the FILE-scoped decisions.
|
|
194
|
+
*/
|
|
195
|
+
function isFullyDecided(set: FileChangeSet): boolean {
|
|
196
|
+
if (set.changes.length === 0) return false;
|
|
197
|
+
|
|
198
|
+
const decidedFileIds = new Set<string>();
|
|
199
|
+
for (const decision of set.decisions) {
|
|
200
|
+
if (decision.scope === FileDecisionScope.CHANGE_SET) return true;
|
|
201
|
+
if (decision.scope === FileDecisionScope.FILE) {
|
|
202
|
+
decidedFileIds.add(decision.fileChangeId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const change of set.changes) {
|
|
207
|
+
if (!decidedFileIds.has(change.id)) return false;
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Projects a CapturedFileChange (the file-review ledger/domain type) onto the
|
|
2
|
+
// FileChange display view-model that every diff renderer speaks.
|
|
3
|
+
//
|
|
4
|
+
// The two representations coexist by design (see the CapturedFileChange proto
|
|
5
|
+
// comment): CapturedFileChange is the apply-then-review domain type (always
|
|
6
|
+
// whole-file, byte-exact before/after, carries digests / capture_class /
|
|
7
|
+
// blocked_reason for enforcement), while FileChange is the *display superset* —
|
|
8
|
+
// it additionally models captureLevel and inline HUNK_ONLY diffs, which the
|
|
9
|
+
// still-live deny-gate approval produces directly. Because FileChange is the
|
|
10
|
+
// superset, this projection is lossless; the reverse would not be (a whole-file
|
|
11
|
+
// CapturedFileChange has no inline hunk to lose, but a HUNK_ONLY FileChange has
|
|
12
|
+
// no whole-file bytes). That asymmetry is exactly why display renders through
|
|
13
|
+
// FileChange and this adapter only ever runs one way.
|
|
14
|
+
//
|
|
15
|
+
// This is a pure derive-on-read projection from the single source (the ledger),
|
|
16
|
+
// not a stored second copy: it is called at render time by both FileReviewCard
|
|
17
|
+
// and useSessionFileChanges, never persisted.
|
|
18
|
+
//
|
|
19
|
+
// Framework-agnostic (no React) so it is shared across @stigmer/react,
|
|
20
|
+
// @stigmer/ink, and mirror-able by the Go CLI, alongside tool-view.ts.
|
|
21
|
+
|
|
22
|
+
import { create } from "@bufbuild/protobuf";
|
|
23
|
+
import type { CapturedFileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
|
|
24
|
+
import type { FileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
25
|
+
import { FileChangeSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
26
|
+
import {
|
|
27
|
+
FileChangeCaptureLevel,
|
|
28
|
+
FileChangeKind,
|
|
29
|
+
FileChangeType,
|
|
30
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Adapts a {@link CapturedFileChange} to the {@link FileChange} the diff
|
|
34
|
+
* renderers consume. The two reuse the same `FileContent` for before/after, so
|
|
35
|
+
* the bodies map directly; a capture is always WHOLE_FILE (the candidate carries
|
|
36
|
+
* the byte-exact sides), so the resulting `FileChange` renders as a true
|
|
37
|
+
* before/after diff.
|
|
38
|
+
*
|
|
39
|
+
* @param captured - A captured file change from a `FileChangeSet`.
|
|
40
|
+
* @returns The equivalent display-side {@link FileChange}.
|
|
41
|
+
*/
|
|
42
|
+
export function toDisplayFileChange(captured: CapturedFileChange): FileChange {
|
|
43
|
+
return create(FileChangeSchema, {
|
|
44
|
+
path: captured.pathAfter || captured.pathBefore,
|
|
45
|
+
changeType: toFileChangeType(captured.kind),
|
|
46
|
+
captureLevel: FileChangeCaptureLevel.WHOLE_FILE,
|
|
47
|
+
before: captured.before,
|
|
48
|
+
after: captured.after,
|
|
49
|
+
renameFrom:
|
|
50
|
+
captured.kind === FileChangeKind.RENAME ? captured.pathBefore : "",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Maps the ledger's {@link FileChangeKind} to the display {@link FileChangeType}. */
|
|
55
|
+
function toFileChangeType(kind: FileChangeKind): FileChangeType {
|
|
56
|
+
switch (kind) {
|
|
57
|
+
case FileChangeKind.ADD:
|
|
58
|
+
return FileChangeType.CREATE;
|
|
59
|
+
case FileChangeKind.DELETE:
|
|
60
|
+
return FileChangeType.DELETE;
|
|
61
|
+
case FileChangeKind.RENAME:
|
|
62
|
+
return FileChangeType.RENAME;
|
|
63
|
+
default:
|
|
64
|
+
return FileChangeType.MODIFY;
|
|
65
|
+
}
|
|
66
|
+
}
|