@gajae-code/coding-agent 0.12.6 → 0.12.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.
- package/CHANGELOG.md +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
package/src/tools/output-meta.ts
CHANGED
|
@@ -15,80 +15,19 @@ import type { ImageContent, TextContent } from "@gajae-code/ai";
|
|
|
15
15
|
import { getDefault, type Settings } from "../config/settings";
|
|
16
16
|
import { formatGroupedDiagnosticMessages } from "../lsp/utils";
|
|
17
17
|
import type { Theme } from "../modes/theme/theme";
|
|
18
|
-
import {
|
|
18
|
+
import { sessionArtifactCapability } from "../session/session-manager";
|
|
19
19
|
import {
|
|
20
|
-
DEFAULT_ARTIFACT_MAX_BYTES,
|
|
21
20
|
formatMiddleElisionMarker,
|
|
22
|
-
hasCurrentExecutionArtifactProof,
|
|
23
21
|
type OutputSummary,
|
|
24
22
|
type ReadWindow,
|
|
25
23
|
type TruncationResult,
|
|
26
|
-
truncateHeadBytes,
|
|
27
24
|
truncateMiddle,
|
|
28
25
|
truncateTail,
|
|
29
26
|
} from "../session/streaming-output";
|
|
30
|
-
import { isValidArtifactId } from "../utils/artifact-id";
|
|
31
|
-
|
|
32
|
-
export { isValidArtifactId } from "../utils/artifact-id";
|
|
33
27
|
|
|
34
28
|
import { formatBytes, wrapBrackets } from "./render-utils";
|
|
35
29
|
import { renderError } from "./tool-errors";
|
|
36
30
|
|
|
37
|
-
const kCurrentExecutionArtifactProof = Symbol("OutputMeta.CurrentExecutionArtifactProof");
|
|
38
|
-
const pendingArtifactLookupsByOwner = new WeakMap<SessionArtifactCapability, Set<Promise<boolean>>>();
|
|
39
|
-
|
|
40
|
-
function bindCurrentExecutionArtifactProof(truncation: TruncationMeta, summary: OutputSummary): void {
|
|
41
|
-
const artifactId = truncation.artifactId;
|
|
42
|
-
if (!artifactId || !hasCurrentExecutionArtifactProof(summary, artifactId, summary.output)) return;
|
|
43
|
-
Object.defineProperty(truncation, kCurrentExecutionArtifactProof, {
|
|
44
|
-
value: { artifactId, payload: summary.output, consumed: false },
|
|
45
|
-
enumerable: false,
|
|
46
|
-
configurable: false,
|
|
47
|
-
writable: false,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function hasBoundCurrentExecutionArtifactProof(
|
|
52
|
-
truncation: TruncationMeta,
|
|
53
|
-
artifactId: string,
|
|
54
|
-
payload: string,
|
|
55
|
-
): boolean {
|
|
56
|
-
const proof = (
|
|
57
|
-
truncation as TruncationMeta & {
|
|
58
|
-
[kCurrentExecutionArtifactProof]?: { artifactId: string; payload: string; consumed: boolean };
|
|
59
|
-
}
|
|
60
|
-
)[kCurrentExecutionArtifactProof];
|
|
61
|
-
if (!proof || proof.artifactId !== artifactId || proof.payload !== payload || proof.consumed) return false;
|
|
62
|
-
proof.consumed = true;
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function pendingArtifactLookupsForOwner(capability: SessionArtifactCapability): Set<Promise<boolean>> {
|
|
67
|
-
let pending = pendingArtifactLookupsByOwner.get(capability);
|
|
68
|
-
if (!pending) {
|
|
69
|
-
pending = new Set();
|
|
70
|
-
pendingArtifactLookupsByOwner.set(capability, pending);
|
|
71
|
-
}
|
|
72
|
-
return pending;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function resolveArtifactForCurrentSessionBounded(
|
|
76
|
-
capability: SessionArtifactCapability | undefined,
|
|
77
|
-
artifactId: string,
|
|
78
|
-
): Promise<boolean> {
|
|
79
|
-
if (!capability) return false;
|
|
80
|
-
const pending = pendingArtifactLookupsForOwner(capability);
|
|
81
|
-
if (pending.size >= INLINE_ARTIFACT_PENDING_LIMIT) return false;
|
|
82
|
-
const lookup = Promise.resolve()
|
|
83
|
-
.then(async () => (await capability.getArtifactPath(artifactId)) !== null)
|
|
84
|
-
.catch(() => false);
|
|
85
|
-
pending.add(lookup);
|
|
86
|
-
void lookup.then(
|
|
87
|
-
() => pending.delete(lookup),
|
|
88
|
-
() => pending.delete(lookup),
|
|
89
|
-
);
|
|
90
|
-
return Promise.race([lookup, Bun.sleep(INLINE_ARTIFACT_SAVE_WAIT_MS).then(() => false)]);
|
|
91
|
-
}
|
|
92
31
|
/**
|
|
93
32
|
* Truncation metadata for the output notice.
|
|
94
33
|
*/
|
|
@@ -116,16 +55,8 @@ export interface TruncationMeta {
|
|
|
116
55
|
elidedLines?: number;
|
|
117
56
|
/** Artifact ID when output was persisted; completeness is tracked separately below. */
|
|
118
57
|
artifactId?: string;
|
|
119
|
-
/** Artifact existence was proven by a successful save or publisher receipt. */
|
|
120
|
-
artifactVerified?: boolean;
|
|
121
58
|
/** Bytes omitted from an artifact after its hard storage cap was reached. */
|
|
122
59
|
artifactTruncatedBytes?: number;
|
|
123
|
-
/** Bytes dropped before Bash received the native output stream. */
|
|
124
|
-
sourceTruncatedBytes?: number;
|
|
125
|
-
/** Exact source capture completeness could not be proven. */
|
|
126
|
-
sourceCaptureIncomplete?: boolean;
|
|
127
|
-
/** Bytes omitted from the visible output by per-line column capping. */
|
|
128
|
-
columnDroppedBytes?: number;
|
|
129
60
|
/** Bounded diagnostic when artifact writer creation, write, or finalization failed. */
|
|
130
61
|
artifactFailureDiagnostic?: string;
|
|
131
62
|
/** Next offset for pagination (head truncation only) */
|
|
@@ -166,8 +97,6 @@ export interface OutputMeta {
|
|
|
166
97
|
source?: SourceMeta;
|
|
167
98
|
diagnostics?: DiagnosticMeta;
|
|
168
99
|
limits?: LimitsMeta;
|
|
169
|
-
/** Diagnostics were omitted to preserve the configured inline byte cap. */
|
|
170
|
-
diagnosticsOmitted?: boolean;
|
|
171
100
|
}
|
|
172
101
|
|
|
173
102
|
// =============================================================================
|
|
@@ -179,9 +108,6 @@ export interface TruncationOptions {
|
|
|
179
108
|
startLine?: number;
|
|
180
109
|
totalFileLines?: number;
|
|
181
110
|
artifactId?: string;
|
|
182
|
-
artifactVerified?: boolean;
|
|
183
|
-
artifactFailureDiagnostic?: string;
|
|
184
|
-
sourceCaptureIncomplete?: boolean;
|
|
185
111
|
maxBytes?: number;
|
|
186
112
|
noticeOwner?: "body";
|
|
187
113
|
}
|
|
@@ -199,8 +125,6 @@ export interface TruncationTextOptions {
|
|
|
199
125
|
totalBytes?: number;
|
|
200
126
|
maxBytes?: number;
|
|
201
127
|
artifactId?: string;
|
|
202
|
-
artifactVerified?: boolean;
|
|
203
|
-
sourceCaptureIncomplete?: boolean;
|
|
204
128
|
noticeOwner?: "body";
|
|
205
129
|
}
|
|
206
130
|
|
|
@@ -223,17 +147,7 @@ export class OutputMetaBuilder {
|
|
|
223
147
|
truncation(result: TruncationResult, options: TruncationOptions): this {
|
|
224
148
|
if (!result.truncated) return this;
|
|
225
149
|
|
|
226
|
-
const {
|
|
227
|
-
direction,
|
|
228
|
-
startLine = 1,
|
|
229
|
-
totalFileLines,
|
|
230
|
-
artifactId,
|
|
231
|
-
artifactVerified,
|
|
232
|
-
artifactFailureDiagnostic,
|
|
233
|
-
sourceCaptureIncomplete,
|
|
234
|
-
noticeOwner,
|
|
235
|
-
maxBytes,
|
|
236
|
-
} = options;
|
|
150
|
+
const { direction, startLine = 1, totalFileLines, artifactId, noticeOwner, maxBytes } = options;
|
|
237
151
|
const outputLines = result.outputLines ?? result.totalLines;
|
|
238
152
|
const outputBytes = result.outputBytes ?? result.totalBytes;
|
|
239
153
|
const isMiddle = direction === "middle" || result.truncatedBy === "middle";
|
|
@@ -249,6 +163,12 @@ export class OutputMetaBuilder {
|
|
|
249
163
|
if (isMiddle) {
|
|
250
164
|
const elidedLines = result.elidedLines ?? Math.max(0, effectiveTotalLines - outputLines);
|
|
251
165
|
const elidedBytes = result.elidedBytes ?? Math.max(0, result.totalBytes - outputBytes);
|
|
166
|
+
// Reconstruct head/tail line ranges. The kept output spans the first
|
|
167
|
+
// `headLines` lines and the last `tailLines` lines of the source; lines
|
|
168
|
+
// in the middle (count == elidedLines) are dropped.
|
|
169
|
+
const keptLines = Math.max(0, outputLines - 1); // -1 for marker line
|
|
170
|
+
const headLines = Math.ceil(keptLines / 2);
|
|
171
|
+
const tailLines = keptLines - headLines;
|
|
252
172
|
this.#meta.truncation = {
|
|
253
173
|
direction: "middle",
|
|
254
174
|
truncatedBy: "middle",
|
|
@@ -258,14 +178,12 @@ export class OutputMetaBuilder {
|
|
|
258
178
|
outputLines,
|
|
259
179
|
outputBytes,
|
|
260
180
|
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
261
|
-
headRange:
|
|
262
|
-
tailRange:
|
|
181
|
+
headRange: headLines > 0 ? { start: 1, end: headLines } : undefined,
|
|
182
|
+
tailRange:
|
|
183
|
+
tailLines > 0 ? { start: effectiveTotalLines - tailLines + 1, end: effectiveTotalLines } : undefined,
|
|
263
184
|
elidedLines,
|
|
264
185
|
elidedBytes,
|
|
265
186
|
artifactId,
|
|
266
|
-
artifactVerified,
|
|
267
|
-
artifactFailureDiagnostic,
|
|
268
|
-
sourceCaptureIncomplete: sourceCaptureIncomplete || undefined,
|
|
269
187
|
};
|
|
270
188
|
return this;
|
|
271
189
|
}
|
|
@@ -290,11 +208,8 @@ export class OutputMetaBuilder {
|
|
|
290
208
|
outputLines,
|
|
291
209
|
outputBytes,
|
|
292
210
|
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
293
|
-
shownRange:
|
|
211
|
+
shownRange: { start: shownStart, end: shownEnd },
|
|
294
212
|
artifactId,
|
|
295
|
-
artifactVerified,
|
|
296
|
-
artifactFailureDiagnostic,
|
|
297
|
-
sourceCaptureIncomplete: sourceCaptureIncomplete || undefined,
|
|
298
213
|
nextOffset: direction === "head" ? shownEnd + 1 : undefined,
|
|
299
214
|
};
|
|
300
215
|
|
|
@@ -306,7 +221,6 @@ export class OutputMetaBuilder {
|
|
|
306
221
|
windows: ReadWindow,
|
|
307
222
|
options: {
|
|
308
223
|
artifactId?: string;
|
|
309
|
-
artifactVerified?: boolean;
|
|
310
224
|
noticeOwner?: "body";
|
|
311
225
|
maxBytes?: number;
|
|
312
226
|
rangeBase?: "file" | "window";
|
|
@@ -314,7 +228,7 @@ export class OutputMetaBuilder {
|
|
|
314
228
|
): this {
|
|
315
229
|
if (windows.kind === "full") return this;
|
|
316
230
|
|
|
317
|
-
const { artifactId,
|
|
231
|
+
const { artifactId, noticeOwner, rangeBase } = options;
|
|
318
232
|
const maxBytes = options.maxBytes ?? (windows as ReadWindow & { maxBytes?: number }).maxBytes;
|
|
319
233
|
const outputLinesOverride = (windows as ReadWindow & { outputLinesOverride?: number }).outputLinesOverride;
|
|
320
234
|
const outputBytesOverride = (windows as ReadWindow & { outputBytesOverride?: number }).outputBytesOverride;
|
|
@@ -350,7 +264,6 @@ export class OutputMetaBuilder {
|
|
|
350
264
|
sourceBytes: partialTail.sourceLineBytes,
|
|
351
265
|
},
|
|
352
266
|
artifactId,
|
|
353
|
-
artifactVerified,
|
|
354
267
|
};
|
|
355
268
|
return this;
|
|
356
269
|
}
|
|
@@ -369,7 +282,6 @@ export class OutputMetaBuilder {
|
|
|
369
282
|
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
370
283
|
shownRange: { start: head.origin.startLine, end: head.origin.endLine },
|
|
371
284
|
artifactId,
|
|
372
|
-
artifactVerified,
|
|
373
285
|
nextOffset: head.origin.endLine + 1,
|
|
374
286
|
};
|
|
375
287
|
return this;
|
|
@@ -389,7 +301,6 @@ export class OutputMetaBuilder {
|
|
|
389
301
|
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
390
302
|
shownRange: { start: tail.origin.startLine, end: tail.origin.endLine },
|
|
391
303
|
artifactId,
|
|
392
|
-
artifactVerified,
|
|
393
304
|
};
|
|
394
305
|
return this;
|
|
395
306
|
}
|
|
@@ -408,12 +319,10 @@ export class OutputMetaBuilder {
|
|
|
408
319
|
outputBytes: head.bytes + tail.bytes + Buffer.byteLength(marker, "utf-8") + 2,
|
|
409
320
|
...(maxBytes !== undefined ? { maxBytes } : {}),
|
|
410
321
|
headRange: { start: head.origin.startLine, end: head.origin.endLine },
|
|
411
|
-
tailRange:
|
|
412
|
-
tail.kind === "partial-line" ? undefined : { start: tail.origin.startLine, end: tail.origin.endLine },
|
|
322
|
+
tailRange: { start: tail.origin.startLine, end: tail.origin.endLine },
|
|
413
323
|
elidedLines: windows.elidedLines,
|
|
414
324
|
elidedBytes: windows.elidedBytes,
|
|
415
325
|
artifactId,
|
|
416
|
-
artifactVerified,
|
|
417
326
|
};
|
|
418
327
|
return this;
|
|
419
328
|
}
|
|
@@ -421,45 +330,14 @@ export class OutputMetaBuilder {
|
|
|
421
330
|
/** Add truncation info from OutputSummary. No-op if not truncated or artifact evidence is absent. */
|
|
422
331
|
truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this {
|
|
423
332
|
const artifactFailureDiagnostic = summary.artifactFailureDiagnostic;
|
|
424
|
-
const artifactLossValid = isValidLossCounter(summary.artifactTruncatedBytes);
|
|
425
|
-
const sourceLossValid = isValidLossCounter(summary.sourceTruncatedBytes);
|
|
426
|
-
const columnLossValid =
|
|
427
|
-
isValidLossCounter(summary.columnDroppedBytes) &&
|
|
428
|
-
isValidLossCounter(summary.columnTruncatedLines) &&
|
|
429
|
-
isValidLossCounter(summary.columnMax);
|
|
430
|
-
const sourceCaptureFlagValid =
|
|
431
|
-
summary.sourceCaptureIncomplete === undefined || typeof summary.sourceCaptureIncomplete === "boolean";
|
|
432
333
|
const artifactTruncatedBytes =
|
|
433
|
-
|
|
334
|
+
summary.artifactTruncatedBytes != null && summary.artifactTruncatedBytes > 0
|
|
434
335
|
? summary.artifactTruncatedBytes
|
|
435
336
|
: undefined;
|
|
436
|
-
const sourceTruncatedBytes =
|
|
437
|
-
sourceLossValid && summary.sourceTruncatedBytes != null && summary.sourceTruncatedBytes > 0
|
|
438
|
-
? summary.sourceTruncatedBytes
|
|
439
|
-
: undefined;
|
|
440
|
-
const columnDroppedBytes =
|
|
441
|
-
columnLossValid && summary.columnDroppedBytes != null && summary.columnDroppedBytes > 0
|
|
442
|
-
? summary.columnDroppedBytes
|
|
443
|
-
: undefined;
|
|
444
|
-
const sourceCaptureIncomplete =
|
|
445
|
-
summary.sourceCaptureIncomplete === true ||
|
|
446
|
-
!sourceCaptureFlagValid ||
|
|
447
|
-
!artifactLossValid ||
|
|
448
|
-
!sourceLossValid ||
|
|
449
|
-
!columnLossValid
|
|
450
|
-
? true
|
|
451
|
-
: undefined;
|
|
452
|
-
if (columnLossValid && (summary.columnTruncatedLines ?? 0) > 0 && (summary.columnMax ?? 0) > 0) {
|
|
453
|
-
this.columnTruncated(summary.columnMax ?? 0);
|
|
454
|
-
}
|
|
455
|
-
const sourceCoordinatesIncomplete =
|
|
456
|
-
sourceTruncatedBytes !== undefined || sourceCaptureIncomplete === true || columnDroppedBytes !== undefined;
|
|
457
337
|
const hasArtifactEvidence =
|
|
458
338
|
summary.artifactId !== undefined ||
|
|
459
339
|
artifactFailureDiagnostic !== undefined ||
|
|
460
|
-
artifactTruncatedBytes !== undefined
|
|
461
|
-
sourceTruncatedBytes !== undefined ||
|
|
462
|
-
sourceCaptureIncomplete !== undefined;
|
|
340
|
+
artifactTruncatedBytes !== undefined;
|
|
463
341
|
if (!summary.truncated && !hasArtifactEvidence) return this;
|
|
464
342
|
|
|
465
343
|
const { direction, startLine = 1, totalFileLines, noticeOwner } = options;
|
|
@@ -468,26 +346,18 @@ export class OutputMetaBuilder {
|
|
|
468
346
|
summary.artifactId !== undefined && summary.output.includes(`artifact://${summary.artifactId}`);
|
|
469
347
|
const bodyOwnsArtifact =
|
|
470
348
|
bodyHasArtifact &&
|
|
471
|
-
(artifactTruncatedBytes === undefined
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
summary.columnDroppedBytes === undefined
|
|
475
|
-
? true
|
|
476
|
-
: summary.artifactId !== undefined &&
|
|
477
|
-
summary.output.includes(
|
|
478
|
-
formatArtifactReference(
|
|
479
|
-
summary.artifactId,
|
|
480
|
-
artifactTruncatedBytes,
|
|
481
|
-
sourceTruncatedBytes,
|
|
482
|
-
sourceCaptureIncomplete,
|
|
483
|
-
),
|
|
484
|
-
));
|
|
349
|
+
(artifactTruncatedBytes === undefined ||
|
|
350
|
+
(summary.artifactId !== undefined &&
|
|
351
|
+
summary.output.includes(formatArtifactReference(summary.artifactId, artifactTruncatedBytes))));
|
|
485
352
|
const owner =
|
|
486
353
|
noticeOwner !== undefined ? { noticeOwner } : bodyOwnsArtifact ? { noticeOwner: "body" as const } : {};
|
|
487
354
|
|
|
488
355
|
// Middle elision: the sink retained head + tail with an elision marker.
|
|
489
356
|
if (summary.elidedBytes != null && summary.elidedBytes > 0) {
|
|
490
357
|
const elidedLines = summary.elidedLines ?? Math.max(0, totalLines - summary.outputLines);
|
|
358
|
+
const keptLines = Math.max(0, summary.outputLines - 1); // -1 for marker line
|
|
359
|
+
const headLines = Math.ceil(keptLines / 2);
|
|
360
|
+
const tailLines = keptLines - headLines;
|
|
491
361
|
this.#meta.truncation = {
|
|
492
362
|
direction: "middle",
|
|
493
363
|
truncatedBy: "middle",
|
|
@@ -496,19 +366,14 @@ export class OutputMetaBuilder {
|
|
|
496
366
|
totalBytes: summary.totalBytes,
|
|
497
367
|
outputLines: summary.outputLines,
|
|
498
368
|
outputBytes: summary.outputBytes,
|
|
499
|
-
headRange:
|
|
500
|
-
tailRange:
|
|
369
|
+
headRange: headLines > 0 ? { start: 1, end: headLines } : undefined,
|
|
370
|
+
tailRange: tailLines > 0 ? { start: totalLines - tailLines + 1, end: totalLines } : undefined,
|
|
501
371
|
elidedBytes: summary.elidedBytes,
|
|
502
372
|
elidedLines,
|
|
503
373
|
artifactId: summary.artifactId,
|
|
504
|
-
artifactVerified: summary.artifactVerified,
|
|
505
374
|
artifactTruncatedBytes,
|
|
506
|
-
sourceTruncatedBytes,
|
|
507
|
-
sourceCaptureIncomplete,
|
|
508
|
-
columnDroppedBytes,
|
|
509
375
|
artifactFailureDiagnostic,
|
|
510
376
|
};
|
|
511
|
-
bindCurrentExecutionArtifactProof(this.#meta.truncation, summary);
|
|
512
377
|
return this;
|
|
513
378
|
}
|
|
514
379
|
|
|
@@ -538,20 +403,12 @@ export class OutputMetaBuilder {
|
|
|
538
403
|
totalBytes: summary.totalBytes,
|
|
539
404
|
outputLines: summary.outputLines,
|
|
540
405
|
outputBytes: summary.outputBytes,
|
|
541
|
-
shownRange:
|
|
542
|
-
sourceCoordinatesIncomplete || (direction === "tail" && summary.lastLinePartial)
|
|
543
|
-
? undefined
|
|
544
|
-
: { start: shownStart, end: shownEnd },
|
|
406
|
+
shownRange: { start: shownStart, end: shownEnd },
|
|
545
407
|
artifactId: summary.artifactId,
|
|
546
|
-
artifactVerified: summary.artifactVerified,
|
|
547
408
|
artifactTruncatedBytes,
|
|
548
|
-
sourceTruncatedBytes,
|
|
549
|
-
sourceCaptureIncomplete,
|
|
550
|
-
columnDroppedBytes,
|
|
551
409
|
artifactFailureDiagnostic,
|
|
552
|
-
nextOffset: direction === "head"
|
|
410
|
+
nextOffset: direction === "head" ? shownEnd + 1 : undefined,
|
|
553
411
|
};
|
|
554
|
-
bindCurrentExecutionArtifactProof(this.#meta.truncation, summary);
|
|
555
412
|
|
|
556
413
|
return this;
|
|
557
414
|
}
|
|
@@ -596,13 +453,8 @@ export class OutputMetaBuilder {
|
|
|
596
453
|
outputBytes,
|
|
597
454
|
maxBytes: options.maxBytes,
|
|
598
455
|
artifactId: options.artifactId,
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
options.sourceCaptureIncomplete || options.direction === "middle"
|
|
602
|
-
? undefined
|
|
603
|
-
: { start: shownStart, end: shownEnd },
|
|
604
|
-
sourceCaptureIncomplete: options.sourceCaptureIncomplete || undefined,
|
|
605
|
-
nextOffset: options.direction === "head" && !options.sourceCaptureIncomplete ? shownEnd + 1 : undefined,
|
|
456
|
+
shownRange: { start: shownStart, end: shownEnd },
|
|
457
|
+
nextOffset: options.direction === "head" ? shownEnd + 1 : undefined,
|
|
606
458
|
};
|
|
607
459
|
|
|
608
460
|
return this;
|
|
@@ -697,196 +549,33 @@ export function formatFullOutputReference(artifactId: string): string {
|
|
|
697
549
|
return `Read artifact://${artifactId} for full output`;
|
|
698
550
|
}
|
|
699
551
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const INLINE_ARTIFACT_SAVE_WAIT_MS = 500;
|
|
706
|
-
const INLINE_ARTIFACT_PENDING_LIMIT = 64;
|
|
707
|
-
const pendingInlineArtifactSavesByOwner = new WeakMap<SessionArtifactCapability, Set<Promise<unknown>>>();
|
|
708
|
-
|
|
709
|
-
function pendingInlineArtifactSavesForOwner(capability: SessionArtifactCapability): Set<Promise<unknown>> {
|
|
710
|
-
let pending = pendingInlineArtifactSavesByOwner.get(capability);
|
|
711
|
-
if (!pending) {
|
|
712
|
-
pending = new Set<Promise<unknown>>();
|
|
713
|
-
pendingInlineArtifactSavesByOwner.set(capability, pending);
|
|
714
|
-
}
|
|
715
|
-
return pending;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
async function saveInlineArtifactBounded(
|
|
719
|
-
capability: SessionArtifactCapability,
|
|
720
|
-
save: () => Promise<string | null | undefined>,
|
|
721
|
-
resolvePath: (artifactId: string) => Promise<string | null>,
|
|
722
|
-
): Promise<
|
|
723
|
-
{ status: "ok"; artifactId: string; artifactTruncatedBytes?: number } | { status: "failed"; diagnostic: string }
|
|
724
|
-
> {
|
|
725
|
-
const pendingInlineArtifactSaves = pendingInlineArtifactSavesForOwner(capability);
|
|
726
|
-
if (pendingInlineArtifactSaves.size >= INLINE_ARTIFACT_PENDING_LIMIT) {
|
|
727
|
-
return { status: "failed", diagnostic: "pending artifact save limit reached for this session" };
|
|
728
|
-
}
|
|
729
|
-
const promise = Promise.resolve()
|
|
730
|
-
.then(save)
|
|
731
|
-
.then(async artifactId => {
|
|
732
|
-
if (!isValidArtifactId(artifactId)) {
|
|
733
|
-
return { status: "failed", diagnostic: "storage returned an invalid artifact id" } as const;
|
|
734
|
-
}
|
|
735
|
-
const artifactPath = await resolvePath(artifactId);
|
|
736
|
-
if (!artifactPath) {
|
|
737
|
-
return { status: "failed", diagnostic: "saved artifact is not protocol-resolvable" } as const;
|
|
738
|
-
}
|
|
739
|
-
return { status: "ok", artifactId } as const;
|
|
740
|
-
})
|
|
741
|
-
.catch(error => ({ status: "failed", diagnostic: boundArtifactFailureDiagnostic(error) }) as const);
|
|
742
|
-
pendingInlineArtifactSaves.add(promise);
|
|
743
|
-
void promise.then(
|
|
744
|
-
() => pendingInlineArtifactSaves.delete(promise),
|
|
745
|
-
() => pendingInlineArtifactSaves.delete(promise),
|
|
746
|
-
);
|
|
747
|
-
return Promise.race([
|
|
748
|
-
promise,
|
|
749
|
-
Bun.sleep(INLINE_ARTIFACT_SAVE_WAIT_MS).then(
|
|
750
|
-
() => ({ status: "failed", diagnostic: `did not settle within ${INLINE_ARTIFACT_SAVE_WAIT_MS}ms` }) as const,
|
|
751
|
-
),
|
|
752
|
-
]);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
function genericArtifactOmittedBytes(content: string): number | undefined {
|
|
756
|
-
const contentBytes = Buffer.byteLength(content, "utf-8");
|
|
757
|
-
if (contentBytes <= DEFAULT_ARTIFACT_MAX_BYTES) return undefined;
|
|
758
|
-
const retained = truncateHeadBytes(content, DEFAULT_ARTIFACT_MAX_BYTES);
|
|
759
|
-
return contentBytes - retained.bytes;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const ARTIFACT_FAILURE_PREVIEW_DEFAULT_BYTES = 8 * 1024;
|
|
763
|
-
|
|
764
|
-
export interface ArtifactFailurePreview {
|
|
765
|
-
text: string;
|
|
766
|
-
omittedBytes: number;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/** Build a bounded, UTF-8-safe preview when artifact storage cannot retain the full payload. */
|
|
770
|
-
export function createArtifactFailurePreview(
|
|
771
|
-
fullText: string,
|
|
772
|
-
maxInlineBytes: number | undefined,
|
|
773
|
-
diagnostic: string,
|
|
774
|
-
): ArtifactFailurePreview {
|
|
775
|
-
const totalBytes = Buffer.byteLength(fullText, "utf-8");
|
|
776
|
-
const previewLimit =
|
|
777
|
-
maxInlineBytes !== undefined && maxInlineBytes > 0 ? maxInlineBytes : ARTIFACT_FAILURE_PREVIEW_DEFAULT_BYTES;
|
|
778
|
-
const boundedDiagnostic = boundArtifactFailureDiagnostic(diagnostic);
|
|
779
|
-
let bodyBudget = Math.min(Math.max(0, totalBytes - 1), previewLimit);
|
|
780
|
-
for (let attempt = 0; attempt < 8; attempt++) {
|
|
781
|
-
const body = truncateTail(fullText, { maxBytes: bodyBudget }).content;
|
|
782
|
-
const bodyBytes = Buffer.byteLength(body, "utf-8");
|
|
783
|
-
const omittedBytes = Math.max(1, totalBytes - bodyBytes);
|
|
784
|
-
const marker = `\n\n[${omittedBytes} bytes omitted; no artifact available: ${boundedDiagnostic}]\n\n`;
|
|
785
|
-
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
786
|
-
if (bodyBytes + markerBytes <= previewLimit) {
|
|
787
|
-
return { text: `${body}${marker}`, omittedBytes };
|
|
788
|
-
}
|
|
789
|
-
bodyBudget = Math.max(0, Math.min(bodyBudget - 1, previewLimit - markerBytes));
|
|
790
|
-
}
|
|
791
|
-
const marker = `\n\n[${totalBytes} bytes omitted; no artifact available: ${boundedDiagnostic}]\n\n`;
|
|
792
|
-
return { text: truncateHeadBytes(marker, previewLimit).text, omittedBytes: totalBytes };
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/** Format an artifact reference without claiming completeness when capture or storage omitted bytes. */
|
|
796
|
-
export function formatArtifactReference(
|
|
797
|
-
artifactId: string,
|
|
798
|
-
artifactTruncatedBytes?: number,
|
|
799
|
-
sourceTruncatedBytes?: number,
|
|
800
|
-
sourceCaptureIncomplete?: boolean,
|
|
801
|
-
): string {
|
|
802
|
-
const omissions: string[] = [];
|
|
803
|
-
if (!isValidLossCounter(sourceTruncatedBytes) || !isValidLossCounter(artifactTruncatedBytes)) {
|
|
804
|
-
omissions.push("loss accounting was invalid");
|
|
805
|
-
}
|
|
806
|
-
if (sourceTruncatedBytes != null && sourceTruncatedBytes > 0) {
|
|
807
|
-
omissions.push(`at least ${formatBytes(sourceTruncatedBytes)} dropped before Bash capture`);
|
|
808
|
-
}
|
|
809
|
-
if (sourceCaptureIncomplete) {
|
|
810
|
-
omissions.push("source capture completeness could not be proven");
|
|
811
|
-
}
|
|
552
|
+
/**
|
|
553
|
+
* Format an artifact reference without claiming completeness when storage was hard-capped.
|
|
554
|
+
*/
|
|
555
|
+
export function formatArtifactReference(artifactId: string, artifactTruncatedBytes?: number): string {
|
|
812
556
|
if (artifactTruncatedBytes != null && artifactTruncatedBytes > 0) {
|
|
813
|
-
|
|
557
|
+
return `Read artifact://${artifactId} for retained output (at least ${formatBytes(artifactTruncatedBytes)} omitted by the artifact storage cap)`;
|
|
814
558
|
}
|
|
815
|
-
return
|
|
816
|
-
? `Read artifact://${artifactId} for retained output (${omissions.join("; ")})`
|
|
817
|
-
: formatFullOutputReference(artifactId);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
export interface ArtifactEvidence {
|
|
821
|
-
artifactId?: string;
|
|
822
|
-
artifactVerified?: boolean;
|
|
823
|
-
artifactTruncatedBytes?: number;
|
|
824
|
-
sourceTruncatedBytes?: number;
|
|
825
|
-
sourceCaptureIncomplete?: boolean;
|
|
826
|
-
artifactFailureDiagnostic?: string;
|
|
827
|
-
columnDroppedBytes?: number;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
export function formatArtifactEvidenceNotice(evidence: ArtifactEvidence): string | undefined {
|
|
831
|
-
const parts: string[] = [];
|
|
832
|
-
if (
|
|
833
|
-
isValidArtifactId(evidence.artifactId) &&
|
|
834
|
-
evidence.artifactVerified === true &&
|
|
835
|
-
!evidence.artifactFailureDiagnostic
|
|
836
|
-
) {
|
|
837
|
-
parts.push(
|
|
838
|
-
formatArtifactReference(
|
|
839
|
-
evidence.artifactId,
|
|
840
|
-
evidence.artifactTruncatedBytes,
|
|
841
|
-
evidence.sourceTruncatedBytes,
|
|
842
|
-
evidence.sourceCaptureIncomplete || (evidence.columnDroppedBytes ?? 0) > 0,
|
|
843
|
-
),
|
|
844
|
-
);
|
|
845
|
-
if ((evidence.columnDroppedBytes ?? 0) > 0) {
|
|
846
|
-
parts.push(`Visible line caps omitted ${formatBytes(evidence.columnDroppedBytes ?? 0)}`);
|
|
847
|
-
}
|
|
848
|
-
} else {
|
|
849
|
-
if (evidence.artifactId && evidence.artifactVerified !== true) {
|
|
850
|
-
parts.push("Artifact availability could not be proven");
|
|
851
|
-
}
|
|
852
|
-
if ((evidence.sourceTruncatedBytes ?? 0) > 0) {
|
|
853
|
-
parts.push(
|
|
854
|
-
`Bash capture omitted at least ${formatBytes(evidence.sourceTruncatedBytes ?? 0)} before artifact storage`,
|
|
855
|
-
);
|
|
856
|
-
}
|
|
857
|
-
if (evidence.sourceCaptureIncomplete) {
|
|
858
|
-
parts.push("Source capture completeness could not be proven before retained output was finalized");
|
|
859
|
-
}
|
|
860
|
-
if ((evidence.artifactTruncatedBytes ?? 0) > 0) {
|
|
861
|
-
parts.push(`Artifact storage omitted at least ${formatBytes(evidence.artifactTruncatedBytes ?? 0)}`);
|
|
862
|
-
}
|
|
863
|
-
if ((evidence.columnDroppedBytes ?? 0) > 0) {
|
|
864
|
-
parts.push(`Visible line caps omitted ${formatBytes(evidence.columnDroppedBytes ?? 0)}`);
|
|
865
|
-
}
|
|
866
|
-
if (parts.length > 0) {
|
|
867
|
-
parts.push("no artifact reference is available");
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
if (evidence.artifactFailureDiagnostic) {
|
|
871
|
-
parts.push(`Artifact storage failed: ${evidence.artifactFailureDiagnostic}`);
|
|
872
|
-
}
|
|
873
|
-
if (!evidence.artifactId && parts.length > 0 && !parts.includes("no artifact reference is available")) {
|
|
874
|
-
parts.push("no artifact reference is available");
|
|
875
|
-
}
|
|
876
|
-
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
559
|
+
return formatFullOutputReference(artifactId);
|
|
877
560
|
}
|
|
878
561
|
|
|
879
562
|
function formatTruncationArtifactNotice(truncation: TruncationMeta): string {
|
|
880
|
-
|
|
563
|
+
const reference = truncation.artifactId
|
|
564
|
+
? formatArtifactReference(truncation.artifactId, truncation.artifactTruncatedBytes)
|
|
565
|
+
: undefined;
|
|
566
|
+
if (truncation.artifactFailureDiagnostic) {
|
|
567
|
+
const failure = `Artifact storage failed: ${truncation.artifactFailureDiagnostic}`;
|
|
568
|
+
if (reference) return `${reference}; ${failure}`;
|
|
569
|
+
return failure;
|
|
570
|
+
}
|
|
571
|
+
if (reference) return reference;
|
|
572
|
+
return `Artifact storage omitted at least ${formatBytes(truncation.artifactTruncatedBytes ?? 0)}; no artifact reference is available`;
|
|
881
573
|
}
|
|
882
574
|
|
|
883
575
|
function hasArtifactNotice(truncation: TruncationMeta): boolean {
|
|
884
576
|
return (
|
|
885
577
|
truncation.artifactId != null ||
|
|
886
578
|
(truncation.artifactTruncatedBytes ?? 0) > 0 ||
|
|
887
|
-
(truncation.sourceTruncatedBytes ?? 0) > 0 ||
|
|
888
|
-
truncation.sourceCaptureIncomplete ||
|
|
889
|
-
(truncation.columnDroppedBytes ?? 0) > 0 ||
|
|
890
579
|
truncation.artifactFailureDiagnostic != null
|
|
891
580
|
);
|
|
892
581
|
}
|
|
@@ -899,16 +588,6 @@ function formatTruncationRangeTotal(truncation: TruncationMeta): string {
|
|
|
899
588
|
|
|
900
589
|
export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
|
901
590
|
const rangeTotal = formatTruncationRangeTotal(truncation);
|
|
902
|
-
if ((truncation.sourceTruncatedBytes ?? 0) > 0 || truncation.sourceCaptureIncomplete) {
|
|
903
|
-
let notice = `Showing ${truncation.outputLines} retained line${truncation.outputLines === 1 ? "" : "s"} from an incomplete Bash capture`;
|
|
904
|
-
if (truncation.truncatedBy === "bytes" && truncation.maxBytes != null) {
|
|
905
|
-
notice += ` (${formatBytes(truncation.maxBytes)} limit)`;
|
|
906
|
-
}
|
|
907
|
-
if (hasArtifactNotice(truncation)) {
|
|
908
|
-
notice += `. ${formatTruncationArtifactNotice(truncation)}`;
|
|
909
|
-
}
|
|
910
|
-
return notice;
|
|
911
|
-
}
|
|
912
591
|
if (truncation.partialLine) {
|
|
913
592
|
let notice = `Showing last ${formatBytes(truncation.partialLine.bytes)} of line ${truncation.partialLine.line} of ${rangeTotal}`;
|
|
914
593
|
if (truncation.partialLine.sourceBytes > truncation.partialLine.bytes) {
|
|
@@ -935,7 +614,7 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
|
|
|
935
614
|
} else if (headPart && tailPart) {
|
|
936
615
|
notice = `Showing ${headPart} and ${tailPart} of ${rangeTotal}; ${elidedLines.toLocaleString()} middle line${elidedLines === 1 ? "" : "s"} (${formatBytes(elidedBytes)}) elided`;
|
|
937
616
|
} else {
|
|
938
|
-
notice = `Showing
|
|
617
|
+
notice = `Showing ${truncation.outputLines} of ${rangeTotal}${truncation.rangeBase === "window" ? "" : " lines"}; ${formatBytes(elidedBytes)} middle bytes elided`;
|
|
939
618
|
}
|
|
940
619
|
if (hasArtifactNotice(truncation)) {
|
|
941
620
|
notice += `. ${formatTruncationArtifactNotice(truncation)}`;
|
|
@@ -1009,12 +688,8 @@ export function formatOutputNotice(meta: OutputMeta | undefined): string {
|
|
|
1009
688
|
let diagnosticsNotice = "";
|
|
1010
689
|
if (meta.diagnostics && meta.diagnostics.messages.length > 0) {
|
|
1011
690
|
const d = meta.diagnostics;
|
|
1012
|
-
|
|
1013
|
-
const bounded = truncateHeadBytes(grouped, 1024);
|
|
1014
|
-
const suffix = bounded.bytes < Buffer.byteLength(grouped, "utf-8") ? "\n[diagnostics truncated]" : "";
|
|
1015
|
-
diagnosticsNotice = `\n\nLSP Diagnostics (${d.summary}):\n${bounded.text}${suffix}`;
|
|
691
|
+
diagnosticsNotice = `\n\nLSP Diagnostics (${d.summary}):\n${formatGroupedDiagnosticMessages(d.messages)}`;
|
|
1016
692
|
}
|
|
1017
|
-
if (meta.diagnosticsOmitted) parts.push("Diagnostics omitted by inline result cap");
|
|
1018
693
|
|
|
1019
694
|
const notice = parts.length ? `\n\n[${parts.join(". ")}]` : "";
|
|
1020
695
|
return notice + diagnosticsNotice;
|
|
@@ -1116,15 +791,13 @@ function getSpillConfig(s: Settings | undefined) {
|
|
|
1116
791
|
| "tools.maxInlineResultBytes"
|
|
1117
792
|
| "tools.readArtifactSpillThreshold";
|
|
1118
793
|
const get = <P extends Path>(path: P) => s?.get(path) ?? getDefault(path);
|
|
1119
|
-
const configuredInlineKiB = get("tools.maxInlineResultBytes");
|
|
1120
|
-
const maxInlineBytes = configuredInlineKiB > 0 ? Math.max(1024, Math.floor(configuredInlineKiB * 1024)) : 0;
|
|
1121
794
|
return {
|
|
1122
795
|
threshold: get("tools.artifactSpillThreshold") * 1024,
|
|
1123
796
|
readThreshold: get("tools.readArtifactSpillThreshold") * 1024,
|
|
1124
797
|
tailBytes: get("tools.artifactTailBytes") * 1024,
|
|
1125
798
|
tailLines: get("tools.artifactTailLines"),
|
|
1126
799
|
headBytes: get("tools.artifactHeadBytes") * 1024,
|
|
1127
|
-
maxInlineBytes,
|
|
800
|
+
maxInlineBytes: get("tools.maxInlineResultBytes") * 1024,
|
|
1128
801
|
};
|
|
1129
802
|
}
|
|
1130
803
|
|
|
@@ -1164,107 +837,6 @@ export function resolveBashOutputSinkHeadBytes(s: Settings): number {
|
|
|
1164
837
|
return hasExplicitHeadBytes && configuredHeadBytes !== undefined ? configuredHeadBytes * 1024 : 0;
|
|
1165
838
|
}
|
|
1166
839
|
|
|
1167
|
-
function isValidLossCounter(value: number | undefined): boolean {
|
|
1168
|
-
return value === undefined || (Number.isSafeInteger(value) && value >= 0);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
function stripBodyArtifactReferences(result: AgentToolResult, artifactId: string): AgentToolResult["content"] {
|
|
1172
|
-
return result.content.map(block => {
|
|
1173
|
-
if (block.type !== "text") return block;
|
|
1174
|
-
const lines = block.text.split("\n");
|
|
1175
|
-
const kept = lines.filter(line => {
|
|
1176
|
-
const trimmed = line.trim();
|
|
1177
|
-
return !(
|
|
1178
|
-
(trimmed.startsWith("[raw output:") && trimmed.includes(`artifact://${artifactId}`)) ||
|
|
1179
|
-
trimmed.startsWith(`Read artifact://${artifactId} `)
|
|
1180
|
-
);
|
|
1181
|
-
});
|
|
1182
|
-
return kept.length === lines.length ? block : { ...block, text: kept.join("\n") };
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
async function sanitizeArtifactIdEvidence(
|
|
1187
|
-
result: AgentToolResult,
|
|
1188
|
-
context: AgentToolContext | undefined,
|
|
1189
|
-
): Promise<AgentToolResult> {
|
|
1190
|
-
const meta: OutputMeta | undefined = result.details?.meta;
|
|
1191
|
-
const truncation = meta?.truncation;
|
|
1192
|
-
if (!truncation) return result;
|
|
1193
|
-
const artifactId = truncation.artifactId;
|
|
1194
|
-
const artifactIdValid = artifactId === undefined || isValidArtifactId(artifactId);
|
|
1195
|
-
const countersValid =
|
|
1196
|
-
isValidLossCounter(truncation.artifactTruncatedBytes) &&
|
|
1197
|
-
isValidLossCounter(truncation.sourceTruncatedBytes) &&
|
|
1198
|
-
isValidLossCounter(truncation.columnDroppedBytes);
|
|
1199
|
-
const sourceCaptureFlagValid =
|
|
1200
|
-
truncation.sourceCaptureIncomplete === undefined || typeof truncation.sourceCaptureIncomplete === "boolean";
|
|
1201
|
-
const sourceCoordinatesIncomplete =
|
|
1202
|
-
!countersValid ||
|
|
1203
|
-
!sourceCaptureFlagValid ||
|
|
1204
|
-
truncation.sourceCaptureIncomplete === true ||
|
|
1205
|
-
(truncation.sourceTruncatedBytes ?? 0) > 0;
|
|
1206
|
-
const artifactCapability = artifactCapabilityForContext(context);
|
|
1207
|
-
const artifactResolvable =
|
|
1208
|
-
artifactId === undefined ? true : await resolveArtifactForCurrentSessionBounded(artifactCapability, artifactId);
|
|
1209
|
-
const resultPayload = result.content.map(block => (block.type === "text" ? block.text : "")).join("");
|
|
1210
|
-
const artifactProven =
|
|
1211
|
-
artifactId === undefined ||
|
|
1212
|
-
(truncation.artifactVerified === true &&
|
|
1213
|
-
hasBoundCurrentExecutionArtifactProof(truncation, artifactId, resultPayload) &&
|
|
1214
|
-
artifactResolvable);
|
|
1215
|
-
const artifactFailed = truncation.artifactFailureDiagnostic !== undefined;
|
|
1216
|
-
const invalidateArtifact = artifactId !== undefined && (!artifactIdValid || !artifactProven || artifactFailed);
|
|
1217
|
-
const staleFullClaim =
|
|
1218
|
-
artifactId !== undefined &&
|
|
1219
|
-
(sourceCoordinatesIncomplete ||
|
|
1220
|
-
(truncation.artifactTruncatedBytes ?? 0) > 0 ||
|
|
1221
|
-
(truncation.columnDroppedBytes ?? 0) > 0);
|
|
1222
|
-
if (
|
|
1223
|
-
artifactIdValid &&
|
|
1224
|
-
countersValid &&
|
|
1225
|
-
sourceCaptureFlagValid &&
|
|
1226
|
-
!sourceCoordinatesIncomplete &&
|
|
1227
|
-
!invalidateArtifact &&
|
|
1228
|
-
!staleFullClaim
|
|
1229
|
-
)
|
|
1230
|
-
return result;
|
|
1231
|
-
const diagnostic = !artifactIdValid
|
|
1232
|
-
? "existing metadata contained an invalid artifact id"
|
|
1233
|
-
: artifactFailed
|
|
1234
|
-
? truncation.artifactFailureDiagnostic
|
|
1235
|
-
: !artifactProven
|
|
1236
|
-
? artifactResolvable
|
|
1237
|
-
? "existing metadata did not prove artifact availability"
|
|
1238
|
-
: "existing artifact is not protocol-resolvable in the current session"
|
|
1239
|
-
: undefined;
|
|
1240
|
-
return {
|
|
1241
|
-
...result,
|
|
1242
|
-
content:
|
|
1243
|
-
artifactId && (invalidateArtifact || staleFullClaim)
|
|
1244
|
-
? stripBodyArtifactReferences(result, artifactId)
|
|
1245
|
-
: result.content,
|
|
1246
|
-
details: {
|
|
1247
|
-
...(result.details ?? {}),
|
|
1248
|
-
meta: {
|
|
1249
|
-
...meta,
|
|
1250
|
-
truncation: {
|
|
1251
|
-
...truncation,
|
|
1252
|
-
noticeOwner: invalidateArtifact || staleFullClaim ? undefined : truncation.noticeOwner,
|
|
1253
|
-
artifactId: invalidateArtifact ? undefined : artifactId,
|
|
1254
|
-
artifactVerified: invalidateArtifact ? false : truncation.artifactVerified,
|
|
1255
|
-
sourceCaptureIncomplete:
|
|
1256
|
-
!countersValid || !sourceCaptureFlagValid || truncation.sourceCaptureIncomplete || undefined,
|
|
1257
|
-
shownRange: sourceCoordinatesIncomplete ? undefined : truncation.shownRange,
|
|
1258
|
-
headRange: sourceCoordinatesIncomplete ? undefined : truncation.headRange,
|
|
1259
|
-
tailRange: sourceCoordinatesIncomplete ? undefined : truncation.tailRange,
|
|
1260
|
-
nextOffset: sourceCoordinatesIncomplete ? undefined : truncation.nextOffset,
|
|
1261
|
-
artifactFailureDiagnostic: diagnostic,
|
|
1262
|
-
},
|
|
1263
|
-
},
|
|
1264
|
-
},
|
|
1265
|
-
};
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
840
|
/**
|
|
1269
841
|
* Resolve the per-line column cap from session settings. Shared by streaming
|
|
1270
842
|
* executors (bash/python/ssh/eval via OutputSink) and the `read` tool's
|
|
@@ -1294,95 +866,35 @@ async function spillLargeResultToArtifact(
|
|
|
1294
866
|
return result;
|
|
1295
867
|
}
|
|
1296
868
|
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
);
|
|
869
|
+
const artifactCapability = artifactCapabilityForContext(context);
|
|
870
|
+
if (!artifactCapability) return result;
|
|
871
|
+
const { threshold, readThreshold, tailBytes, tailLines, headBytes } = getSpillConfig(context?.settings);
|
|
1300
872
|
// `read` manages its own per-range truncation, but the combined multi-range
|
|
1301
873
|
// output has no cap — enforce a read-specific (higher) combined threshold
|
|
1302
874
|
// instead of exempting read entirely. 0 disables read spill (backstop only).
|
|
1303
875
|
const effectiveThreshold = toolName === "read" ? readThreshold : threshold;
|
|
876
|
+
if (effectiveThreshold <= 0) return result;
|
|
877
|
+
|
|
878
|
+
// Skip if tool already saved an artifact
|
|
1304
879
|
const existingMeta: OutputMeta | undefined = result.details?.meta;
|
|
880
|
+
if (existingMeta?.truncation?.artifactId) return result;
|
|
1305
881
|
|
|
1306
|
-
// Measure total text content
|
|
1307
|
-
// inline cap is enforced even when token-based spilling is disabled.
|
|
882
|
+
// Measure total text content
|
|
1308
883
|
const textParts: string[] = [];
|
|
1309
884
|
for (const block of result.content) {
|
|
1310
|
-
if (block.type === "text" && block.text)
|
|
885
|
+
if (block.type === "text" && block.text) {
|
|
886
|
+
textParts.push(block.text);
|
|
887
|
+
}
|
|
1311
888
|
}
|
|
1312
889
|
if (textParts.length === 0) return result;
|
|
1313
890
|
|
|
1314
891
|
const fullText = textParts.length === 1 ? textParts[0] : textParts.join("\n");
|
|
1315
|
-
const existingTruncation = existingMeta?.truncation;
|
|
1316
|
-
const existingArtifactProven =
|
|
1317
|
-
existingTruncation?.artifactId !== undefined &&
|
|
1318
|
-
isValidArtifactId(existingTruncation.artifactId) &&
|
|
1319
|
-
existingTruncation.artifactFailureDiagnostic === undefined &&
|
|
1320
|
-
existingTruncation.artifactVerified === true;
|
|
1321
|
-
if (existingArtifactProven) return result;
|
|
1322
892
|
const totalBytes = Buffer.byteLength(fullText, "utf-8");
|
|
1323
|
-
|
|
1324
|
-
if (!exceedsInlineCap && (effectiveThreshold <= 0 || totalBytes <= effectiveThreshold)) return result;
|
|
893
|
+
if (totalBytes <= effectiveThreshold) return result;
|
|
1325
894
|
|
|
1326
|
-
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
fullText,
|
|
1330
|
-
maxInlineBytes > 0 ? maxInlineBytes : undefined,
|
|
1331
|
-
diagnostic,
|
|
1332
|
-
);
|
|
1333
|
-
const outputLines = failurePreview.text.length > 0 ? failurePreview.text.split("\n").length : 0;
|
|
1334
|
-
const outputBytes = Buffer.byteLength(failurePreview.text, "utf-8");
|
|
1335
|
-
const failureTruncation: TruncationMeta = {
|
|
1336
|
-
...(existingTruncation ?? {}),
|
|
1337
|
-
direction: "tail",
|
|
1338
|
-
truncatedBy: "bytes",
|
|
1339
|
-
noticeOwner: undefined,
|
|
1340
|
-
totalLines: fullText.length > 0 ? fullText.split("\n").length : 0,
|
|
1341
|
-
totalBytes,
|
|
1342
|
-
outputLines,
|
|
1343
|
-
outputBytes,
|
|
1344
|
-
maxBytes: maxInlineBytes > 0 ? maxInlineBytes : undefined,
|
|
1345
|
-
shownRange: undefined,
|
|
1346
|
-
headRange: undefined,
|
|
1347
|
-
tailRange: undefined,
|
|
1348
|
-
nextOffset: undefined,
|
|
1349
|
-
artifactId: undefined,
|
|
1350
|
-
artifactVerified: false,
|
|
1351
|
-
sourceCaptureIncomplete: true,
|
|
1352
|
-
artifactFailureDiagnostic: diagnostic,
|
|
1353
|
-
};
|
|
1354
|
-
const content: (TextContent | ImageContent)[] = result.content.filter(block => block.type !== "text");
|
|
1355
|
-
content.push({ type: "text", text: failurePreview.text });
|
|
1356
|
-
return {
|
|
1357
|
-
...result,
|
|
1358
|
-
content,
|
|
1359
|
-
details: {
|
|
1360
|
-
...(result.details ?? {}),
|
|
1361
|
-
meta: { ...(existingMeta ?? {}), truncation: failureTruncation },
|
|
1362
|
-
},
|
|
1363
|
-
};
|
|
1364
|
-
};
|
|
1365
|
-
if (!artifactCapability) return saveFailure("artifact storage is unavailable");
|
|
1366
|
-
|
|
1367
|
-
// Save full output through the bounded, validating path. Failed threshold
|
|
1368
|
-
// spills preserve their diagnostic even when the absolute backstop is disabled.
|
|
1369
|
-
const saveOutcome = await saveInlineArtifactBounded(
|
|
1370
|
-
artifactCapability,
|
|
1371
|
-
() => artifactCapability.saveArtifact(fullText, toolName),
|
|
1372
|
-
artifactId => artifactCapability.getArtifactPath(artifactId),
|
|
1373
|
-
);
|
|
1374
|
-
if (saveOutcome.status !== "ok") return saveFailure(saveOutcome.diagnostic);
|
|
1375
|
-
|
|
1376
|
-
const artifactId = saveOutcome.artifactId;
|
|
1377
|
-
const newArtifactOmissions = genericArtifactOmittedBytes(fullText) ?? 0;
|
|
1378
|
-
const priorArtifactOmissions = isValidLossCounter(existingTruncation?.artifactTruncatedBytes)
|
|
1379
|
-
? (existingTruncation?.artifactTruncatedBytes ?? 0)
|
|
1380
|
-
: 0;
|
|
1381
|
-
const artifactTruncatedBytes = Math.max(newArtifactOmissions, priorArtifactOmissions) || undefined;
|
|
1382
|
-
const sourceTruncatedBytes = existingTruncation?.sourceTruncatedBytes;
|
|
1383
|
-
const columnDroppedBytes = existingTruncation?.columnDroppedBytes;
|
|
1384
|
-
const sourceCaptureIncomplete =
|
|
1385
|
-
existingTruncation?.sourceCaptureIncomplete || (columnDroppedBytes ?? 0) > 0 || undefined;
|
|
895
|
+
// Save full output as artifact
|
|
896
|
+
const artifactId = await artifactCapability.saveArtifact(fullText, toolName);
|
|
897
|
+
if (!artifactId) return result;
|
|
1386
898
|
|
|
1387
899
|
// Truncate: middle elision when a head budget is configured, otherwise tail-only.
|
|
1388
900
|
const useMiddle = headBytes > 0;
|
|
@@ -1398,20 +910,25 @@ async function spillLargeResultToArtifact(
|
|
|
1398
910
|
maxLines: tailLines,
|
|
1399
911
|
});
|
|
1400
912
|
|
|
1401
|
-
// Replace text blocks with single truncated block, keep images
|
|
913
|
+
// Replace text blocks with single truncated block, keep images
|
|
1402
914
|
const newContent: (TextContent | ImageContent)[] = [];
|
|
1403
915
|
for (const block of result.content) {
|
|
1404
|
-
if (block.type !== "text")
|
|
916
|
+
if (block.type !== "text") {
|
|
917
|
+
newContent.push(block);
|
|
918
|
+
}
|
|
1405
919
|
}
|
|
1406
920
|
newContent.push({ type: "text", text: truncated.content });
|
|
1407
921
|
|
|
1408
|
-
// Build truncation meta
|
|
922
|
+
// Build truncation meta
|
|
1409
923
|
const outputLines = truncated.outputLines ?? truncated.totalLines;
|
|
1410
924
|
const outputBytes = truncated.outputBytes ?? truncated.totalBytes;
|
|
1411
925
|
let truncationMeta: TruncationMeta;
|
|
1412
926
|
if (truncated.truncatedBy === "middle") {
|
|
1413
927
|
const elidedLines = truncated.elidedLines ?? Math.max(0, truncated.totalLines - outputLines);
|
|
1414
928
|
const elidedBytes = truncated.elidedBytes ?? Math.max(0, truncated.totalBytes - outputBytes);
|
|
929
|
+
const keptLines = Math.max(0, outputLines - 1); // -1 for marker line
|
|
930
|
+
const headLines = Math.ceil(keptLines / 2);
|
|
931
|
+
const tailLineCount = keptLines - headLines;
|
|
1415
932
|
truncationMeta = {
|
|
1416
933
|
direction: "middle",
|
|
1417
934
|
truncatedBy: "middle",
|
|
@@ -1420,16 +937,14 @@ async function spillLargeResultToArtifact(
|
|
|
1420
937
|
outputLines,
|
|
1421
938
|
outputBytes,
|
|
1422
939
|
maxBytes: headBytes + tailBytes,
|
|
1423
|
-
headRange:
|
|
1424
|
-
tailRange:
|
|
940
|
+
headRange: headLines > 0 ? { start: 1, end: headLines } : undefined,
|
|
941
|
+
tailRange:
|
|
942
|
+
tailLineCount > 0
|
|
943
|
+
? { start: truncated.totalLines - tailLineCount + 1, end: truncated.totalLines }
|
|
944
|
+
: undefined,
|
|
1425
945
|
elidedLines,
|
|
1426
946
|
elidedBytes,
|
|
1427
947
|
artifactId,
|
|
1428
|
-
artifactVerified: true,
|
|
1429
|
-
artifactTruncatedBytes,
|
|
1430
|
-
sourceTruncatedBytes,
|
|
1431
|
-
sourceCaptureIncomplete,
|
|
1432
|
-
columnDroppedBytes,
|
|
1433
948
|
};
|
|
1434
949
|
} else {
|
|
1435
950
|
const shownStart = truncated.totalLines - outputLines + 1;
|
|
@@ -1441,13 +956,8 @@ async function spillLargeResultToArtifact(
|
|
|
1441
956
|
outputLines,
|
|
1442
957
|
outputBytes,
|
|
1443
958
|
maxBytes: tailBytes,
|
|
1444
|
-
shownRange:
|
|
959
|
+
shownRange: { start: shownStart, end: truncated.totalLines },
|
|
1445
960
|
artifactId,
|
|
1446
|
-
artifactVerified: true,
|
|
1447
|
-
artifactTruncatedBytes,
|
|
1448
|
-
sourceTruncatedBytes,
|
|
1449
|
-
sourceCaptureIncomplete,
|
|
1450
|
-
columnDroppedBytes,
|
|
1451
961
|
};
|
|
1452
962
|
}
|
|
1453
963
|
|
|
@@ -1494,68 +1004,26 @@ async function enforceInlineResultBackstop(
|
|
|
1494
1004
|
textParts.push(block.text);
|
|
1495
1005
|
}
|
|
1496
1006
|
}
|
|
1007
|
+
if (textParts.length === 0) return result;
|
|
1497
1008
|
|
|
1498
1009
|
const renderedText = textParts.length === 1 ? textParts[0] : textParts.join("\n");
|
|
1499
1010
|
const fullText = stripBodyOwnedTruncationFooter(renderedText, result.details);
|
|
1500
|
-
const totalBytes = Buffer.byteLength(
|
|
1011
|
+
const totalBytes = Buffer.byteLength(fullText, "utf-8");
|
|
1012
|
+
if (totalBytes <= maxInlineBytes) return result;
|
|
1013
|
+
|
|
1014
|
+
// Reuse an existing artifact (avoid double-artifacting); otherwise save the
|
|
1015
|
+
// full output so the truncated view keeps a reference to the complete text.
|
|
1501
1016
|
const existingMeta: OutputMeta | undefined = result.details?.meta;
|
|
1502
|
-
|
|
1503
|
-
if (totalBytes + existingNoticeBytes <= maxInlineBytes) return result;
|
|
1504
|
-
|
|
1505
|
-
// Reuse only storage-complete artifacts. Source-loss evidence remains bound
|
|
1506
|
-
// to the rendered body even when a new complete artifact is saved.
|
|
1507
|
-
const existingTruncation = existingMeta?.truncation;
|
|
1508
|
-
const previousArtifactId = existingTruncation?.artifactId;
|
|
1509
|
-
const previousArtifactIdValid = previousArtifactId === undefined || isValidArtifactId(previousArtifactId);
|
|
1510
|
-
const previousArtifactProven =
|
|
1511
|
-
previousArtifactId !== undefined && previousArtifactIdValid && existingTruncation?.artifactVerified === true;
|
|
1512
|
-
const existingArtifactStorageComplete =
|
|
1513
|
-
previousArtifactProven && existingTruncation?.artifactFailureDiagnostic === undefined;
|
|
1514
|
-
let artifactId = existingArtifactStorageComplete ? previousArtifactId : undefined;
|
|
1515
|
-
let artifactFailureDiagnostic = previousArtifactIdValid
|
|
1516
|
-
? existingTruncation?.artifactFailureDiagnostic
|
|
1517
|
-
: "existing metadata contained an invalid artifact id";
|
|
1518
|
-
let artifactTruncatedBytes = existingTruncation?.artifactTruncatedBytes;
|
|
1519
|
-
let artifactVerified = existingArtifactStorageComplete;
|
|
1520
|
-
let savedReplacementArtifact = false;
|
|
1017
|
+
let artifactId = existingMeta?.truncation?.artifactId;
|
|
1521
1018
|
const artifactCapability = artifactCapabilityForContext(context);
|
|
1522
|
-
if (!artifactId && artifactCapability
|
|
1523
|
-
|
|
1524
|
-
artifactCapability,
|
|
1525
|
-
() => artifactCapability.saveArtifact(fullText, toolName),
|
|
1526
|
-
artifactId => artifactCapability.getArtifactPath(artifactId),
|
|
1527
|
-
);
|
|
1528
|
-
if (saveOutcome.status === "ok") {
|
|
1529
|
-
artifactId = saveOutcome.artifactId;
|
|
1530
|
-
const newArtifactOmissions = genericArtifactOmittedBytes(fullText) ?? 0;
|
|
1531
|
-
const priorArtifactOmissions = artifactTruncatedBytes ?? 0;
|
|
1532
|
-
artifactTruncatedBytes = Math.max(newArtifactOmissions, priorArtifactOmissions) || undefined;
|
|
1533
|
-
artifactFailureDiagnostic = undefined;
|
|
1534
|
-
artifactVerified = true;
|
|
1535
|
-
savedReplacementArtifact = true;
|
|
1536
|
-
} else {
|
|
1537
|
-
artifactFailureDiagnostic = saveOutcome.diagnostic;
|
|
1538
|
-
}
|
|
1539
|
-
} else if (!artifactId && !artifactCapability) {
|
|
1540
|
-
artifactFailureDiagnostic ??= "artifact storage is unavailable";
|
|
1019
|
+
if (!artifactId && artifactCapability) {
|
|
1020
|
+
artifactId = (await artifactCapability.saveArtifact(fullText, toolName)) ?? undefined;
|
|
1541
1021
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
existingTruncation?.sourceCaptureIncomplete ||
|
|
1546
|
-
(savedReplacementArtifact && (columnDroppedBytes ?? 0) > 0) ||
|
|
1547
|
-
undefined;
|
|
1548
|
-
const sourceCoordinatesIncomplete = (sourceTruncatedBytes ?? 0) > 0 || sourceCaptureIncomplete === true;
|
|
1549
|
-
|
|
1550
|
-
// Reserve the actual pre-existing notice plus room for the bounded truncation
|
|
1551
|
-
// evidence appended below. This prevents large diagnostics from bypassing the
|
|
1552
|
-
// final inline cap while retaining both head/tail content when space permits.
|
|
1022
|
+
|
|
1023
|
+
// Budget head+tail below the cap, reserving room for the elision marker so the
|
|
1024
|
+
// composed `<head>\n<marker>\n<tail>` view never exceeds the configured cap.
|
|
1553
1025
|
const MARKER_RESERVE = 256;
|
|
1554
|
-
const
|
|
1555
|
-
Math.max(0, maxInlineBytes - MARKER_RESERVE),
|
|
1556
|
-
Math.max(4096, existingNoticeBytes + 1024),
|
|
1557
|
-
);
|
|
1558
|
-
const budget = Math.max(0, maxInlineBytes - MARKER_RESERVE - NOTICE_RESERVE);
|
|
1026
|
+
const budget = maxInlineBytes - MARKER_RESERVE;
|
|
1559
1027
|
const useMiddle = headBytes > 0 && budget > 0;
|
|
1560
1028
|
let truncated = useMiddle
|
|
1561
1029
|
? truncateMiddle(fullText, {
|
|
@@ -1564,10 +1032,11 @@ async function enforceInlineResultBackstop(
|
|
|
1564
1032
|
maxHeadBytes: Math.min(headBytes, Math.floor(budget / 2)),
|
|
1565
1033
|
maxHeadLines: tailLines,
|
|
1566
1034
|
})
|
|
1567
|
-
: truncateTail(fullText, { maxBytes:
|
|
1035
|
+
: truncateTail(fullText, { maxBytes: maxInlineBytes, maxLines: tailLines });
|
|
1568
1036
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1037
|
+
// Defensive clamp: guarantee the contract even for pathological marker sizes.
|
|
1038
|
+
if (Buffer.byteLength(truncated.content, "utf-8") > maxInlineBytes) {
|
|
1039
|
+
truncated = truncateTail(fullText, { maxBytes: maxInlineBytes, maxLines: tailLines });
|
|
1571
1040
|
}
|
|
1572
1041
|
|
|
1573
1042
|
const newContent: (TextContent | ImageContent)[] = [];
|
|
@@ -1593,12 +1062,6 @@ async function enforceInlineResultBackstop(
|
|
|
1593
1062
|
elidedLines: truncated.elidedLines ?? Math.max(0, truncated.totalLines - outputLines),
|
|
1594
1063
|
elidedBytes: truncated.elidedBytes ?? Math.max(0, truncated.totalBytes - outputBytes),
|
|
1595
1064
|
artifactId,
|
|
1596
|
-
artifactVerified,
|
|
1597
|
-
artifactTruncatedBytes,
|
|
1598
|
-
sourceTruncatedBytes,
|
|
1599
|
-
sourceCaptureIncomplete,
|
|
1600
|
-
columnDroppedBytes,
|
|
1601
|
-
artifactFailureDiagnostic,
|
|
1602
1065
|
}
|
|
1603
1066
|
: {
|
|
1604
1067
|
direction: "tail",
|
|
@@ -1608,71 +1071,13 @@ async function enforceInlineResultBackstop(
|
|
|
1608
1071
|
outputLines,
|
|
1609
1072
|
outputBytes,
|
|
1610
1073
|
maxBytes: maxInlineBytes,
|
|
1611
|
-
shownRange:
|
|
1612
|
-
? undefined
|
|
1613
|
-
: { start: truncated.totalLines - outputLines + 1, end: truncated.totalLines },
|
|
1074
|
+
shownRange: { start: truncated.totalLines - outputLines + 1, end: truncated.totalLines },
|
|
1614
1075
|
artifactId,
|
|
1615
|
-
artifactVerified,
|
|
1616
|
-
artifactTruncatedBytes,
|
|
1617
|
-
sourceTruncatedBytes,
|
|
1618
|
-
sourceCaptureIncomplete,
|
|
1619
|
-
columnDroppedBytes,
|
|
1620
|
-
artifactFailureDiagnostic,
|
|
1621
1076
|
};
|
|
1622
1077
|
|
|
1623
|
-
|
|
1624
|
-
let finalText = truncated.content;
|
|
1625
|
-
let renderedBytes = Buffer.byteLength(finalText, "utf-8") + Buffer.byteLength(formatOutputNotice(newMeta), "utf-8");
|
|
1626
|
-
if (renderedBytes > maxInlineBytes && newMeta.diagnostics) {
|
|
1627
|
-
newMeta = { ...newMeta, diagnostics: undefined, diagnosticsOmitted: true };
|
|
1628
|
-
renderedBytes = Buffer.byteLength(finalText, "utf-8") + Buffer.byteLength(formatOutputNotice(newMeta), "utf-8");
|
|
1629
|
-
}
|
|
1630
|
-
if (renderedBytes > maxInlineBytes) {
|
|
1631
|
-
const compactTruncation = newMeta.truncation
|
|
1632
|
-
? {
|
|
1633
|
-
...newMeta.truncation,
|
|
1634
|
-
artifactFailureDiagnostic: newMeta.truncation.artifactFailureDiagnostic
|
|
1635
|
-
? truncateHeadBytes(newMeta.truncation.artifactFailureDiagnostic, 64).text
|
|
1636
|
-
: undefined,
|
|
1637
|
-
shownRange: undefined,
|
|
1638
|
-
headRange: undefined,
|
|
1639
|
-
tailRange: undefined,
|
|
1640
|
-
nextOffset: undefined,
|
|
1641
|
-
}
|
|
1642
|
-
: undefined;
|
|
1643
|
-
newMeta = { ...newMeta, truncation: compactTruncation, limits: undefined };
|
|
1644
|
-
const noticeBytes = Buffer.byteLength(formatOutputNotice(newMeta), "utf-8");
|
|
1645
|
-
const bodyBudget = Math.max(0, maxInlineBytes - noticeBytes);
|
|
1646
|
-
const finalTruncated = truncateTail(finalText, { maxBytes: bodyBudget, maxLines: tailLines });
|
|
1647
|
-
finalText = finalTruncated.content;
|
|
1648
|
-
if (newMeta.truncation) {
|
|
1649
|
-
newMeta = {
|
|
1650
|
-
...newMeta,
|
|
1651
|
-
truncation: {
|
|
1652
|
-
...newMeta.truncation,
|
|
1653
|
-
direction: "tail",
|
|
1654
|
-
truncatedBy: "bytes",
|
|
1655
|
-
outputBytes: Buffer.byteLength(finalText, "utf-8"),
|
|
1656
|
-
outputLines: finalText.length > 0 ? finalText.split("\n").length : 0,
|
|
1657
|
-
},
|
|
1658
|
-
};
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
const boundedContent = newContent.map(block => (block.type === "text" ? { ...block, text: finalText } : block));
|
|
1078
|
+
const newMeta: OutputMeta = { ...(existingMeta ?? {}), truncation: truncationMeta };
|
|
1662
1079
|
const newDetails = { ...(result.details ?? {}), meta: newMeta };
|
|
1663
|
-
return { ...result, content:
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
export async function finalizeToolResultForDelivery(
|
|
1667
|
-
result: AgentToolResult,
|
|
1668
|
-
toolName: string,
|
|
1669
|
-
context: AgentToolContext | undefined,
|
|
1670
|
-
): Promise<AgentToolResult> {
|
|
1671
|
-
let finalized = await sanitizeArtifactIdEvidence(result, context);
|
|
1672
|
-
finalized = await spillLargeResultToArtifact(finalized, toolName, context);
|
|
1673
|
-
finalized = await enforceInlineResultBackstop(finalized, toolName, context);
|
|
1674
|
-
const meta: OutputMeta | undefined = finalized.details?.meta;
|
|
1675
|
-
return meta ? { ...finalized, content: appendOutputNotice(finalized.content, meta) } : finalized;
|
|
1080
|
+
return { ...result, content: newContent, details: newDetails };
|
|
1676
1081
|
}
|
|
1677
1082
|
|
|
1678
1083
|
// =============================================================================
|
|
@@ -1690,11 +1095,24 @@ async function wrappedExecute(
|
|
|
1690
1095
|
const originalExecute = this[kUnwrappedExecute];
|
|
1691
1096
|
|
|
1692
1097
|
try {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1098
|
+
let result = await originalExecute.call(this, toolCallId, params, signal, onUpdate, context);
|
|
1099
|
+
|
|
1100
|
+
// Spill large results to artifact, truncate to tail
|
|
1101
|
+
result = await spillLargeResultToArtifact(result, this.name, context);
|
|
1102
|
+
|
|
1103
|
+
// Absolute inline-size backstop: catches oversized text the threshold spill
|
|
1104
|
+
// skipped (read exemption, tools with pre-existing partial artifact meta).
|
|
1105
|
+
result = await enforceInlineResultBackstop(result, this.name, context);
|
|
1106
|
+
|
|
1107
|
+
// Append notices from meta
|
|
1108
|
+
const meta: OutputMeta | undefined = result.details?.meta;
|
|
1109
|
+
if (meta) {
|
|
1110
|
+
return {
|
|
1111
|
+
...result,
|
|
1112
|
+
content: appendOutputNotice(result.content, meta),
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
return result;
|
|
1698
1116
|
} catch (e) {
|
|
1699
1117
|
// Re-throw with formatted message so agent-loop sets isError flag
|
|
1700
1118
|
throw new Error(renderError(e));
|