@stigmer/runner 3.0.8-dev.20260613074252 → 3.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-cursor/hook-script.d.ts +23 -12
  3. package/dist/activities/execute-cursor/hook-script.js +85 -51
  4. package/dist/activities/execute-cursor/hook-script.js.map +1 -1
  5. package/dist/activities/execute-cursor/index.js +210 -79
  6. package/dist/activities/execute-cursor/index.js.map +1 -1
  7. package/dist/activities/execute-cursor/message-translator.d.ts +35 -0
  8. package/dist/activities/execute-cursor/message-translator.js +114 -6
  9. package/dist/activities/execute-cursor/message-translator.js.map +1 -1
  10. package/dist/activities/execute-cursor/prompt-builder.d.ts +25 -0
  11. package/dist/activities/execute-cursor/prompt-builder.js +54 -0
  12. package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
  13. package/dist/activities/execute-cursor/workspace-setup.d.ts +8 -2
  14. package/dist/activities/execute-cursor/workspace-setup.js +62 -30
  15. package/dist/activities/execute-cursor/workspace-setup.js.map +1 -1
  16. package/dist/activities/execute-deep-agent/index.js +14 -4
  17. package/dist/activities/execute-deep-agent/index.js.map +1 -1
  18. package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +0 -1
  19. package/dist/activities/execute-deep-agent/status-builder-shared.js +32 -8
  20. package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
  21. package/dist/activities/execute-deep-agent/status-builder.js +4 -5
  22. package/dist/activities/execute-deep-agent/status-builder.js.map +1 -1
  23. package/dist/activities/execute-deep-agent/streaming-v3.js +3 -4
  24. package/dist/activities/execute-deep-agent/streaming-v3.js.map +1 -1
  25. package/dist/activities/execute-deep-agent/streaming.d.ts +8 -0
  26. package/dist/activities/execute-deep-agent/streaming.js +3 -4
  27. package/dist/activities/execute-deep-agent/streaming.js.map +1 -1
  28. package/dist/activities/execute-deep-agent/subagent-tracker.js +4 -5
  29. package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
  30. package/dist/activities/execute-deep-agent/v3-status-builder.js +6 -5
  31. package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
  32. package/dist/config.d.ts +21 -0
  33. package/dist/config.js +12 -0
  34. package/dist/config.js.map +1 -1
  35. package/dist/in-flight.d.ts +35 -0
  36. package/dist/in-flight.js +61 -0
  37. package/dist/in-flight.js.map +1 -0
  38. package/dist/main.js +6 -3
  39. package/dist/main.js.map +1 -1
  40. package/dist/runner-manager.d.ts +2 -0
  41. package/dist/runner-manager.js +90 -29
  42. package/dist/runner-manager.js.map +1 -1
  43. package/dist/runner.d.ts +2 -0
  44. package/dist/runner.js +2 -0
  45. package/dist/runner.js.map +1 -1
  46. package/dist/shared/grpc-retry.d.ts +9 -20
  47. package/dist/shared/grpc-retry.js +9 -52
  48. package/dist/shared/grpc-retry.js.map +1 -1
  49. package/dist/shared/stall-watchdog.d.ts +68 -0
  50. package/dist/shared/stall-watchdog.js +102 -0
  51. package/dist/shared/stall-watchdog.js.map +1 -0
  52. package/dist/shared/status-offload.d.ts +84 -0
  53. package/dist/shared/status-offload.js +292 -0
  54. package/dist/shared/status-offload.js.map +1 -0
  55. package/dist/shared/status.d.ts +34 -3
  56. package/dist/shared/status.js +102 -9
  57. package/dist/shared/status.js.map +1 -1
  58. package/package.json +2 -2
  59. package/src/__tests__/config.test.ts +8 -0
  60. package/src/__tests__/in-flight.test.ts +84 -0
  61. package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
  62. package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
  63. package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +74 -0
  64. package/src/activities/execute-cursor/__tests__/hook-script.test.ts +90 -15
  65. package/src/activities/execute-cursor/__tests__/tool-result-image.test.ts +244 -0
  66. package/src/activities/execute-cursor/__tests__/workspace-setup.test.ts +53 -4
  67. package/src/activities/execute-cursor/hook-script.ts +85 -51
  68. package/src/activities/execute-cursor/index.ts +170 -35
  69. package/src/activities/execute-cursor/message-translator.ts +113 -6
  70. package/src/activities/execute-cursor/prompt-builder.ts +59 -0
  71. package/src/activities/execute-cursor/workspace-setup.ts +76 -44
  72. package/src/activities/execute-deep-agent/__tests__/index.test.ts +1 -0
  73. package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +66 -0
  74. package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +6 -3
  75. package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +70 -0
  76. package/src/activities/execute-deep-agent/index.ts +16 -4
  77. package/src/activities/execute-deep-agent/status-builder-shared.ts +27 -5
  78. package/src/activities/execute-deep-agent/status-builder.ts +3 -5
  79. package/src/activities/execute-deep-agent/streaming-v3.ts +4 -4
  80. package/src/activities/execute-deep-agent/streaming.ts +13 -4
  81. package/src/activities/execute-deep-agent/subagent-tracker.ts +4 -5
  82. package/src/activities/execute-deep-agent/v3-status-builder.ts +5 -5
  83. package/src/config.ts +27 -0
  84. package/src/in-flight.ts +71 -0
  85. package/src/main.ts +7 -2
  86. package/src/runner-manager.ts +127 -33
  87. package/src/runner.ts +6 -0
  88. package/src/shared/__tests__/artifact-storage.test.ts +1 -0
  89. package/src/shared/__tests__/grpc-retry-extended.test.ts +6 -144
  90. package/src/shared/__tests__/grpc-retry.test.ts +5 -134
  91. package/src/shared/__tests__/stall-watchdog.test.ts +193 -0
  92. package/src/shared/__tests__/status-offload.test.ts +256 -0
  93. package/src/shared/__tests__/status.test.ts +199 -0
  94. package/src/shared/grpc-retry.ts +9 -72
  95. package/src/shared/stall-watchdog.ts +122 -0
  96. package/src/shared/status-offload.ts +342 -0
  97. package/src/shared/status.ts +142 -8
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Size-bounding guard for the persisted AgentExecutionStatus payload.
3
+ *
4
+ * Tool outputs (an MCP screenshot's base64 image, a giant accessibility-tree
5
+ * dump, a multi-MB shell log, a huge file write) are stored inline in
6
+ * `ToolCall.result`/`args_preview`, which live in `status.messages` and are
7
+ * re-serialized whole on every `persistStatus` -> `updateStatus` gRPC call.
8
+ * Left unchecked, a single large result pushes the message past the server's
9
+ * 4 MiB gRPC receive cap; the call fails with `resource_exhausted`, progress
10
+ * stops persisting, and the live UI freezes mid-execution.
11
+ *
12
+ * This module enforces, at the single persist boundary, that the payload stays
13
+ * under the limit:
14
+ *
15
+ * 1. offloadOversizedToolOutputs — per-tool-call, SIZE-DRIVEN (not keyed on
16
+ * tool type): any result over the byte threshold is spilled to artifact
17
+ * storage and replaced with a short head plus a typed ToolCallOutputRef.
18
+ * Images are uploaded as their decoded bytes (so the UI can render an
19
+ * <img>); other large output is uploaded as text with a preview head.
20
+ * Idempotent and content-hash-deduped so the throttled, repeated persists
21
+ * (and result re-inflation by mergeToolCallEvent) upload each blob once.
22
+ *
23
+ * 2. enforceStatusSizeLimit — an aggregate, type-agnostic backstop that runs
24
+ * even when no artifact storage is available: if the encoded status still
25
+ * exceeds a soft cap (comfortably under 4 MiB), it elides the largest
26
+ * remaining inline fields in place until the payload fits.
27
+ *
28
+ * Both operate ONLY on what is persisted/streamed; the agent's working context
29
+ * is managed by the harness/SDK separately, so reasoning is unaffected.
30
+ */
31
+ import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
32
+ import type { ArtifactStorage } from "./artifact-storage.js";
33
+ /**
34
+ * A single tool output (result or args_preview) larger than this many bytes is
35
+ * offloaded to artifact storage instead of inlined into the persisted status.
36
+ * 256 KiB is generous for ordinary tool output yet far below the 4 MiB cap, so
37
+ * even a handful of large-but-sub-threshold results cannot aggregate past it.
38
+ */
39
+ export declare const INLINE_TOOL_OUTPUT_MAX_BYTES: number;
40
+ /** Head of an offloaded text result kept inline for an at-a-glance preview. */
41
+ export declare const TEXT_PREVIEW_HEAD_CHARS = 4000;
42
+ /**
43
+ * Soft cap for the whole encoded status. Kept comfortably under the 4 MiB gRPC
44
+ * receive limit so the aggregate backstop trims before the server ever rejects.
45
+ */
46
+ export declare const STATUS_PAYLOAD_SOFT_LIMIT_BYTES: number;
47
+ /**
48
+ * Tighter cap used only after the server has already rejected a payload as too
49
+ * large, to maximize the chance the retry succeeds.
50
+ */
51
+ export declare const STATUS_PAYLOAD_HARD_LIMIT_BYTES: number;
52
+ export interface ToolOutputOffloadContext {
53
+ readonly artifactStorage: ArtifactStorage;
54
+ readonly executionId: string;
55
+ /** Override the per-result byte threshold (tests use a small value). */
56
+ readonly maxInlineBytes?: number;
57
+ }
58
+ interface ImagePayload {
59
+ readonly base64: string;
60
+ readonly mimeType: string;
61
+ }
62
+ /**
63
+ * Best-effort extraction of an inline base64 image from a tool result string.
64
+ * Handles a raw data URL, MCP image content blocks ({type:"image", data,
65
+ * mimeType}) and OpenAI-style image_url blocks. Returns null for non-image or
66
+ * unparseable results so the caller falls back to text offload.
67
+ */
68
+ export declare function detectImagePayload(result: string): ImagePayload | null;
69
+ /**
70
+ * Offload every oversized tool result in the status to artifact storage,
71
+ * replacing the inline value with a short head + ToolCallOutputRef. Per-tool
72
+ * failures fall back to an inline truncation (a bounded result beats a failed
73
+ * persist) and never throw, so a storage hiccup cannot fail the execution.
74
+ */
75
+ export declare function offloadOversizedToolOutputs(status: AgentExecutionStatus, ctx: ToolOutputOffloadContext): Promise<void>;
76
+ /**
77
+ * Aggregate, type-agnostic backstop. If the encoded status exceeds
78
+ * `softLimitBytes`, elide the largest inline tool fields (and, as a last
79
+ * resort, message content) in place until it fits. Returns true if anything
80
+ * was elided. This guarantees a bounded payload even without artifact storage
81
+ * (e.g. offload disabled) or when many medium results sum past the limit.
82
+ */
83
+ export declare function enforceStatusSizeLimit(status: AgentExecutionStatus, softLimitBytes?: number): boolean;
84
+ export {};
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Size-bounding guard for the persisted AgentExecutionStatus payload.
3
+ *
4
+ * Tool outputs (an MCP screenshot's base64 image, a giant accessibility-tree
5
+ * dump, a multi-MB shell log, a huge file write) are stored inline in
6
+ * `ToolCall.result`/`args_preview`, which live in `status.messages` and are
7
+ * re-serialized whole on every `persistStatus` -> `updateStatus` gRPC call.
8
+ * Left unchecked, a single large result pushes the message past the server's
9
+ * 4 MiB gRPC receive cap; the call fails with `resource_exhausted`, progress
10
+ * stops persisting, and the live UI freezes mid-execution.
11
+ *
12
+ * This module enforces, at the single persist boundary, that the payload stays
13
+ * under the limit:
14
+ *
15
+ * 1. offloadOversizedToolOutputs — per-tool-call, SIZE-DRIVEN (not keyed on
16
+ * tool type): any result over the byte threshold is spilled to artifact
17
+ * storage and replaced with a short head plus a typed ToolCallOutputRef.
18
+ * Images are uploaded as their decoded bytes (so the UI can render an
19
+ * <img>); other large output is uploaded as text with a preview head.
20
+ * Idempotent and content-hash-deduped so the throttled, repeated persists
21
+ * (and result re-inflation by mergeToolCallEvent) upload each blob once.
22
+ *
23
+ * 2. enforceStatusSizeLimit — an aggregate, type-agnostic backstop that runs
24
+ * even when no artifact storage is available: if the encoded status still
25
+ * exceeds a soft cap (comfortably under 4 MiB), it elides the largest
26
+ * remaining inline fields in place until the payload fits.
27
+ *
28
+ * Both operate ONLY on what is persisted/streamed; the agent's working context
29
+ * is managed by the harness/SDK separately, so reasoning is unaffected.
30
+ */
31
+ import { createHash } from "node:crypto";
32
+ import { create, toBinary } from "@bufbuild/protobuf";
33
+ import { AgentExecutionStatusSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
34
+ import { ToolCallOutputRefSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
35
+ /**
36
+ * A single tool output (result or args_preview) larger than this many bytes is
37
+ * offloaded to artifact storage instead of inlined into the persisted status.
38
+ * 256 KiB is generous for ordinary tool output yet far below the 4 MiB cap, so
39
+ * even a handful of large-but-sub-threshold results cannot aggregate past it.
40
+ */
41
+ export const INLINE_TOOL_OUTPUT_MAX_BYTES = 256 * 1024;
42
+ /** Head of an offloaded text result kept inline for an at-a-glance preview. */
43
+ export const TEXT_PREVIEW_HEAD_CHARS = 4_000;
44
+ /**
45
+ * Soft cap for the whole encoded status. Kept comfortably under the 4 MiB gRPC
46
+ * receive limit so the aggregate backstop trims before the server ever rejects.
47
+ */
48
+ export const STATUS_PAYLOAD_SOFT_LIMIT_BYTES = 3 * 1024 * 1024;
49
+ /**
50
+ * Tighter cap used only after the server has already rejected a payload as too
51
+ * large, to maximize the chance the retry succeeds.
52
+ */
53
+ export const STATUS_PAYLOAD_HARD_LIMIT_BYTES = 2 * 1024 * 1024;
54
+ /** Marker left in place of an aggregate-elided inline field. */
55
+ const ELISION_MARKER = "[output elided to keep status under the size limit]";
56
+ /** Below this size an inline field is not worth eliding (the marker is ~50B). */
57
+ const ELISION_MIN_BYTES = 1_024;
58
+ function byteLen(s) {
59
+ return s ? Buffer.byteLength(s, "utf8") : 0;
60
+ }
61
+ function sha256(s) {
62
+ return createHash("sha256").update(s, "utf8").digest("hex");
63
+ }
64
+ function headChars(s, n) {
65
+ return s.length <= n ? s : s.slice(0, n);
66
+ }
67
+ function formatBytes(n) {
68
+ if (n >= 1024 * 1024)
69
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
70
+ if (n >= 1024)
71
+ return `${(n / 1024).toFixed(1)} KB`;
72
+ return `${n} B`;
73
+ }
74
+ function extFromMime(mimeType) {
75
+ switch (mimeType) {
76
+ case "image/png": return "png";
77
+ case "image/jpeg": return "jpg";
78
+ case "image/gif": return "gif";
79
+ case "image/webp": return "webp";
80
+ case "image/svg+xml": return "svg";
81
+ default: return "bin";
82
+ }
83
+ }
84
+ function matchDataUrl(s) {
85
+ const m = s.match(/^data:(image\/[a-zA-Z0-9.+-]+);base64,([\s\S]+)$/);
86
+ if (!m)
87
+ return null;
88
+ return { mimeType: m[1], base64: m[2].replace(/\s+/g, "") };
89
+ }
90
+ function contentBlocks(parsed) {
91
+ if (Array.isArray(parsed))
92
+ return parsed;
93
+ if (parsed && typeof parsed === "object") {
94
+ const obj = parsed;
95
+ if (Array.isArray(obj.content))
96
+ return obj.content;
97
+ // Defensive: a serialized LangChain ToolMessage envelope nests its blocks
98
+ // under kwargs.content. The deep-agent extractor already normalizes image
99
+ // results to a top-level array (status-builder-shared.ts), so this branch is
100
+ // insurance against future shape drift — not the primary path.
101
+ const kwargs = obj.kwargs;
102
+ if (kwargs && typeof kwargs === "object" && Array.isArray(kwargs.content)) {
103
+ return kwargs.content;
104
+ }
105
+ }
106
+ return [];
107
+ }
108
+ /**
109
+ * Best-effort extraction of an inline base64 image from a tool result string.
110
+ * Handles a raw data URL, MCP image content blocks ({type:"image", data,
111
+ * mimeType}) and OpenAI-style image_url blocks. Returns null for non-image or
112
+ * unparseable results so the caller falls back to text offload.
113
+ */
114
+ export function detectImagePayload(result) {
115
+ const direct = matchDataUrl(result.trim());
116
+ if (direct)
117
+ return direct;
118
+ let parsed;
119
+ try {
120
+ parsed = JSON.parse(result);
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ for (const block of contentBlocks(parsed)) {
126
+ if (!block || typeof block !== "object")
127
+ continue;
128
+ const obj = block;
129
+ const type = typeof obj.type === "string" ? obj.type : "";
130
+ if (type !== "image" && type !== "image_url")
131
+ continue;
132
+ const raw = typeof obj.data === "string" ? obj.data :
133
+ typeof obj.image === "string" ? obj.image :
134
+ undefined;
135
+ if (raw) {
136
+ const asUrl = matchDataUrl(raw);
137
+ if (asUrl)
138
+ return asUrl;
139
+ const mimeType = typeof obj.mimeType === "string" ? obj.mimeType :
140
+ typeof obj.mime_type === "string" ? obj.mime_type :
141
+ "image/png";
142
+ return { mimeType, base64: raw.replace(/\s+/g, "") };
143
+ }
144
+ const imageUrl = obj.image_url;
145
+ if (imageUrl && typeof imageUrl === "object") {
146
+ const url = imageUrl.url;
147
+ if (typeof url === "string") {
148
+ const asUrl = matchDataUrl(url);
149
+ if (asUrl)
150
+ return asUrl;
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ function collapsedResultFor(ref) {
157
+ if (ref.isImage) {
158
+ return `[image output — ${formatBytes(Number(ref.sizeBytes))}, view inline]`;
159
+ }
160
+ const tail = `\n\n[output truncated — ${formatBytes(Number(ref.sizeBytes))} total; view full output]`;
161
+ return ref.truncatedPreview + tail;
162
+ }
163
+ async function maybeOffloadToolCall(tc, ctx, maxBytes) {
164
+ const result = tc.result;
165
+ if (!result)
166
+ return;
167
+ const hash = sha256(result);
168
+ // Idempotent: the same content was already offloaded on a prior persist (or
169
+ // mergeToolCallEvent re-inflated `result` with identical bytes). Re-collapse
170
+ // the inline copy without re-uploading.
171
+ if (tc.outputRef && tc.outputRef.contentHash === hash) {
172
+ tc.result = collapsedResultFor(tc.outputRef);
173
+ return;
174
+ }
175
+ // Images are offloaded regardless of size, BEFORE the size gate below: a
176
+ // `ToolCallOutputRef` is the only path the UI has to render an image inline,
177
+ // so even a sub-threshold screenshot must be lifted out of `result` into a
178
+ // renderable ref. Non-image text only offloads when it exceeds maxBytes.
179
+ const image = detectImagePayload(result);
180
+ if (image) {
181
+ const bytes = Buffer.from(image.base64, "base64");
182
+ const key = `artifacts/${ctx.executionId}/toolcalls/${tc.id}.${extFromMime(image.mimeType)}`;
183
+ await ctx.artifactStorage.upload(key, bytes, image.mimeType);
184
+ const downloadUrl = await ctx.artifactStorage.getDownloadUrl(key);
185
+ tc.outputRef = create(ToolCallOutputRefSchema, {
186
+ storageKey: key,
187
+ downloadUrl,
188
+ sizeBytes: BigInt(bytes.length),
189
+ contentHash: hash,
190
+ mimeType: image.mimeType,
191
+ isImage: true,
192
+ truncatedPreview: "",
193
+ });
194
+ tc.result = collapsedResultFor(tc.outputRef);
195
+ return;
196
+ }
197
+ // Non-image text below the inline budget stays inline; only oversized text is
198
+ // spilled (with a head preview) to keep the persisted payload bounded.
199
+ if (byteLen(result) <= maxBytes)
200
+ return;
201
+ const content = Buffer.from(result, "utf8");
202
+ const key = `artifacts/${ctx.executionId}/toolcalls/${tc.id}.txt`;
203
+ await ctx.artifactStorage.upload(key, content, "text/plain");
204
+ const downloadUrl = await ctx.artifactStorage.getDownloadUrl(key);
205
+ tc.outputRef = create(ToolCallOutputRefSchema, {
206
+ storageKey: key,
207
+ downloadUrl,
208
+ sizeBytes: BigInt(content.length),
209
+ contentHash: hash,
210
+ mimeType: "text/plain",
211
+ isImage: false,
212
+ truncatedPreview: headChars(result, TEXT_PREVIEW_HEAD_CHARS),
213
+ });
214
+ tc.result = collapsedResultFor(tc.outputRef);
215
+ }
216
+ /**
217
+ * Offload every oversized tool result in the status to artifact storage,
218
+ * replacing the inline value with a short head + ToolCallOutputRef. Per-tool
219
+ * failures fall back to an inline truncation (a bounded result beats a failed
220
+ * persist) and never throw, so a storage hiccup cannot fail the execution.
221
+ */
222
+ export async function offloadOversizedToolOutputs(status, ctx) {
223
+ const maxBytes = ctx.maxInlineBytes ?? INLINE_TOOL_OUTPUT_MAX_BYTES;
224
+ for (const msg of status.messages) {
225
+ for (const tc of msg.toolCalls) {
226
+ try {
227
+ await maybeOffloadToolCall(tc, ctx, maxBytes);
228
+ }
229
+ catch (err) {
230
+ const original = tc.result ?? "";
231
+ tc.result =
232
+ headChars(original, TEXT_PREVIEW_HEAD_CHARS) +
233
+ `\n\n[output truncated — offload failed: ${err instanceof Error ? err.message : String(err)}]`;
234
+ console.warn(`[status-offload] execution=${ctx.executionId} tool=${tc.name} ` +
235
+ `offload failed (non-fatal); truncated inline`);
236
+ }
237
+ }
238
+ }
239
+ }
240
+ function encodedSize(status) {
241
+ return toBinary(AgentExecutionStatusSchema, status).length;
242
+ }
243
+ /**
244
+ * Aggregate, type-agnostic backstop. If the encoded status exceeds
245
+ * `softLimitBytes`, elide the largest inline tool fields (and, as a last
246
+ * resort, message content) in place until it fits. Returns true if anything
247
+ * was elided. This guarantees a bounded payload even without artifact storage
248
+ * (e.g. offload disabled) or when many medium results sum past the limit.
249
+ */
250
+ export function enforceStatusSizeLimit(status, softLimitBytes = STATUS_PAYLOAD_SOFT_LIMIT_BYTES) {
251
+ if (encodedSize(status) <= softLimitBytes)
252
+ return false;
253
+ const toolCalls = [];
254
+ for (const msg of status.messages) {
255
+ for (const tc of msg.toolCalls)
256
+ toolCalls.push(tc);
257
+ }
258
+ // Largest inline footprint first so we shed the most bytes per elision.
259
+ toolCalls.sort((a, b) => byteLen(b.result) + byteLen(b.argsPreview) -
260
+ (byteLen(a.result) + byteLen(a.argsPreview)));
261
+ let elidedAny = false;
262
+ for (const tc of toolCalls) {
263
+ if (encodedSize(status) <= softLimitBytes)
264
+ return elidedAny;
265
+ if (byteLen(tc.result) > ELISION_MIN_BYTES) {
266
+ tc.result = ELISION_MARKER;
267
+ elidedAny = true;
268
+ }
269
+ if (byteLen(tc.argsPreview) > ELISION_MIN_BYTES) {
270
+ tc.argsPreview = ELISION_MARKER;
271
+ elidedAny = true;
272
+ }
273
+ if (tc.args !== undefined) {
274
+ tc.args = undefined;
275
+ elidedAny = true;
276
+ }
277
+ }
278
+ // Last resort: oversized message content (e.g. a huge AI response).
279
+ if (encodedSize(status) > softLimitBytes) {
280
+ const byContent = [...status.messages].sort((a, b) => byteLen(b.content) - byteLen(a.content));
281
+ for (const msg of byContent) {
282
+ if (encodedSize(status) <= softLimitBytes)
283
+ break;
284
+ if (byteLen(msg.content) > ELISION_MIN_BYTES) {
285
+ msg.content = headChars(msg.content, TEXT_PREVIEW_HEAD_CHARS) + `\n\n${ELISION_MARKER}`;
286
+ elidedAny = true;
287
+ }
288
+ }
289
+ }
290
+ return elidedAny;
291
+ }
292
+ //# sourceMappingURL=status-offload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-offload.js","sourceRoot":"","sources":["../../src/shared/status-offload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,0BAA0B,GAC3B,MAAM,6DAA6D,CAAC;AAErE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iEAAiE,CAAC;AAI1G;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,GAAG,IAAI,CAAC;AAEvD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/D;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/D,gEAAgE;AAChE,MAAM,cAAc,GAAG,qDAAqD,CAAC;AAE7E,iFAAiF;AACjF,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAchC,SAAS,OAAO,CAAC,CAAqB;IACpC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAC,CAAS,EAAE,CAAS;IACrC,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,IAAI,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACpE,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IACpD,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,WAAW,CAAC,CAAC,OAAO,KAAK,CAAC;QAC/B,KAAK,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;QAChC,KAAK,WAAW,CAAC,CAAC,OAAO,KAAK,CAAC;QAC/B,KAAK,YAAY,CAAC,CAAC,OAAO,MAAM,CAAC;QACjC,KAAK,eAAe,CAAC,CAAC,OAAO,KAAK,CAAC;QACnC,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED,SAAS,aAAa,CAAC,MAAe;IACpC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC;QACnD,0EAA0E;QAC1E,0EAA0E;QAC1E,6EAA6E;QAC7E,+DAA+D;QAC/D,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAE,MAAkC,CAAC,OAAO,CAAC,EAAE,CAAC;YACvG,OAAQ,MAAkC,CAAC,OAAoB,CAAC;QAClE,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAClD,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,WAAW;YAAE,SAAS;QAEvD,MAAM,GAAG,GACP,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACzC,OAAO,GAAG,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC3C,SAAS,CAAC;QACZ,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,MAAM,QAAQ,GACZ,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACjD,OAAO,GAAG,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;oBACnD,WAAW,CAAC;YACd,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG,CAAC,SAAS,CAAC;QAC/B,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAI,QAAoC,CAAC,GAAG,CAAC;YACtD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,KAAK;oBAAE,OAAO,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAsE;IAChG,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAChB,OAAO,mBAAmB,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,gBAAgB,CAAC;IAC/E,CAAC;IACD,MAAM,IAAI,GAAG,2BAA2B,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC;IACtG,OAAO,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC;AACrC,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,EAAY,EACZ,GAA6B,EAC7B,QAAgB;IAEhB,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC;IACzB,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAE5B,4EAA4E;IAC5E,6EAA6E;IAC7E,wCAAwC;IACxC,IAAI,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;QACtD,EAAE,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,yEAAyE;IACzE,6EAA6E;IAC7E,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,aAAa,GAAG,CAAC,WAAW,cAAc,EAAE,CAAC,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7F,MAAM,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC7D,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAClE,EAAE,CAAC,SAAS,GAAG,MAAM,CAAC,uBAAuB,EAAE;YAC7C,UAAU,EAAE,GAAG;YACf,WAAW;YACX,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;YAC/B,WAAW,EAAE,IAAI;YACjB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,OAAO,EAAE,IAAI;YACb,gBAAgB,EAAE,EAAE;SACrB,CAAC,CAAC;QACH,EAAE,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,8EAA8E;IAC9E,uEAAuE;IACvE,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,QAAQ;QAAE,OAAO;IAExC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,aAAa,GAAG,CAAC,WAAW,cAAc,EAAE,CAAC,EAAE,MAAM,CAAC;IAClE,MAAM,GAAG,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAC7D,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAClE,EAAE,CAAC,SAAS,GAAG,MAAM,CAAC,uBAAuB,EAAE;QAC7C,UAAU,EAAE,GAAG;QACf,WAAW;QACX,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;QACjC,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,KAAK;QACd,gBAAgB,EAAE,SAAS,CAAC,MAAM,EAAE,uBAAuB,CAAC;KAC7D,CAAC,CAAC;IACH,EAAE,CAAC,MAAM,GAAG,kBAAkB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,MAA4B,EAC5B,GAA6B;IAE7B,MAAM,QAAQ,GAAG,GAAG,CAAC,cAAc,IAAI,4BAA4B,CAAC;IACpE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClC,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,oBAAoB,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAChD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC;gBACjC,EAAE,CAAC,MAAM;oBACP,SAAS,CAAC,QAAQ,EAAE,uBAAuB,CAAC;wBAC5C,2CAA2C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;gBACjG,OAAO,CAAC,IAAI,CACV,8BAA8B,GAAG,CAAC,WAAW,SAAS,EAAE,CAAC,IAAI,GAAG;oBAChE,8CAA8C,CAC/C,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,MAA4B;IAC/C,OAAO,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAA4B,EAC5B,iBAAyB,+BAA+B;IAExD,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,cAAc;QAAE,OAAO,KAAK,CAAC;IAExD,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClC,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS;YAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,wEAAwE;IACxE,SAAS,CAAC,IAAI,CACZ,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;QAC1C,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAC/C,CAAC;IAEF,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,cAAc;YAAE,OAAO,SAAS,CAAC;QAC5D,IAAI,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,iBAAiB,EAAE,CAAC;YAC3C,EAAE,CAAC,MAAM,GAAG,cAAc,CAAC;YAC3B,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QACD,IAAI,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,iBAAiB,EAAE,CAAC;YAChD,EAAE,CAAC,WAAW,GAAG,cAAc,CAAC;YAChC,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QACD,IAAI,EAAE,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC1B,EAAE,CAAC,IAAI,GAAG,SAAS,CAAC;YACpB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,cAAc,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CACzC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAClD,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC5B,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,cAAc;gBAAE,MAAM;YACjD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,iBAAiB,EAAE,CAAC;gBAC7C,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,uBAAuB,CAAC,GAAG,OAAO,cAAc,EAAE,CAAC;gBACxF,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -10,13 +10,44 @@
10
10
  import type { AgentExecutionStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
11
11
  import { ExecutionControlSignal } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
12
12
  import type { StigmerClient } from "../client/stigmer-client.js";
13
+ import { type ToolOutputOffloadContext } from "./status-offload.js";
14
+ import { type RetryOptions } from "./grpc-retry.js";
13
15
  export declare function utcTimestamp(): string;
16
+ /** Options controlling how a status payload is bounded and persisted. */
17
+ export interface PersistStatusOptions {
18
+ /**
19
+ * When supplied, oversized tool outputs (a screenshot's base64, a multi-MB
20
+ * dump) are spilled to artifact storage before persisting — the only path
21
+ * that populates a renderable `ToolCallOutputRef`. When omitted, the
22
+ * aggregate size backstop still runs, so the payload is bounded regardless.
23
+ */
24
+ readonly offload?: ToolOutputOffloadContext;
25
+ /**
26
+ * Transient-error backoff policy. Omitted fields fall back to
27
+ * {@link DEFAULT_PERSIST_RETRY}; an omitted `retry` uses it entirely.
28
+ */
29
+ readonly retry?: RetryOptions;
30
+ }
14
31
  /**
15
32
  * Persist execution status via gRPC. Returns the control signal from the
16
- * backend (e.g. STOP to abort the execution). Errors are logged and
17
- * swallowed — a failed status update should not crash the activity.
33
+ * backend (e.g. STOP to abort the execution).
34
+ *
35
+ * This is the single, unforgeable chokepoint for status persistence across
36
+ * every harness (streaming and terminal, native and Cursor). Routing all
37
+ * persistence through here guarantees three invariants that must never be
38
+ * skipped by an individual call site:
39
+ *
40
+ * 1. Size bounding — when `offload` is supplied, oversized tool outputs are
41
+ * spilled to artifact storage first (the path that makes screenshots
42
+ * render); an aggregate backstop then runs unconditionally so callers
43
+ * without storage are still protected from the 4 MiB gRPC cap.
44
+ * 2. Transient resilience — `Unavailable`/`DeadlineExceeded` back off and
45
+ * retry (default three attempts); terminal errors never retry.
46
+ * 3. Oversize recovery — a `resource_exhausted` rejection is never silently
47
+ * swallowed: we hard-elide and retry once, so an oversized payload can
48
+ * never freeze persistence invisibly (the bug that stalled the UI).
18
49
  */
19
- export declare function persistStatus(client: StigmerClient, executionId: string, status: AgentExecutionStatus): Promise<ExecutionControlSignal>;
50
+ export declare function persistStatus(client: StigmerClient, executionId: string, status: AgentExecutionStatus, options?: PersistStatusOptions): Promise<ExecutionControlSignal>;
20
51
  /**
21
52
  * Report a setup progress phase (e.g. "Resolving MCP servers") so the
22
53
  * frontend can display a spinner with context.
@@ -10,23 +10,116 @@
10
10
  import { create, toJson } from "@bufbuild/protobuf";
11
11
  import { AgentExecutionStatusSchema, SetupProgressSchema, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
12
12
  import { ExecutionControlSignal, ExecutionPhase, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
13
+ import { offloadOversizedToolOutputs, enforceStatusSizeLimit, STATUS_PAYLOAD_HARD_LIMIT_BYTES, } from "./status-offload.js";
14
+ import { isRetryableError, isTerminalError, } from "./grpc-retry.js";
13
15
  export function utcTimestamp() {
14
16
  return new Date().toISOString().replace("+00:00", "Z");
15
17
  }
18
+ /**
19
+ * A connect-rpc `resource_exhausted` (gRPC code 8) — the payload exceeded the
20
+ * server's receive cap. Matched by code and, defensively, by the message buf's
21
+ * size check emits ("exceeds maximum size").
22
+ */
23
+ function isPayloadTooLarge(err) {
24
+ if (typeof err === "object" && err !== null && "code" in err) {
25
+ if (err.code === 8)
26
+ return true;
27
+ }
28
+ const msg = err instanceof Error ? err.message : String(err);
29
+ return /resource_exhausted|exceeds maximum size/i.test(msg);
30
+ }
31
+ /**
32
+ * Default transient-retry policy: three attempts with exponential backoff.
33
+ * Applied to every persist so a transient drop (server restart, brief
34
+ * unavailability) recovers silently instead of dropping a status update.
35
+ */
36
+ const DEFAULT_PERSIST_RETRY = {
37
+ baseDelayMs: 100,
38
+ backoffFactor: 2,
39
+ maxRetries: 3,
40
+ };
41
+ function defaultDelay(ms) {
42
+ return new Promise((resolve) => setTimeout(resolve, ms));
43
+ }
16
44
  /**
17
45
  * Persist execution status via gRPC. Returns the control signal from the
18
- * backend (e.g. STOP to abort the execution). Errors are logged and
19
- * swallowed — a failed status update should not crash the activity.
46
+ * backend (e.g. STOP to abort the execution).
47
+ *
48
+ * This is the single, unforgeable chokepoint for status persistence across
49
+ * every harness (streaming and terminal, native and Cursor). Routing all
50
+ * persistence through here guarantees three invariants that must never be
51
+ * skipped by an individual call site:
52
+ *
53
+ * 1. Size bounding — when `offload` is supplied, oversized tool outputs are
54
+ * spilled to artifact storage first (the path that makes screenshots
55
+ * render); an aggregate backstop then runs unconditionally so callers
56
+ * without storage are still protected from the 4 MiB gRPC cap.
57
+ * 2. Transient resilience — `Unavailable`/`DeadlineExceeded` back off and
58
+ * retry (default three attempts); terminal errors never retry.
59
+ * 3. Oversize recovery — a `resource_exhausted` rejection is never silently
60
+ * swallowed: we hard-elide and retry once, so an oversized payload can
61
+ * never freeze persistence invisibly (the bug that stalled the UI).
20
62
  */
21
- export async function persistStatus(client, executionId, status) {
22
- try {
23
- const response = await client.updateStatus(executionId, status);
24
- return response.signal;
63
+ export async function persistStatus(client, executionId, status, options = {}) {
64
+ const { offload, retry } = options;
65
+ if (offload) {
66
+ try {
67
+ await offloadOversizedToolOutputs(status, offload);
68
+ }
69
+ catch (err) {
70
+ console.warn(`[persistStatus] ${executionId}: tool-output offload failed (non-fatal): ${err}`);
71
+ }
72
+ }
73
+ if (enforceStatusSizeLimit(status)) {
74
+ console.warn(`[persistStatus] ${executionId}: status exceeded the soft size limit; ` +
75
+ `elided oversized inline fields to fit under the gRPC cap`);
25
76
  }
26
- catch (err) {
27
- console.error(`Failed to persist status for ${executionId}:`, err);
28
- return ExecutionControlSignal.UNSPECIFIED;
77
+ const baseDelayMs = retry?.baseDelayMs ?? DEFAULT_PERSIST_RETRY.baseDelayMs;
78
+ const backoffFactor = retry?.backoffFactor ?? DEFAULT_PERSIST_RETRY.backoffFactor;
79
+ const maxRetries = retry?.maxRetries ?? DEFAULT_PERSIST_RETRY.maxRetries;
80
+ const delay = retry?.delayFn ?? defaultDelay;
81
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
82
+ try {
83
+ const response = await client.updateStatus(executionId, status);
84
+ return response.signal;
85
+ }
86
+ catch (err) {
87
+ // Oversize rejection is recoverable exactly once and out-of-band from the
88
+ // transient budget: hard-elide the largest inline fields and retry now.
89
+ if (isPayloadTooLarge(err)) {
90
+ console.error(`[persistStatus] ${executionId}: payload rejected as too large despite ` +
91
+ `the size guard; hard-eliding and retrying once`);
92
+ enforceStatusSizeLimit(status, STATUS_PAYLOAD_HARD_LIMIT_BYTES);
93
+ try {
94
+ const response = await client.updateStatus(executionId, status);
95
+ return response.signal;
96
+ }
97
+ catch (retryErr) {
98
+ console.error(`[persistStatus] ${executionId}: still failing after hard elide:`, retryErr);
99
+ return ExecutionControlSignal.UNSPECIFIED;
100
+ }
101
+ }
102
+ // Terminal errors (invalid argument, not found, permission denied) are
103
+ // deterministic — retrying cannot help.
104
+ if (isTerminalError(err)) {
105
+ console.error(`[persistStatus] ${executionId}: terminal error persisting status ` +
106
+ `(attempt ${attempt + 1}/${maxRetries + 1}): ${err}`);
107
+ return ExecutionControlSignal.UNSPECIFIED;
108
+ }
109
+ // Transient errors back off and retry until the budget is exhausted.
110
+ if (isRetryableError(err) && attempt < maxRetries) {
111
+ const delayMs = baseDelayMs * Math.pow(backoffFactor, attempt);
112
+ console.warn(`[persistStatus] ${executionId}: retryable error ` +
113
+ `(attempt ${attempt + 1}/${maxRetries + 1}, retry in ${delayMs}ms): ${err}`);
114
+ await delay(delayMs);
115
+ continue;
116
+ }
117
+ // Non-retryable, non-terminal (e.g. a bare Error), or retries exhausted.
118
+ console.error(`Failed to persist status for ${executionId}:`, err);
119
+ return ExecutionControlSignal.UNSPECIFIED;
120
+ }
29
121
  }
122
+ return ExecutionControlSignal.UNSPECIFIED;
30
123
  }
31
124
  /**
32
125
  * Report a setup progress phase (e.g. "Resolving MCP servers") so the
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,6DAA6D,CAAC;AAErE,OAAO,EACL,sBAAsB,EACtB,cAAc,GACf,MAAM,8DAA8D,CAAC;AAGtE,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAqB,EACrB,WAAmB,EACnB,MAA4B;IAE5B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChE,OAAO,QAAQ,CAAC,MAAM,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,gCAAgC,WAAW,GAAG,EAAE,GAAG,CAAC,CAAC;QACnE,OAAO,sBAAsB,CAAC,WAAW,CAAC;IAC5C,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAqB,EACrB,WAAmB,EACnB,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,CAAC,0BAA0B,EAAE;QAChD,KAAK,EAAE,cAAc,CAAC,qBAAqB;QAC3C,aAAa,EAAE,MAAM,CAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;KACpE,CAAC,CAAC;IACH,MAAM,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,IAA0B;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,0BAA0B,EAAE;QAC9C,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;KACxC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAA+B,CAAC;QAChD,MAAM,QAAQ,GAAG,kBAAkB,IAAI,OAAO,CAAC;QAC/C,OAAO,CAAC,GAAG,CACT,+CAA+C;YAC/C,yCAAyC,QAAQ,IAAI;YACrD,cAAc,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC/C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/shared/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,GACpB,MAAM,6DAA6D,CAAC;AAErE,OAAO,EACL,sBAAsB,EACtB,cAAc,GACf,MAAM,8DAA8D,CAAC;AAEtE,OAAO,EACL,2BAA2B,EAC3B,sBAAsB,EACtB,+BAA+B,GAEhC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,gBAAgB,EAChB,eAAe,GAEhB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,GAAY;IACrC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAC7D,IAAK,GAA0B,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1D,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7D,OAAO,0CAA0C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC;AAkBD;;;;GAIG;AACH,MAAM,qBAAqB,GAAG;IAC5B,WAAW,EAAE,GAAG;IAChB,aAAa,EAAE,CAAC;IAChB,UAAU,EAAE,CAAC;CACL,CAAC;AAEX,SAAS,YAAY,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAqB,EACrB,WAAmB,EACnB,MAA4B,EAC5B,UAAgC,EAAE;IAElC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAEnC,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,2BAA2B,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,mBAAmB,WAAW,6CAA6C,GAAG,EAAE,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;IACD,IAAI,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CACV,mBAAmB,WAAW,yCAAyC;YACvE,0DAA0D,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,EAAE,WAAW,IAAI,qBAAqB,CAAC,WAAW,CAAC;IAC5E,MAAM,aAAa,GAAG,KAAK,EAAE,aAAa,IAAI,qBAAqB,CAAC,aAAa,CAAC;IAClF,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,IAAI,qBAAqB,CAAC,UAAU,CAAC;IACzE,MAAM,KAAK,GAAG,KAAK,EAAE,OAAO,IAAI,YAAY,CAAC;IAE7C,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;YAChE,OAAO,QAAQ,CAAC,MAAM,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,0EAA0E;YAC1E,wEAAwE;YACxE,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,OAAO,CAAC,KAAK,CACX,mBAAmB,WAAW,0CAA0C;oBACxE,gDAAgD,CACjD,CAAC;gBACF,sBAAsB,CAAC,MAAM,EAAE,+BAA+B,CAAC,CAAC;gBAChE,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;oBAChE,OAAO,QAAQ,CAAC,MAAM,CAAC;gBACzB,CAAC;gBAAC,OAAO,QAAQ,EAAE,CAAC;oBAClB,OAAO,CAAC,KAAK,CAAC,mBAAmB,WAAW,mCAAmC,EAAE,QAAQ,CAAC,CAAC;oBAC3F,OAAO,sBAAsB,CAAC,WAAW,CAAC;gBAC5C,CAAC;YACH,CAAC;YAED,uEAAuE;YACvE,wCAAwC;YACxC,IAAI,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CACX,mBAAmB,WAAW,qCAAqC;oBACnE,YAAY,OAAO,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,MAAM,GAAG,EAAE,CACrD,CAAC;gBACF,OAAO,sBAAsB,CAAC,WAAW,CAAC;YAC5C,CAAC;YAED,qEAAqE;YACrE,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBAClD,MAAM,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;gBAC/D,OAAO,CAAC,IAAI,CACV,mBAAmB,WAAW,oBAAoB;oBAClD,YAAY,OAAO,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,cAAc,OAAO,QAAQ,GAAG,EAAE,CAC5E,CAAC;gBACF,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,yEAAyE;YACzE,OAAO,CAAC,KAAK,CAAC,gCAAgC,WAAW,GAAG,EAAE,GAAG,CAAC,CAAC;YACnE,OAAO,sBAAsB,CAAC,WAAW,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,sBAAsB,CAAC,WAAW,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAqB,EACrB,WAAmB,EACnB,KAAa;IAEb,MAAM,MAAM,GAAG,MAAM,CAAC,0BAA0B,EAAE;QAChD,KAAK,EAAE,cAAc,CAAC,qBAAqB;QAC3C,aAAa,EAAE,MAAM,CAAC,mBAAmB,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;KACpE,CAAC,CAAC;IACH,MAAM,aAAa,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,IAA0B;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,0BAA0B,EAAE;QAC9C,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;KACxC,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,MAAM,CAAC,0BAA0B,EAAE,IAAI,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAA+B,CAAC;QAChD,MAAM,QAAQ,GAAG,kBAAkB,IAAI,OAAO,CAAC;QAC/C,OAAO,CAAC,GAAG,CACT,+CAA+C;YAC/C,yCAAyC,QAAQ,IAAI;YACrD,cAAc,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAC/C,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.0.8-dev.20260613074252",
3
+ "version": "3.0.8",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -86,7 +86,7 @@
86
86
  "@opentelemetry/resources": "^2.0.0",
87
87
  "@opentelemetry/sdk-trace-base": "^2.0.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
- "@stigmer/protos": "3.0.8-dev.20260613074252",
89
+ "@stigmer/protos": "3.0.8",
90
90
  "@temporalio/activity": "^1.11.0",
91
91
  "@temporalio/client": "^1.11.0",
92
92
  "@temporalio/common": "^1.11.0",
@@ -32,6 +32,14 @@ describe("loadConfig", () => {
32
32
  expect(config.cursorApiKey).toBe("test-key");
33
33
  expect(config.proxyEndpoint).toBeNull();
34
34
  expect(config.maxConcurrentActivities).toBe(5);
35
+ expect(config.cursorStreamStallTimeoutMs).toBe(180000);
36
+ });
37
+
38
+ it("respects CURSOR_STREAM_STALL_TIMEOUT_MS", () => {
39
+ process.env.CURSOR_API_KEY = "test-key";
40
+ process.env.CURSOR_STREAM_STALL_TIMEOUT_MS = "90000";
41
+ const config = loadConfig();
42
+ expect(config.cursorStreamStallTimeoutMs).toBe(90000);
35
43
  });
36
44
 
37
45
  it("respects STIGMER_TASK_QUEUE", () => {