@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
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
// that presentation layers (React, Ink, the Go CLI's equivalent) render
|
|
9
9
|
// without re-parsing third-party engine formats.
|
|
10
10
|
//
|
|
11
|
+
// For file edits, normalizeToolResult prefers the runner's authoritative
|
|
12
|
+
// before/after capture (ToolCall.file_changes, #186) over reconstructing a diff
|
|
13
|
+
// from args, surfacing the capture's fidelity via the diff view's captureLevel so
|
|
14
|
+
// the renderer is honest about whole-file vs hunk-only. The capture is a clean,
|
|
15
|
+
// structured proto, so it is consumed proto-first rather than re-modeled here;
|
|
16
|
+
// the repeated/multi-file projection and offloaded-body fetching live in the
|
|
17
|
+
// React layer. Counts and unified diffs for a whole-file capture stay derivable
|
|
18
|
+
// by the presentation layer (the runner emits 0/"" sentinels for them), keeping
|
|
19
|
+
// one diff implementation as the source of truth.
|
|
20
|
+
//
|
|
11
21
|
// This module has no React or framework dependency so it can be shared by
|
|
12
22
|
// @stigmer/react and @stigmer/ink. The Go CLI mirrors it; the shared contract
|
|
13
23
|
// is test/fixtures/tool-view/. Engine result formats are version-fragile, so
|
|
@@ -15,9 +25,10 @@
|
|
|
15
25
|
|
|
16
26
|
import { ToolKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
17
27
|
import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
28
|
+
import { FileChangeCaptureLevel } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
18
29
|
import type { ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
19
30
|
|
|
20
|
-
export { ToolKind };
|
|
31
|
+
export { ToolKind, FileChangeCaptureLevel };
|
|
21
32
|
|
|
22
33
|
/** A single match in a search/grep result. */
|
|
23
34
|
export interface ToolSearchMatch {
|
|
@@ -46,10 +57,18 @@ export interface ToolContentBlock {
|
|
|
46
57
|
* showing the raw result.
|
|
47
58
|
*/
|
|
48
59
|
export type ToolResultView =
|
|
49
|
-
// File edit.
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
60
|
+
// File edit. oldText/newText are the two sides of the change: the authoritative
|
|
61
|
+
// whole-file before/after captured by the runner (ToolCall.file_changes) when
|
|
62
|
+
// available, otherwise the old_string/new_string fragments from args. The
|
|
63
|
+
// presentation layer computes the visual diff (and its line counts) from
|
|
64
|
+
// oldText/newText; linesAdded/linesRemoved/unifiedDiff are populated only when
|
|
65
|
+
// the source provides them directly (e.g. the Cursor hunk-only envelope).
|
|
66
|
+
//
|
|
67
|
+
// captureLevel reports the fidelity of oldText/newText so the renderer can be
|
|
68
|
+
// honest about it: WHOLE_FILE (full-file before/after), HUNK_ONLY (Cursor's
|
|
69
|
+
// hunk diff, no whole-file content), or undefined (reconstructed from args, the
|
|
70
|
+
// legacy path). It is the one fact field presence alone cannot convey, since a
|
|
71
|
+
// whole-file capture and an args fragment both populate oldText/newText.
|
|
53
72
|
| {
|
|
54
73
|
readonly type: "diff";
|
|
55
74
|
readonly path: string;
|
|
@@ -58,6 +77,7 @@ export type ToolResultView =
|
|
|
58
77
|
readonly linesAdded?: number;
|
|
59
78
|
readonly linesRemoved?: number;
|
|
60
79
|
readonly unifiedDiff?: string;
|
|
80
|
+
readonly captureLevel?: FileChangeCaptureLevel;
|
|
61
81
|
}
|
|
62
82
|
// File read or full-file write. `content` is the file body (from result for
|
|
63
83
|
// read, from args for write).
|
|
@@ -68,14 +88,32 @@ export type ToolResultView =
|
|
|
68
88
|
readonly language?: string;
|
|
69
89
|
readonly truncated: boolean;
|
|
70
90
|
}
|
|
71
|
-
//
|
|
91
|
+
// A rendered terminal session. `command` is the command that produced this
|
|
92
|
+
// output (the prompt line) — it belongs here, not as a stray arg, because a
|
|
93
|
+
// "terminal" view models the whole session (prompt + output), not output
|
|
94
|
+
// alone. exitCode is parsed from the engine marker when present.
|
|
72
95
|
| {
|
|
73
96
|
readonly type: "terminal";
|
|
97
|
+
readonly command?: string;
|
|
74
98
|
readonly stdout: string;
|
|
75
99
|
readonly stderr: string;
|
|
76
100
|
readonly exitCode?: number;
|
|
77
101
|
}
|
|
78
|
-
|
|
102
|
+
// A search result. `kind` distinguishes the two shapes a search engine
|
|
103
|
+
// returns: `"files"` is a file-name search (glob/file_search) whose matches are
|
|
104
|
+
// paths to render as a file list; `"content"` is a grep-style search whose
|
|
105
|
+
// matches carry line text (and a `file`/`line` when the engine provides them)
|
|
106
|
+
// to render grouped by file. `count` is the authoritative total when the
|
|
107
|
+
// engine reports it (it can exceed `matches.length` when `truncated`).
|
|
108
|
+
// `kind`/`truncated` are optional so legacy/plain-text results (which only know
|
|
109
|
+
// a flat match list) stay valid without them.
|
|
110
|
+
| {
|
|
111
|
+
readonly type: "search";
|
|
112
|
+
readonly matches: readonly ToolSearchMatch[];
|
|
113
|
+
readonly count: number;
|
|
114
|
+
readonly kind?: "files" | "content";
|
|
115
|
+
readonly truncated?: boolean;
|
|
116
|
+
}
|
|
79
117
|
| { readonly type: "list"; readonly entries: readonly string[]; readonly count: number }
|
|
80
118
|
| {
|
|
81
119
|
readonly type: "contentBlocks";
|
|
@@ -86,11 +124,13 @@ export type ToolResultView =
|
|
|
86
124
|
| { readonly type: "json"; readonly value: unknown }
|
|
87
125
|
| { readonly type: "error"; readonly message: string }
|
|
88
126
|
// Output offloaded to artifact storage to keep the status payload small (e.g. a
|
|
89
|
-
// screenshot from a computer-use MCP server or a multi-MB dump). The bytes
|
|
90
|
-
//
|
|
127
|
+
// screenshot from a computer-use MCP server or a multi-MB dump). The bytes are
|
|
128
|
+
// resolved on demand from `storageKey` at view time (never from a baked URL,
|
|
129
|
+
// which expires); `preview` is a short inline head for non-image content.
|
|
91
130
|
| {
|
|
92
131
|
readonly type: "outputRef";
|
|
93
|
-
readonly
|
|
132
|
+
readonly storageKey: string;
|
|
133
|
+
readonly contentHash: string;
|
|
94
134
|
readonly isImage: boolean;
|
|
95
135
|
readonly mimeType: string;
|
|
96
136
|
readonly sizeBytes: number;
|
|
@@ -214,12 +254,15 @@ export function normalizeToolResult(toolCall: ToolCall): ToolResultView {
|
|
|
214
254
|
|
|
215
255
|
// Output was offloaded to artifact storage (too large to inline): the real
|
|
216
256
|
// bytes are no longer in `result` (which holds only a short head/label), so
|
|
217
|
-
// render from the reference instead of re-parsing the placeholder.
|
|
218
|
-
|
|
257
|
+
// render from the reference instead of re-parsing the placeholder. The view
|
|
258
|
+
// carries the stable `storageKey` so consumers resolve the bytes/URL on
|
|
259
|
+
// demand — the persisted URL was ephemeral and is no longer trusted.
|
|
260
|
+
if (toolCall.outputRef && toolCall.outputRef.storageKey) {
|
|
219
261
|
const ref = toolCall.outputRef;
|
|
220
262
|
return {
|
|
221
263
|
type: "outputRef",
|
|
222
|
-
|
|
264
|
+
storageKey: ref.storageKey,
|
|
265
|
+
contentHash: ref.contentHash,
|
|
223
266
|
isImage: ref.isImage,
|
|
224
267
|
mimeType: ref.mimeType,
|
|
225
268
|
sizeBytes: Number(ref.sizeBytes),
|
|
@@ -239,7 +282,7 @@ export function normalizeToolResult(toolCall: ToolCall): ToolResultView {
|
|
|
239
282
|
case ToolKind.FILE_DELETE:
|
|
240
283
|
return result ? { type: "text", text: result } : { type: "empty" };
|
|
241
284
|
case ToolKind.SHELL:
|
|
242
|
-
return normalizeShell(result);
|
|
285
|
+
return normalizeShell(result, args);
|
|
243
286
|
case ToolKind.SEARCH:
|
|
244
287
|
return normalizeSearch(result);
|
|
245
288
|
case ToolKind.LIST:
|
|
@@ -254,15 +297,17 @@ export function normalizeToolResult(toolCall: ToolCall): ToolResultView {
|
|
|
254
297
|
}
|
|
255
298
|
|
|
256
299
|
function normalizeEdit(args: Args, result: string): ToolResultView {
|
|
300
|
+
// The inline transcript diff for a file edit is reconstructed from the tool
|
|
301
|
+
// args (there is no captured `file_changes` mirror — apply-then-review renders
|
|
302
|
+
// captured diffs via FileReviewCard/FileChangeDiff, not here). The native
|
|
303
|
+
// engine returns prose with no diff, so the presentation layer computes the
|
|
304
|
+
// visual diff from the args fragments; the Cursor SDK returns a stringified
|
|
305
|
+
// envelope with precomputed diff stats.
|
|
257
306
|
const path = firstString(args, PATH_FIELDS) ?? "";
|
|
258
307
|
const oldText = firstString(args, OLD_TEXT_FIELDS);
|
|
259
308
|
const newText = firstString(args, NEW_TEXT_FIELDS);
|
|
260
309
|
|
|
261
|
-
|
|
262
|
-
// prefer those when present. The native engine returns prose with no diff, so
|
|
263
|
-
// the presentation layer computes the visual diff from oldText/newText.
|
|
264
|
-
const envelope = tryParseJson(result);
|
|
265
|
-
const value = isRecord(envelope) && isRecord(envelope.value) ? envelope.value : undefined;
|
|
310
|
+
const value = cursorEnvelopeValue(tryParseJson(result));
|
|
266
311
|
const linesAdded = value ? asNumber(value.linesAdded) : undefined;
|
|
267
312
|
const linesRemoved = value ? asNumber(value.linesRemoved) : undefined;
|
|
268
313
|
const unifiedDiff = value ? asString(value.diffString) : undefined;
|
|
@@ -279,6 +324,8 @@ function normalizeEdit(args: Args, result: string): ToolResultView {
|
|
|
279
324
|
}
|
|
280
325
|
|
|
281
326
|
function normalizeWrite(args: Args): ToolResultView {
|
|
327
|
+
// The inline transcript view for a file write is the proposed content from the
|
|
328
|
+
// tool args (captured diffs render via FileReviewCard, not here).
|
|
282
329
|
const path = firstString(args, PATH_FIELDS) ?? "";
|
|
283
330
|
const content = firstString(args, WRITE_CONTENT_FIELDS) ?? "";
|
|
284
331
|
return { type: "file", path, content, language: languageFromPath(path), truncated: false };
|
|
@@ -300,15 +347,27 @@ function normalizeRead(args: Args, result: string): ToolResultView {
|
|
|
300
347
|
// test/fixtures/tool-view/result-views.json so a format change fails one test.
|
|
301
348
|
const SHELL_EXIT_MARKER = /\n?\[Command (?:succeeded|failed with exit code (\d+))\]\s*$/;
|
|
302
349
|
|
|
303
|
-
function normalizeShell(result: string): ToolResultView {
|
|
304
|
-
//
|
|
350
|
+
function normalizeShell(result: string, args: Args): ToolResultView {
|
|
351
|
+
// The command is the session's prompt line. "command" matches the shell
|
|
352
|
+
// primaryField shared with the runner/Go classifier and both native and
|
|
353
|
+
// Cursor args, so no fallback fields are needed.
|
|
354
|
+
const command = firstString(args, ["command"]);
|
|
355
|
+
|
|
356
|
+
// Two shapes carry structured shell output: native sub-agent steps emit the
|
|
357
|
+
// {stdout,stderr,exitCode} record directly; the Cursor SDK nests it under a
|
|
358
|
+
// {status, value} envelope (the same envelope the edit tool uses). Read
|
|
359
|
+
// whichever is present so the real output surfaces instead of the raw JSON
|
|
360
|
+
// string. A failed shell never reaches here — normalizeToolResult routes
|
|
361
|
+
// TOOL_CALL_FAILED to the error view first — so only success is handled.
|
|
305
362
|
const parsed = tryParseJson(result);
|
|
306
|
-
|
|
363
|
+
const shell = hasShellFields(parsed) ? parsed : cursorEnvelopeValue(parsed);
|
|
364
|
+
if (hasShellFields(shell)) {
|
|
307
365
|
return {
|
|
308
366
|
type: "terminal",
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
367
|
+
command,
|
|
368
|
+
stdout: asString(shell.stdout) ?? "",
|
|
369
|
+
stderr: asString(shell.stderr) ?? "",
|
|
370
|
+
exitCode: asNumber(shell.exitCode),
|
|
312
371
|
};
|
|
313
372
|
}
|
|
314
373
|
|
|
@@ -317,28 +376,186 @@ function normalizeShell(result: string): ToolResultView {
|
|
|
317
376
|
const stdout = result.replace(SHELL_EXIT_MARKER, "");
|
|
318
377
|
// Group 1 is set only on failure; a matched marker without it is success (0).
|
|
319
378
|
const exitCode = marker[1] !== undefined ? Number(marker[1]) : 0;
|
|
320
|
-
return { type: "terminal", stdout, stderr: "", exitCode };
|
|
379
|
+
return { type: "terminal", command, stdout, stderr: "", exitCode };
|
|
321
380
|
}
|
|
322
381
|
|
|
323
|
-
return { type: "terminal", stdout: result, stderr: "" };
|
|
382
|
+
return { type: "terminal", command, stdout: result, stderr: "" };
|
|
324
383
|
}
|
|
325
384
|
|
|
326
385
|
// A grep-style match line, e.g. " 12: // TODO: fix".
|
|
327
386
|
const GREP_MATCH_LINE = /^\s*\d+[:\t]/;
|
|
328
387
|
|
|
388
|
+
// The Cursor SDK returns search results as a stringified JSON envelope, not the
|
|
389
|
+
// plain text the native harness emits. Two shapes are observed, both fixture-
|
|
390
|
+
// backed in test/fixtures/tool-view/result-views.json:
|
|
391
|
+
// - file-name search (Glob/file_search): the {status,value} envelope with
|
|
392
|
+
// value.files (paths), value.totalFiles, and the truncation flags.
|
|
393
|
+
// - grep / codebase search: {workspaceResults: {<path>: {type, output}}},
|
|
394
|
+
// where each workspace's output carries files and/or line-bearing matches.
|
|
395
|
+
// Anything we don't recognise degrades to the json view (readable) rather than
|
|
396
|
+
// being wrapped as a single fake match by the plain-text path below.
|
|
329
397
|
function normalizeSearch(result: string): ToolResultView {
|
|
398
|
+
const parsed = tryParseJson(result);
|
|
399
|
+
if (parsed !== undefined) {
|
|
400
|
+
const fromEnvelope = searchFromEnvelope(parsed);
|
|
401
|
+
if (fromEnvelope) {
|
|
402
|
+
return fromEnvelope;
|
|
403
|
+
}
|
|
404
|
+
// Recognised JSON shape but nothing searchable in it (or an unknown shape):
|
|
405
|
+
// a clean json/object view beats wrapping the raw string as one "match".
|
|
406
|
+
if (isRecord(parsed) || Array.isArray(parsed)) {
|
|
407
|
+
return { type: "json", value: parsed };
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return searchFromPlainText(result);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Projects a parsed Cursor search envelope into a search view, or null when the
|
|
415
|
+
// parsed JSON is not one of the recognised search shapes.
|
|
416
|
+
//
|
|
417
|
+
// Both shapes are wrapped in the Cursor {status,value} envelope (shared with
|
|
418
|
+
// edit/shell), so the payload lives under `value`: file-name search puts `files`
|
|
419
|
+
// there, grep/codebase search puts `workspaceResults` there. We read the inner
|
|
420
|
+
// value when present and fall back to the parsed object itself, so a future
|
|
421
|
+
// un-enveloped shape still resolves rather than dropping to the json fallback.
|
|
422
|
+
function searchFromEnvelope(parsed: unknown): ToolResultView | null {
|
|
423
|
+
const inner = cursorEnvelopeValue(parsed) ?? (isRecord(parsed) ? parsed : undefined);
|
|
424
|
+
if (!inner) return null;
|
|
425
|
+
|
|
426
|
+
const truncated =
|
|
427
|
+
Boolean(inner.clientTruncated) || Boolean(inner.ripgrepTruncated);
|
|
428
|
+
|
|
429
|
+
// file-name search (Glob/file_search): inner.files is an array of path strings.
|
|
430
|
+
if (Array.isArray(inner.files)) {
|
|
431
|
+
const matches = pathsToFileMatches(inner.files);
|
|
432
|
+
const count = asNumber(inner.totalFiles) ?? matches.length;
|
|
433
|
+
// Safety net: the engine reports results but we extracted none — the shape
|
|
434
|
+
// drifted (e.g. files are objects, not strings). Surface the raw JSON rather
|
|
435
|
+
// than a misleading "No files found" that hides real data. (Truncation always
|
|
436
|
+
// returns a non-empty page, so this never fires on a legitimately capped result.)
|
|
437
|
+
if (count > 0 && matches.length === 0) return null;
|
|
438
|
+
return { type: "search", matches, count, kind: "files", truncated };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// grep / codebase search: inner.workspaceResults maps a workspace path to its
|
|
442
|
+
// { type, output }. Flatten every workspace's output into one match list so a
|
|
443
|
+
// multi-root search reads as a single result. Truncation can be reported at the
|
|
444
|
+
// envelope level (passed in) or per-workspace (read inside).
|
|
445
|
+
if (isRecord(inner.workspaceResults)) {
|
|
446
|
+
return searchFromWorkspaceResults(inner.workspaceResults, truncated);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Flattens the per-workspace results of a grep/codebase search. Each workspace's
|
|
453
|
+
// `output` carries `files` (path matches) and/or `matches` (line-bearing content
|
|
454
|
+
// matches); both are collected, and `truncated` is OR-ed across workspaces.
|
|
455
|
+
//
|
|
456
|
+
// Returns null when the engine reports results (count > 0) but none could be
|
|
457
|
+
// extracted — a shape drift — so the caller degrades to the raw JSON view rather
|
|
458
|
+
// than silently rendering "No files found" over data that is actually there.
|
|
459
|
+
function searchFromWorkspaceResults(
|
|
460
|
+
workspaceResults: Record<string, unknown>,
|
|
461
|
+
envelopeTruncated: boolean,
|
|
462
|
+
): ToolResultView | null {
|
|
463
|
+
const fileMatches: ToolSearchMatch[] = [];
|
|
464
|
+
const contentMatches: ToolSearchMatch[] = [];
|
|
465
|
+
let reportedCount = 0;
|
|
466
|
+
let hasReportedCount = false;
|
|
467
|
+
let truncated = envelopeTruncated;
|
|
468
|
+
|
|
469
|
+
for (const ws of Object.values(workspaceResults)) {
|
|
470
|
+
const output = isRecord(ws) ? ws.output : undefined;
|
|
471
|
+
if (!isRecord(output)) continue;
|
|
472
|
+
|
|
473
|
+
if (Array.isArray(output.files)) {
|
|
474
|
+
fileMatches.push(...pathsToFileMatches(output.files));
|
|
475
|
+
}
|
|
476
|
+
if (Array.isArray(output.matches)) {
|
|
477
|
+
contentMatches.push(...toContentMatches(output.matches));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const count = asNumber(output.count);
|
|
481
|
+
if (count !== undefined) {
|
|
482
|
+
reportedCount += count;
|
|
483
|
+
hasReportedCount = true;
|
|
484
|
+
}
|
|
485
|
+
if (Boolean(output.clientTruncated) || Boolean(output.ripgrepTruncated)) {
|
|
486
|
+
truncated = true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Content matches are the richer signal, so when present they drive the view;
|
|
491
|
+
// otherwise the result is a file-name list.
|
|
492
|
+
const isContent = contentMatches.length > 0;
|
|
493
|
+
const matches = isContent ? contentMatches : fileMatches;
|
|
494
|
+
|
|
495
|
+
// Safety net: results were reported but none extracted — the per-workspace
|
|
496
|
+
// output shape drifted. Bail to the raw JSON view (see the caller) instead of
|
|
497
|
+
// hiding the data behind a "No matches".
|
|
498
|
+
if (hasReportedCount && reportedCount > 0 && matches.length === 0) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
type: "search",
|
|
504
|
+
matches,
|
|
505
|
+
count: hasReportedCount ? reportedCount : matches.length,
|
|
506
|
+
kind: isContent ? "content" : "files",
|
|
507
|
+
truncated,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Maps an engine-provided array of file paths into file-name matches. Both
|
|
512
|
+
// `file` and `text` are set to the path so a kind-unaware consumer (e.g. the Ink
|
|
513
|
+
// summary) still shows something useful. Non-string entries are skipped.
|
|
514
|
+
function pathsToFileMatches(files: readonly unknown[]): ToolSearchMatch[] {
|
|
515
|
+
const matches: ToolSearchMatch[] = [];
|
|
516
|
+
for (const f of files) {
|
|
517
|
+
if (typeof f === "string" && f.length > 0) {
|
|
518
|
+
matches.push({ file: f, text: f });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return matches;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Maps an engine-provided array of grep matches into content matches. Each entry
|
|
525
|
+
// is read defensively (the exact shape is engine-fragile): a `file`/`path` and a
|
|
526
|
+
// 1-based `line` are carried when present, and the line text is taken from the
|
|
527
|
+
// first available text field, falling back to the stringified entry.
|
|
528
|
+
function toContentMatches(rawMatches: readonly unknown[]): ToolSearchMatch[] {
|
|
529
|
+
const matches: ToolSearchMatch[] = [];
|
|
530
|
+
for (const m of rawMatches) {
|
|
531
|
+
if (typeof m === "string") {
|
|
532
|
+
if (m.length > 0) matches.push({ text: m });
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (!isRecord(m)) continue;
|
|
536
|
+
const file = asString(m.file) ?? asString(m.path);
|
|
537
|
+
const line = asNumber(m.line) ?? asNumber(m.lineNumber);
|
|
538
|
+
const text = asString(m.text) ?? asString(m.line_text) ?? asString(m.content) ?? "";
|
|
539
|
+
matches.push({ file, line, text });
|
|
540
|
+
}
|
|
541
|
+
return matches;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// The plain-text path for the native harness: grep emits " 12: match" lines;
|
|
545
|
+
// glob/semantic search emit one path per line.
|
|
546
|
+
function searchFromPlainText(result: string): ToolResultView {
|
|
330
547
|
const lines = nonEmptyLines(result);
|
|
331
548
|
const matchLines = lines.filter((l) => GREP_MATCH_LINE.test(l));
|
|
332
549
|
|
|
333
550
|
if (matchLines.length > 0) {
|
|
334
551
|
const matches = matchLines.map((l) => ({ text: l.trim() }));
|
|
335
|
-
return { type: "search", matches, count: matches.length };
|
|
552
|
+
return { type: "search", matches, count: matches.length, kind: "content" };
|
|
336
553
|
}
|
|
337
554
|
|
|
338
555
|
// Path/name results (glob, semantic search): each meaningful line is a match.
|
|
339
556
|
const entries = lines.filter((l) => !/^no (files|matches|results)/i.test(l));
|
|
340
|
-
const matches = entries.map((text) => ({ text }));
|
|
341
|
-
return { type: "search", matches, count: matches.length };
|
|
557
|
+
const matches = entries.map((text) => ({ file: text, text }));
|
|
558
|
+
return { type: "search", matches, count: matches.length, kind: "files" };
|
|
342
559
|
}
|
|
343
560
|
|
|
344
561
|
function normalizeList(result: string): ToolResultView {
|
|
@@ -417,6 +634,20 @@ function isRecord(v: unknown): v is Record<string, unknown> {
|
|
|
417
634
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
418
635
|
}
|
|
419
636
|
|
|
637
|
+
// The Cursor SDK wraps tool payloads in a {status, value} envelope. Returns the
|
|
638
|
+
// inner `value` record when present, so edit and shell read the envelope through
|
|
639
|
+
// one definition instead of each re-deriving its shape.
|
|
640
|
+
function cursorEnvelopeValue(parsed: unknown): Record<string, unknown> | undefined {
|
|
641
|
+
return isRecord(parsed) && isRecord(parsed.value) ? parsed.value : undefined;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// True when a record carries structured shell output (stdout/exitCode). Used to
|
|
645
|
+
// pick between the un-enveloped native shape and the Cursor envelope's inner
|
|
646
|
+
// value in normalizeShell.
|
|
647
|
+
function hasShellFields(v: unknown): v is Record<string, unknown> {
|
|
648
|
+
return isRecord(v) && ("stdout" in v || "exitCode" in v);
|
|
649
|
+
}
|
|
650
|
+
|
|
420
651
|
function asString(v: unknown): string | undefined {
|
|
421
652
|
return typeof v === "string" ? v : undefined;
|
|
422
653
|
}
|
|
@@ -8,7 +8,7 @@ import { createClient, type Client, type Transport } from "@connectrpc/connect";
|
|
|
8
8
|
import { AgentExecutionSchema, type AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
9
9
|
import { AgentExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/command_pb";
|
|
10
10
|
import { InteractionMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
11
|
-
import { AgentExecutionIdSchema, AgentExecutionUpdateStatusInputSchema, UpdateStatusResponseSchema, SubmitApprovalInputSchema, CancelAgentExecutionInputSchema, TerminateAgentExecutionInputSchema, RecoverAgentExecutionInputSchema, PauseAgentExecutionInputSchema, ResumeAgentExecutionInputSchema, UploadAttachmentRequestSchema, UploadAttachmentResponseSchema, ListAgentExecutionsRequestSchema, AgentExecutionListSchema, ListAgentExecutionsBySessionRequestSchema, GetArtifactDownloadUrlRequestSchema, GetArtifactDownloadUrlResponseSchema, GetArtifactContentRequestSchema, GetArtifactContentResponseSchema, GetExecutionUsageReportInputSchema, GetExecutionUsageReportOutputSchema, GetSessionUsageReportInputSchema, GetSessionUsageReportOutputSchema, GetAgentUsageReportInputSchema, GetAgentUsageReportOutputSchema, GetOrgUsageReportInputSchema, GetOrgUsageReportOutputSchema, GetAgentExecutionSummaryRequestSchema, AgentExecutionSummarySchema, type AgentExecutionUpdateStatusInput, type UpdateStatusResponse, type SubmitApprovalInput, type CancelAgentExecutionInput, type TerminateAgentExecutionInput, type RecoverAgentExecutionInput, type PauseAgentExecutionInput, type ResumeAgentExecutionInput, type UploadAttachmentRequest, type UploadAttachmentResponse, type ListAgentExecutionsRequest, type AgentExecutionList, type ListAgentExecutionsBySessionRequest, type GetArtifactDownloadUrlRequest, type GetArtifactDownloadUrlResponse, type GetArtifactContentRequest, type GetArtifactContentResponse, type GetExecutionUsageReportInput, type GetExecutionUsageReportOutput, type GetSessionUsageReportInput, type GetSessionUsageReportOutput, type GetAgentUsageReportInput, type GetAgentUsageReportOutput, type GetOrgUsageReportInput, type GetOrgUsageReportOutput, type GetAgentExecutionSummaryRequest, type AgentExecutionSummary } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
|
|
11
|
+
import { AgentExecutionIdSchema, AgentExecutionUpdateStatusInputSchema, UpdateStatusResponseSchema, SubmitApprovalInputSchema, SubmitFileDecisionInputSchema, CancelAgentExecutionInputSchema, TerminateAgentExecutionInputSchema, RecoverAgentExecutionInputSchema, PauseAgentExecutionInputSchema, ResumeAgentExecutionInputSchema, UploadAttachmentRequestSchema, UploadAttachmentResponseSchema, ListAgentExecutionsRequestSchema, AgentExecutionListSchema, ListAgentExecutionsBySessionRequestSchema, GetArtifactDownloadUrlRequestSchema, GetArtifactDownloadUrlResponseSchema, GetArtifactContentRequestSchema, GetArtifactContentResponseSchema, GetExecutionUsageReportInputSchema, GetExecutionUsageReportOutputSchema, GetSessionUsageReportInputSchema, GetSessionUsageReportOutputSchema, GetAgentUsageReportInputSchema, GetAgentUsageReportOutputSchema, GetOrgUsageReportInputSchema, GetOrgUsageReportOutputSchema, GetAgentExecutionSummaryRequestSchema, AgentExecutionSummarySchema, type AgentExecutionUpdateStatusInput, type UpdateStatusResponse, type SubmitApprovalInput, type SubmitFileDecisionInput, type CancelAgentExecutionInput, type TerminateAgentExecutionInput, type RecoverAgentExecutionInput, type PauseAgentExecutionInput, type ResumeAgentExecutionInput, type UploadAttachmentRequest, type UploadAttachmentResponse, type ListAgentExecutionsRequest, type AgentExecutionList, type ListAgentExecutionsBySessionRequest, type GetArtifactDownloadUrlRequest, type GetArtifactDownloadUrlResponse, type GetArtifactContentRequest, type GetArtifactContentResponse, type GetExecutionUsageReportInput, type GetExecutionUsageReportOutput, type GetSessionUsageReportInput, type GetSessionUsageReportOutput, type GetAgentUsageReportInput, type GetAgentUsageReportOutput, type GetOrgUsageReportInput, type GetOrgUsageReportOutput, type GetAgentExecutionSummaryRequest, type AgentExecutionSummary } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/io_pb";
|
|
12
12
|
import { AgentExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/query_pb";
|
|
13
13
|
import { AgentExecutionSpecSchema, ContextManagementConfigSchema, ExecutionConfigSchema, AttachmentSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
14
14
|
import { ExecutionValueSchema } from "@stigmer/protos/ai/stigmer/agentic/executioncontext/v1/spec_pb";
|
|
@@ -56,6 +56,12 @@ export class AgentExecutionClient {
|
|
|
56
56
|
} catch (e) { throw wrapError(e); }
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
async submitFileDecision(input: SubmitFileDecisionInput): Promise<AgentExecution> {
|
|
60
|
+
try {
|
|
61
|
+
return await this.command.submitFileDecision(input);
|
|
62
|
+
} catch (e) { throw wrapError(e); }
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
async cancel(input: CancelAgentExecutionInput): Promise<AgentExecution> {
|
|
60
66
|
try {
|
|
61
67
|
return await this.command.cancel(input);
|
package/src/gen/mcpserver.ts
CHANGED
|
@@ -189,6 +189,7 @@ export interface EnvVarDeclarationInput {
|
|
|
189
189
|
export interface ToolApprovalPolicyInput {
|
|
190
190
|
toolName?: string;
|
|
191
191
|
message?: string;
|
|
192
|
+
fromDestructiveHint?: boolean;
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
/** SDK input type for McpServerAuth. */
|
|
@@ -229,6 +230,7 @@ function buildToolApprovalPolicyProto(input: ToolApprovalPolicyInput) {
|
|
|
229
230
|
return Object.assign(create(ToolApprovalPolicySchema), stripUndefined({
|
|
230
231
|
toolName: input.toolName,
|
|
231
232
|
message: input.message,
|
|
233
|
+
fromDestructiveHint: input.fromDestructiveHint,
|
|
232
234
|
}));
|
|
233
235
|
}
|
|
234
236
|
|
|
@@ -10,7 +10,7 @@ import { ExecutionTarget } from "@stigmer/protos/ai/stigmer/agentic/session/v1/e
|
|
|
10
10
|
import { WorkflowExecutionSchema, type WorkflowExecution } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
11
11
|
import { WorkflowExecutionCommandController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/command_pb";
|
|
12
12
|
import { WorkflowExecutionEventSchema, type WorkflowExecutionEvent } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/event_pb";
|
|
13
|
-
import { WorkflowExecutionIdSchema, WorkflowExecutionUpdateStatusInputSchema, SubmitWorkflowApprovalInputSchema, SubmitWorkflowTaskApprovalInputSchema, SendSignalInputSchema, CancelWorkflowExecutionInputSchema, TerminateWorkflowExecutionInputSchema, RecoverWorkflowExecutionInputSchema, PauseWorkflowExecutionInputSchema, ResumeWorkflowExecutionInputSchema, ListWorkflowExecutionsRequestSchema, WorkflowExecutionListSchema, ListWorkflowExecutionsByWorkflowRequestSchema, SubscribeWorkflowExecutionRequestSchema, GetEventLogRequestSchema, GetEventLogResponseSchema, SubscribeEventsRequestSchema, GetExecutionSummaryRequestSchema, ExecutionSummarySchema, ListPendingApprovalsRequestSchema, PendingApprovalsListSchema, type WorkflowExecutionUpdateStatusInput, type SubmitWorkflowApprovalInput, type SubmitWorkflowTaskApprovalInput, type SendSignalInput, type CancelWorkflowExecutionInput, type TerminateWorkflowExecutionInput, type RecoverWorkflowExecutionInput, type PauseWorkflowExecutionInput, type ResumeWorkflowExecutionInput, type ListWorkflowExecutionsRequest, type WorkflowExecutionList, type ListWorkflowExecutionsByWorkflowRequest, type SubscribeWorkflowExecutionRequest, type GetEventLogRequest, type GetEventLogResponse, type SubscribeEventsRequest, type GetExecutionSummaryRequest, type ExecutionSummary, type ListPendingApprovalsRequest, type PendingApprovalsList } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
13
|
+
import { WorkflowExecutionIdSchema, WorkflowExecutionUpdateStatusInputSchema, SubmitWorkflowApprovalInputSchema, SubmitWorkflowFileDecisionInputSchema, SubmitWorkflowTaskApprovalInputSchema, SendSignalInputSchema, CancelWorkflowExecutionInputSchema, TerminateWorkflowExecutionInputSchema, RecoverWorkflowExecutionInputSchema, PauseWorkflowExecutionInputSchema, ResumeWorkflowExecutionInputSchema, ListWorkflowExecutionsRequestSchema, WorkflowExecutionListSchema, ListWorkflowExecutionsByWorkflowRequestSchema, SubscribeWorkflowExecutionRequestSchema, GetEventLogRequestSchema, GetEventLogResponseSchema, SubscribeEventsRequestSchema, GetExecutionSummaryRequestSchema, ExecutionSummarySchema, ListPendingApprovalsRequestSchema, PendingApprovalsListSchema, type WorkflowExecutionUpdateStatusInput, type SubmitWorkflowApprovalInput, type SubmitWorkflowFileDecisionInput, type SubmitWorkflowTaskApprovalInput, type SendSignalInput, type CancelWorkflowExecutionInput, type TerminateWorkflowExecutionInput, type RecoverWorkflowExecutionInput, type PauseWorkflowExecutionInput, type ResumeWorkflowExecutionInput, type ListWorkflowExecutionsRequest, type WorkflowExecutionList, type ListWorkflowExecutionsByWorkflowRequest, type SubscribeWorkflowExecutionRequest, type GetEventLogRequest, type GetEventLogResponse, type SubscribeEventsRequest, type GetExecutionSummaryRequest, type ExecutionSummary, type ListPendingApprovalsRequest, type PendingApprovalsList } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/io_pb";
|
|
14
14
|
import { WorkflowExecutionQueryController } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/query_pb";
|
|
15
15
|
import { WorkflowExecutionSpecSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/spec_pb";
|
|
16
16
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
@@ -51,6 +51,12 @@ export class WorkflowExecutionClient {
|
|
|
51
51
|
} catch (e) { throw wrapError(e); }
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
async submitFileDecision(input: SubmitWorkflowFileDecisionInput): Promise<WorkflowExecution> {
|
|
55
|
+
try {
|
|
56
|
+
return await this.command.submitFileDecision(input);
|
|
57
|
+
} catch (e) { throw wrapError(e); }
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
async submitWorkflowTaskApproval(input: SubmitWorkflowTaskApprovalInput): Promise<WorkflowExecution> {
|
|
55
61
|
try {
|
|
56
62
|
return await this.command.submitWorkflowTaskApproval(input);
|
package/src/index.ts
CHANGED
|
@@ -186,6 +186,7 @@ export { PENDING_SUBJECT, resolvedSubject } from "./session";
|
|
|
186
186
|
// Tool-call view model (framework-agnostic; shared by @stigmer/react and @stigmer/ink)
|
|
187
187
|
export {
|
|
188
188
|
ToolKind,
|
|
189
|
+
FileChangeCaptureLevel,
|
|
189
190
|
resolveToolKind,
|
|
190
191
|
resolveToolKindByName,
|
|
191
192
|
normalizeToolResult,
|
|
@@ -193,6 +194,17 @@ export {
|
|
|
193
194
|
type ToolSearchMatch,
|
|
194
195
|
type ToolContentBlock,
|
|
195
196
|
} from "./execution/tool-view";
|
|
197
|
+
export {
|
|
198
|
+
ApprovalPolicySource,
|
|
199
|
+
describeApprovalPolicySource,
|
|
200
|
+
isInformativePolicySource,
|
|
201
|
+
} from "./execution/approval-provenance";
|
|
202
|
+
export { isTerminalPhase } from "./execution/execution-phases";
|
|
203
|
+
export {
|
|
204
|
+
foldFileReviewEventStream,
|
|
205
|
+
displayFileChangeSets,
|
|
206
|
+
} from "./execution/file-review-fold";
|
|
207
|
+
export { toDisplayFileChange } from "./execution/to-display-file-change";
|
|
196
208
|
export { SkillClient, type SkillInput } from "./gen/skill";
|
|
197
209
|
export {
|
|
198
210
|
WorkflowClient,
|