@aexol/spectral 0.9.210 → 0.9.212

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 (37) hide show
  1. package/dist/agent/agents.d.ts.map +1 -1
  2. package/dist/agent/agents.js +40 -0
  3. package/dist/commands/serve.d.ts.map +1 -1
  4. package/dist/commands/serve.js +24 -4
  5. package/dist/extensions/browser/tools/console.d.ts.map +1 -1
  6. package/dist/extensions/browser/tools/console.js +22 -2
  7. package/dist/extensions/browser/tools/evaluate.d.ts.map +1 -1
  8. package/dist/extensions/browser/tools/evaluate.js +28 -4
  9. package/dist/extensions/browser/tools/network.d.ts.map +1 -1
  10. package/dist/extensions/browser/tools/network.js +22 -2
  11. package/dist/extensions/seo/tools/fetch.d.ts.map +1 -1
  12. package/dist/extensions/seo/tools/fetch.js +59 -8
  13. package/dist/extensions/web/tools/fetch.d.ts.map +1 -1
  14. package/dist/extensions/web/tools/fetch.js +28 -15
  15. package/dist/generated/zeus/const.d.ts.map +1 -1
  16. package/dist/generated/zeus/const.js +27 -0
  17. package/dist/generated/zeus/index.d.ts +122 -0
  18. package/dist/generated/zeus/index.d.ts.map +1 -1
  19. package/dist/memory/tool-output-compressor.d.ts.map +1 -1
  20. package/dist/memory/tool-output-compressor.js +53 -2
  21. package/dist/relay/client.d.ts +11 -0
  22. package/dist/relay/client.d.ts.map +1 -1
  23. package/dist/relay/client.js +24 -6
  24. package/dist/relay/history-projection.d.ts.map +1 -1
  25. package/dist/relay/history-projection.js +7 -0
  26. package/dist/sdk/coding-agent/core/tools/offload.d.ts +102 -0
  27. package/dist/sdk/coding-agent/core/tools/offload.d.ts.map +1 -0
  28. package/dist/sdk/coding-agent/core/tools/offload.js +207 -0
  29. package/dist/server/agent-bridge.d.ts.map +1 -1
  30. package/dist/server/agent-bridge.js +21 -0
  31. package/dist/server/offload-gc.d.ts +21 -0
  32. package/dist/server/offload-gc.d.ts.map +1 -0
  33. package/dist/server/offload-gc.js +111 -0
  34. package/dist/server/session-stream.d.ts +2 -0
  35. package/dist/server/session-stream.d.ts.map +1 -1
  36. package/dist/server/session-stream.js +166 -9
  37. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tool-output-compressor.d.ts","sourceRoot":"","sources":["../../src/memory/tool-output-compressor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAEX,gBAAgB,EAIhB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAO5C,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAE1D,eAAO,MAAM,yBAAyB,EAAE,gBAEvC,CAAC;AAgsBF,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3F;AA8BD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,OAAO,GAAG,8BAA8B,CAElG;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE1E;AAkDD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,EAC7F,GAAG,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,GAC7C,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,CAYvG;AAED,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,OAAO,GACd,IAAI,CAkCN"}
1
+ {"version":3,"file":"tool-output-compressor.d.ts","sourceRoot":"","sources":["../../src/memory/tool-output-compressor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAEX,gBAAgB,EAIhB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ5C,MAAM,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAE1D,eAAO,MAAM,yBAAyB,EAAE,gBAEvC,CAAC;AAgsBF,MAAM,WAAW,gBAAgB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3F;AA8BD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,OAAO,GAAG,8BAA8B,CAElG;AAED,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE1E;AAgGD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,EAC7F,GAAG,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,GAC7C,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,CAYvG;AAED,wBAAgB,4BAA4B,CAC3C,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,OAAO,GACd,IAAI,CAmCN"}
@@ -29,6 +29,7 @@
29
29
  */
30
30
  import { DEFAULT_COMPACTION_POLICY, } from "../sdk/coding-agent/core/compaction/policy.js";
31
31
  import { debugLog } from "./debug-log.js";
32
+ import { isAlreadyOffloaded, offloadLargeResult } from "../sdk/coding-agent/core/tools/offload.js";
32
33
  export const DEFAULT_COMPRESSOR_CONFIG = {
33
34
  ...DEFAULT_COMPACTION_POLICY.toolOutputCompressor,
34
35
  };
@@ -672,6 +673,55 @@ function resolveCompressorConfig(ctx) {
672
673
  const manager = ctx.settingsManager;
673
674
  return manager?.getCompactionPolicy?.().toolOutputCompressor ?? DEFAULT_COMPRESSOR_CONFIG;
674
675
  }
676
+ // ============================================================================
677
+ // Large-result offload fallback
678
+ // ============================================================================
679
+ /**
680
+ * Fallback offload for tool types without native preview+file handling.
681
+ *
682
+ * - Skips read/bash (they already compress natively) and anything already
683
+ * carrying a `Full output:` footer (notably the bash offload path).
684
+ * - Skips error results and non-text/binary content.
685
+ * - Persists the full text via `offloadLargeResult()` and swaps the event
686
+ * text for preview + footer. Small results pass through untouched.
687
+ *
688
+ * The `tool_result` hook carries no session id, so offloads land under the
689
+ * shared `offloads/shared/` namespace.
690
+ */
691
+ function maybeOffloadLargeResult(event) {
692
+ if (event.toolName === "read" || event.toolName === "bash")
693
+ return undefined;
694
+ if (event.isError)
695
+ return undefined;
696
+ const texts = event.content.filter((c) => c.type === "text" && typeof c.text === "string");
697
+ if (texts.length === 0)
698
+ return undefined;
699
+ const original = texts.map((c) => c.text).join("\n");
700
+ if (!original || original.includes("\0"))
701
+ return undefined;
702
+ if (isAlreadyOffloaded(original))
703
+ return undefined;
704
+ let offloaded;
705
+ try {
706
+ offloaded = offloadLargeResult(original, event.toolName, "shared");
707
+ }
708
+ catch {
709
+ return undefined;
710
+ }
711
+ if (!offloaded.fullOutputPath || !offloaded.truncation)
712
+ return undefined;
713
+ return {
714
+ content: [
715
+ ...event.content.filter((c) => c.type !== "text"),
716
+ { type: "text", text: offloaded.preview },
717
+ ],
718
+ details: {
719
+ ...(typeof event.details === "object" && event.details !== null ? event.details : {}),
720
+ fullOutputPath: offloaded.fullOutputPath,
721
+ offloadTruncation: offloaded.truncation,
722
+ },
723
+ };
724
+ }
675
725
  function compressedContentResult(runtime, event, toolName, original, compressed) {
676
726
  if (!compressed || compressed === original)
677
727
  return undefined;
@@ -734,7 +784,8 @@ export function registerToolOutputCompressor(ext, runtime) {
734
784
  const compressed = compressBashOutput(event, config);
735
785
  return compressedContentResult(runtime, event, "bash", original, compressed);
736
786
  }
737
- // Unknown tool types: do nothing
738
- return;
787
+ // All other tool types (web_fetch, browser_*, seo_*, pdf_*, …): fall back
788
+ // to large-result offloading so no truncated output is ever lost.
789
+ return maybeOffloadLargeResult(_event);
739
790
  });
740
791
  }
@@ -122,6 +122,17 @@ export declare class RelayClient extends EventEmitter {
122
122
  /** Guards the async window of `scheduleReconnect` (health check). */
123
123
  private reconnectInFlight;
124
124
  constructor(opts: RelayClientOptions);
125
+ /**
126
+ * Emit a non-fatal error without being able to crash the host process.
127
+ *
128
+ * The `error` event is documented as non-fatal (parse failure, transport
129
+ * noise, send failure), but a bare `this.emit("error", ...)` on a Node
130
+ * EventEmitter THROWS when no listener is registered. Embeddings that
131
+ * never subscribe to `error` (SDK consumers, `serve` running in silent
132
+ * mode, ad-hoc clients in tests) would die on the first transport hiccup
133
+ * instead of logging it. When a listener exists, behaviour is unchanged.
134
+ */
135
+ private emitError;
125
136
  /** Open the connection. Idempotent — calling twice is a no-op. */
126
137
  connect(): void;
127
138
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/relay/client.ts"],"names":[],"mappings":"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"}
@@ -124,6 +124,24 @@ export class RelayClient extends EventEmitter {
124
124
  this.exit = opts.exit ?? ((code) => process.exit(code));
125
125
  this.runtimeInfo = opts.runtimeInfo;
126
126
  }
127
+ /**
128
+ * Emit a non-fatal error without being able to crash the host process.
129
+ *
130
+ * The `error` event is documented as non-fatal (parse failure, transport
131
+ * noise, send failure), but a bare `this.emit("error", ...)` on a Node
132
+ * EventEmitter THROWS when no listener is registered. Embeddings that
133
+ * never subscribe to `error` (SDK consumers, `serve` running in silent
134
+ * mode, ad-hoc clients in tests) would die on the first transport hiccup
135
+ * instead of logging it. When a listener exists, behaviour is unchanged.
136
+ */
137
+ emitError(err) {
138
+ if (this.listenerCount("error") > 0) {
139
+ this.emit("error", err);
140
+ return;
141
+ }
142
+ const msg = err instanceof Error ? err.message : String(err);
143
+ this.logger.warn(`Relay error: ${msg}`);
144
+ }
127
145
  /** Open the connection. Idempotent — calling twice is a no-op. */
128
146
  connect() {
129
147
  if (this.disposed)
@@ -213,7 +231,7 @@ export class RelayClient extends EventEmitter {
213
231
  parsed = JSON.parse(data.toString());
214
232
  }
215
233
  catch (err) {
216
- this.emit("error", err);
234
+ this.emitError(err);
217
235
  return;
218
236
  }
219
237
  // Heartbeat: reply pong inline, do NOT propagate.
@@ -257,7 +275,7 @@ export class RelayClient extends EventEmitter {
257
275
  // `ws` emits 'error' before 'close'. Reconnect is driven from the
258
276
  // `close` handler (or `forceReconnect` for suspected-dead sockets);
259
277
  // scheduling it here would double-schedule.
260
- this.emit("error", err);
278
+ this.emitError(err);
261
279
  });
262
280
  ws.on("close", (code, reason) => {
263
281
  this.handleDisconnect(ws, code, reason?.toString() ?? "");
@@ -312,7 +330,7 @@ export class RelayClient extends EventEmitter {
312
330
  clearTimeout(this.heartbeatPongTimer);
313
331
  this.heartbeatPongTimer = null;
314
332
  }
315
- this.emit("error", err);
333
+ this.emitError(err);
316
334
  this.forceReconnect("heartbeat-error");
317
335
  }
318
336
  }, HEARTBEAT_INTERVAL_MS);
@@ -428,7 +446,7 @@ export class RelayClient extends EventEmitter {
428
446
  clearTimeout(this.sendTimer);
429
447
  this.sendTimer = null;
430
448
  }
431
- this.emit("error", err);
449
+ this.emitError(err);
432
450
  this.forceReconnect("send-error");
433
451
  }
434
452
  }
@@ -456,7 +474,7 @@ export class RelayClient extends EventEmitter {
456
474
  ws.terminate();
457
475
  }
458
476
  catch (err) {
459
- this.emit("error", err);
477
+ this.emitError(err);
460
478
  }
461
479
  this.handleDisconnect(ws, 1006, reason);
462
480
  }
@@ -641,7 +659,7 @@ export class RelayClient extends EventEmitter {
641
659
  // A synchronous throw from the WebSocket constructor (DNS, bad
642
660
  // URL, etc.) must not kill the daemon — log it and re-schedule
643
661
  // so the backoff continues forever.
644
- this.emit("error", err);
662
+ this.emitError(err);
645
663
  void this.scheduleReconnect().catch(() => { });
646
664
  }
647
665
  }, delay);
@@ -1 +1 @@
1
- {"version":3,"file":"history-projection.d.ts","sourceRoot":"","sources":["../../src/relay/history-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,QAAa,CAAC;AAEpD,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,iDAAiD,CAAC;AAoMhF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAwC9D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAMtF"}
1
+ {"version":3,"file":"history-projection.d.ts","sourceRoot":"","sources":["../../src/relay/history-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,QAAa,CAAC;AAEpD,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,iDAAiD,CAAC;AAwMhF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAwC9D;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAMtF"}
@@ -72,6 +72,13 @@ function projectToolResult(event) {
72
72
  compactDetails.status = details.status;
73
73
  if ("mode" in details)
74
74
  compactDetails.mode = details.mode;
75
+ // Preserve offload pointers so the agent can re-read full outputs.
76
+ if ("fullOutputPath" in details)
77
+ compactDetails.fullOutputPath = details.fullOutputPath;
78
+ if ("offloadTruncation" in details)
79
+ compactDetails.offloadTruncation = details.offloadTruncation;
80
+ if ("truncation" in details)
81
+ compactDetails.truncation = truncateStrings(details.truncation);
75
82
  projectedResult = { ...result, details: compactDetails };
76
83
  }
77
84
  if (typeof projectedResult.content === "string") {
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Large-result offloading for tool outputs.
3
+ *
4
+ * Goal: no truncated tool result is ever lost. The full output goes to a
5
+ * file under `<configDir>/offloads/<sessionId>/`, and only a head+tail
6
+ * preview plus a footer (path + re-read instructions) enters the LLM context.
7
+ * The agent can then page through the file with `read` (offset/limit) or
8
+ * `bash` (sed/head/tail).
9
+ *
10
+ * This is the generic version of the pattern pioneered by the bash tool
11
+ * (preview + `Full output: <path>` footer). Small results pass through
12
+ * untouched — no file, no overhead. Binary-looking content (NUL bytes) is
13
+ * never offloaded.
14
+ */
15
+ /** Default byte threshold above which a result is offloaded to disk. */
16
+ export declare const DEFAULT_OFFLOAD_BYTES: number;
17
+ /** Default line threshold above which a result is offloaded to disk. */
18
+ export declare const DEFAULT_OFFLOAD_LINES = 2000;
19
+ /** Preview head size (lines kept from the start). */
20
+ export declare const PREVIEW_HEAD_LINES = 100;
21
+ /** Preview tail size (lines kept from the end). */
22
+ export declare const PREVIEW_TAIL_LINES = 100;
23
+ /** Emergency byte cap for previews (head+tail can still be huge with long lines). */
24
+ export declare const PREVIEW_MAX_BYTES: number;
25
+ /** Marker identifying an already-offloaded result (bash tool compat). */
26
+ export declare const OFFLOAD_MARKER = "Full output:";
27
+ /** Anchored marker check: only a real `Full output: <path>.log` footer counts. */
28
+ export declare const OFFLOAD_PATH_RE: RegExp;
29
+ /**
30
+ * Parse a positive-int env override. Returns `fallback` when the variable is
31
+ * unset, unparseable, zero, or negative — a non-positive budget would
32
+ * otherwise disable offloading/GC entirely, so it is treated as "not set".
33
+ */
34
+ export declare function envPositiveInt(name: string, fallback: number): number;
35
+ /** Effective byte limit (override via `SPECTRAL_OFFLOAD_BYTES`). */
36
+ export declare function getOffloadByteLimit(): number;
37
+ /** Effective line limit (override via `SPECTRAL_OFFLOAD_LINES`). */
38
+ export declare function getOffloadLineLimit(): number;
39
+ export interface OffloadTruncation {
40
+ originalBytes: number;
41
+ shownBytes: number;
42
+ headLines: number;
43
+ tailLines: number;
44
+ omittedLines: number;
45
+ totalLines: number;
46
+ fullOutputPath: string;
47
+ }
48
+ export interface OffloadResult {
49
+ /** Model-facing text: full original for small results, preview+footer for large ones. */
50
+ preview: string;
51
+ /** Absolute path of the persisted full output, or null when nothing was written. */
52
+ fullOutputPath: string | null;
53
+ /** Truncation metadata, or null when nothing was offloaded. */
54
+ truncation: OffloadTruncation | null;
55
+ }
56
+ export declare function countLines(text: string): number;
57
+ /** Marker for results that were already offloaded (or produced by the bash tool). */
58
+ export declare function isAlreadyOffloaded(text: string): boolean;
59
+ /**
60
+ * Whether `text` is large enough to deserve offloading.
61
+ * Binary-looking content (NUL bytes) is never offloaded.
62
+ */
63
+ export declare function shouldOffload(text: string): boolean;
64
+ export interface PreviewResult {
65
+ preview: string;
66
+ headLines: number;
67
+ tailLines: number;
68
+ omittedLines: number;
69
+ totalLines: number;
70
+ }
71
+ /**
72
+ * Build a head+tail preview. Never cuts in the middle of a line (except via
73
+ * the emergency byte cap below); the gap is replaced with a
74
+ * `[... N lines omitted ...]` marker. When head+tail itself exceeds
75
+ * `PREVIEW_MAX_BYTES` (e.g. a single giant line), the preview is cut
76
+ * mid-line with a `[... N bytes omitted ...]` marker so the LLM context
77
+ * never receives an unbounded preview.
78
+ */
79
+ export declare function buildPreview(text: string, headLines?: number, tailLines?: number): PreviewResult;
80
+ export interface OffloadFooterOptions {
81
+ headLines?: number;
82
+ tailLines?: number;
83
+ totalLines?: number;
84
+ }
85
+ /**
86
+ * Single-line footer appended to a preview. Carries the full-output path plus
87
+ * a copy-pasteable re-read hint for the agent.
88
+ */
89
+ export declare function formatOffloadFooter(path: string, shown: number, total: number, opts?: OffloadFooterOptions): string;
90
+ /**
91
+ * Resolve (and create) the offload file path for a tool result:
92
+ * `<configDir>/offloads/<sessionId>/<tool>-<timestamp>-<rand>.log`.
93
+ * Honors `SPECTRAL_CONFIG_DIR` via `getConfigDir()`.
94
+ */
95
+ export declare function resolveOffloadPath(sessionId: string, toolName: string): string;
96
+ /**
97
+ * Offload `text` to disk when it exceeds the size thresholds.
98
+ * Small (or binary, or already-offloaded) results are returned unchanged
99
+ * with `fullOutputPath: null` — no file, no overhead.
100
+ */
101
+ export declare function offloadLargeResult(text: string, toolName: string, sessionId: string): OffloadResult;
102
+ //# sourceMappingURL=offload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offload.d.ts","sourceRoot":"","sources":["../../../../../src/sdk/coding-agent/core/tools/offload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,QAAY,CAAC;AAC/C,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,QAAoB,CAAC;AACnD,yEAAyE;AACzE,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAC7C,kFAAkF;AAClF,eAAO,MAAM,eAAe,QAA0B,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,MAAM,WAAW,iBAAiB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACrC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKnD;AAED,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAA2B,EACtC,SAAS,GAAE,MAA2B,GACpC,aAAa,CAmBf;AAmCD,MAAM,WAAW,oBAAoB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,oBAAyB,GAC7B,MAAM,CAiBR;AAOD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,CA6BnG"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Large-result offloading for tool outputs.
3
+ *
4
+ * Goal: no truncated tool result is ever lost. The full output goes to a
5
+ * file under `<configDir>/offloads/<sessionId>/`, and only a head+tail
6
+ * preview plus a footer (path + re-read instructions) enters the LLM context.
7
+ * The agent can then page through the file with `read` (offset/limit) or
8
+ * `bash` (sed/head/tail).
9
+ *
10
+ * This is the generic version of the pattern pioneered by the bash tool
11
+ * (preview + `Full output: <path>` footer). Small results pass through
12
+ * untouched — no file, no overhead. Binary-looking content (NUL bytes) is
13
+ * never offloaded.
14
+ */
15
+ import { randomBytes } from "node:crypto";
16
+ import { mkdirSync, writeFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { getConfigDir } from "../../../../config.js";
19
+ import { DEFAULT_MAX_BYTES } from "./truncate.js";
20
+ /** Default byte threshold above which a result is offloaded to disk. */
21
+ export const DEFAULT_OFFLOAD_BYTES = 50 * 1024; // 50KB
22
+ /** Default line threshold above which a result is offloaded to disk. */
23
+ export const DEFAULT_OFFLOAD_LINES = 2000;
24
+ /** Preview head size (lines kept from the start). */
25
+ export const PREVIEW_HEAD_LINES = 100;
26
+ /** Preview tail size (lines kept from the end). */
27
+ export const PREVIEW_TAIL_LINES = 100;
28
+ /** Emergency byte cap for previews (head+tail can still be huge with long lines). */
29
+ export const PREVIEW_MAX_BYTES = DEFAULT_MAX_BYTES;
30
+ /** Marker identifying an already-offloaded result (bash tool compat). */
31
+ export const OFFLOAD_MARKER = "Full output:";
32
+ /** Anchored marker check: only a real `Full output: <path>.log` footer counts. */
33
+ export const OFFLOAD_PATH_RE = /Full output: \S+\.log/;
34
+ /**
35
+ * Parse a positive-int env override. Returns `fallback` when the variable is
36
+ * unset, unparseable, zero, or negative — a non-positive budget would
37
+ * otherwise disable offloading/GC entirely, so it is treated as "not set".
38
+ */
39
+ export function envPositiveInt(name, fallback) {
40
+ const raw = process.env[name];
41
+ if (!raw)
42
+ return fallback;
43
+ const parsed = Number.parseInt(raw, 10);
44
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
45
+ }
46
+ /** Effective byte limit (override via `SPECTRAL_OFFLOAD_BYTES`). */
47
+ export function getOffloadByteLimit() {
48
+ return envPositiveInt("SPECTRAL_OFFLOAD_BYTES", DEFAULT_OFFLOAD_BYTES);
49
+ }
50
+ /** Effective line limit (override via `SPECTRAL_OFFLOAD_LINES`). */
51
+ export function getOffloadLineLimit() {
52
+ return envPositiveInt("SPECTRAL_OFFLOAD_LINES", DEFAULT_OFFLOAD_LINES);
53
+ }
54
+ export function countLines(text) {
55
+ if (text.length === 0)
56
+ return 0;
57
+ const lines = text.split("\n");
58
+ // A trailing newline does not start a new line.
59
+ return text.endsWith("\n") ? lines.length - 1 : lines.length;
60
+ }
61
+ /** Marker for results that were already offloaded (or produced by the bash tool). */
62
+ export function isAlreadyOffloaded(text) {
63
+ return OFFLOAD_PATH_RE.test(text);
64
+ }
65
+ /**
66
+ * Whether `text` is large enough to deserve offloading.
67
+ * Binary-looking content (NUL bytes) is never offloaded.
68
+ */
69
+ export function shouldOffload(text) {
70
+ if (!text || text.includes("\0"))
71
+ return false;
72
+ if (isAlreadyOffloaded(text))
73
+ return false;
74
+ if (Buffer.byteLength(text, "utf-8") > getOffloadByteLimit())
75
+ return true;
76
+ return countLines(text) > getOffloadLineLimit();
77
+ }
78
+ /**
79
+ * Build a head+tail preview. Never cuts in the middle of a line (except via
80
+ * the emergency byte cap below); the gap is replaced with a
81
+ * `[... N lines omitted ...]` marker. When head+tail itself exceeds
82
+ * `PREVIEW_MAX_BYTES` (e.g. a single giant line), the preview is cut
83
+ * mid-line with a `[... N bytes omitted ...]` marker so the LLM context
84
+ * never receives an unbounded preview.
85
+ */
86
+ export function buildPreview(text, headLines = PREVIEW_HEAD_LINES, tailLines = PREVIEW_TAIL_LINES) {
87
+ const rawLines = text.split("\n");
88
+ // Drop the phantom empty element from a trailing newline.
89
+ const lines = text.endsWith("\n") && rawLines.length > 0 ? rawLines.slice(0, -1) : rawLines;
90
+ const totalLines = lines.length;
91
+ if (totalLines <= headLines + tailLines) {
92
+ return capPreviewBytes(text, {
93
+ preview: text,
94
+ headLines: totalLines,
95
+ tailLines: 0,
96
+ omittedLines: 0,
97
+ totalLines,
98
+ });
99
+ }
100
+ const head = lines.slice(0, headLines);
101
+ const tail = lines.slice(totalLines - tailLines);
102
+ const omittedLines = totalLines - headLines - tailLines;
103
+ const preview = [...head, `[... ${omittedLines} lines omitted ...]`, ...tail].join("\n");
104
+ return capPreviewBytes(preview, { preview, headLines, tailLines, omittedLines, totalLines });
105
+ }
106
+ /**
107
+ * Emergency byte cap for previews. Long-line blobs (e.g. minified JSON on a
108
+ * single line) can make head+tail previews arbitrarily large; cut them
109
+ * mid-line with a byte-omitted marker. UTF-8 safe via Buffer slicing.
110
+ */
111
+ function capPreviewBytes(textOrResult, fallback) {
112
+ const text = typeof textOrResult === "string" ? textOrResult : textOrResult.preview;
113
+ const base = typeof textOrResult === "string"
114
+ ? fallback
115
+ : {
116
+ preview: textOrResult.preview,
117
+ headLines: textOrResult.headLines,
118
+ tailLines: textOrResult.tailLines,
119
+ omittedLines: textOrResult.omittedLines,
120
+ totalLines: textOrResult.totalLines,
121
+ };
122
+ if (Buffer.byteLength(text, "utf-8") <= PREVIEW_MAX_BYTES) {
123
+ return { ...base, preview: text };
124
+ }
125
+ const buf = Buffer.from(text, "utf-8");
126
+ // Leave room for the marker; walk back to a UTF-8 char boundary.
127
+ const markerReserve = 64;
128
+ let end = Math.max(0, PREVIEW_MAX_BYTES - markerReserve);
129
+ while (end > 0 && end < buf.length && (buf[end] & 0xc0) === 0x80)
130
+ end--;
131
+ const omitted = buf.length - end;
132
+ const preview = buf.subarray(0, end).toString("utf-8") + `\n[... ${omitted} bytes omitted ...]`;
133
+ return { ...base, preview };
134
+ }
135
+ /**
136
+ * Single-line footer appended to a preview. Carries the full-output path plus
137
+ * a copy-pasteable re-read hint for the agent.
138
+ */
139
+ export function formatOffloadFooter(path, shown, total, opts = {}) {
140
+ const headLines = opts.headLines ?? PREVIEW_HEAD_LINES;
141
+ const tailLines = opts.tailLines ?? PREVIEW_TAIL_LINES;
142
+ // Guard against inconsistent callers (e.g. byte-counted `shown` on a
143
+ // line-based total): never claim to show more than exists.
144
+ const safeShown = Math.min(shown, total);
145
+ const safeTotal = Math.max(total, 0);
146
+ let readHint;
147
+ if (opts.totalLines !== undefined && opts.totalLines > headLines + tailLines) {
148
+ const tailStart = opts.totalLines - tailLines + 1;
149
+ readHint =
150
+ `Read with: sed -n '1,${headLines}p' ${path} | head -c ${DEFAULT_MAX_BYTES} ` +
151
+ `or sed -n '${tailStart},${opts.totalLines}p' ${path} | head -c ${DEFAULT_MAX_BYTES}`;
152
+ }
153
+ else {
154
+ readHint = `Read with: sed -n '1,${headLines}p' ${path} | head -c ${DEFAULT_MAX_BYTES}`;
155
+ }
156
+ return `[Truncated: showing ${safeShown} of ${safeTotal} bytes. Full output: ${path}. ${readHint}]`;
157
+ }
158
+ function sanitizeSegment(segment) {
159
+ const cleaned = segment.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
160
+ return cleaned || "shared";
161
+ }
162
+ /**
163
+ * Resolve (and create) the offload file path for a tool result:
164
+ * `<configDir>/offloads/<sessionId>/<tool>-<timestamp>-<rand>.log`.
165
+ * Honors `SPECTRAL_CONFIG_DIR` via `getConfigDir()`.
166
+ */
167
+ export function resolveOffloadPath(sessionId, toolName) {
168
+ const dir = join(getConfigDir(), "offloads", sanitizeSegment(sessionId));
169
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
170
+ const stamp = `${Date.now()}-${randomBytes(4).toString("hex")}`;
171
+ return join(dir, `${sanitizeSegment(toolName)}-${stamp}.log`);
172
+ }
173
+ /**
174
+ * Offload `text` to disk when it exceeds the size thresholds.
175
+ * Small (or binary, or already-offloaded) results are returned unchanged
176
+ * with `fullOutputPath: null` — no file, no overhead.
177
+ */
178
+ export function offloadLargeResult(text, toolName, sessionId) {
179
+ if (!shouldOffload(text)) {
180
+ return { preview: text, fullOutputPath: null, truncation: null };
181
+ }
182
+ const originalBytes = Buffer.byteLength(text, "utf-8");
183
+ const { preview, headLines, tailLines, omittedLines, totalLines } = buildPreview(text);
184
+ const fullOutputPath = resolveOffloadPath(sessionId, toolName);
185
+ // Owner-only: offloads may contain secrets (headers, tokens, page bodies).
186
+ writeFileSync(fullOutputPath, text, { encoding: "utf-8", mode: 0o600 });
187
+ // `shown` is measured on the actually displayed preview text.
188
+ const footer = formatOffloadFooter(fullOutputPath, Buffer.byteLength(preview, "utf-8"), originalBytes, {
189
+ headLines,
190
+ tailLines,
191
+ totalLines,
192
+ });
193
+ const previewWithFooter = `${preview}\n${footer}`;
194
+ return {
195
+ preview: previewWithFooter,
196
+ fullOutputPath,
197
+ truncation: {
198
+ originalBytes,
199
+ shownBytes: Buffer.byteLength(previewWithFooter, "utf-8"),
200
+ headLines,
201
+ tailLines,
202
+ omittedLines,
203
+ totalLines,
204
+ fullOutputPath,
205
+ },
206
+ };
207
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-bridge.d.ts","sourceRoot":"","sources":["../../src/server/agent-bridge.ts"],"names":[],"mappings":"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"}
@@ -446,6 +446,13 @@ function parseWireToolEvents(eventsJsonl) {
446
446
  * images and other rich results.
447
447
  */
448
448
  const MAX_REHYDRATED_TEXT = 100_000;
449
+ /**
450
+ * Ceiling for blocks that already carry an offload footer (`Full output:`).
451
+ * These blocks are a bounded preview + a re-readable path, so cutting them
452
+ * with the generic limit would destroy the footer. Keep them whole up to
453
+ * this size; larger ones lose their middle but keep head + footer.
454
+ */
455
+ const MAX_REHYDRATED_OFFLOAD_TEXT = 200_000;
449
456
  function sanitizeRehydratedBlock(block) {
450
457
  // Tool-result image blocks are not accepted by every provider; the raw
451
458
  // result remains in events_jsonl for the UI, while the model gets a compact reference.
@@ -460,6 +467,20 @@ function sanitizeRehydratedBlock(block) {
460
467
  // Truncate oversized text blocks to prevent context overflow.
461
468
  if (block.type !== "text")
462
469
  return block;
470
+ if (block.text.includes("Full output:")) {
471
+ if (block.text.length <= MAX_REHYDRATED_OFFLOAD_TEXT)
472
+ return block;
473
+ // Keep the head (preview) and the tail (offload footer), drop the middle.
474
+ const keepHead = Math.floor(MAX_REHYDRATED_OFFLOAD_TEXT * 0.7);
475
+ const keepTail = MAX_REHYDRATED_OFFLOAD_TEXT - keepHead;
476
+ const omitted = block.text.length - keepHead - keepTail;
477
+ return {
478
+ ...block,
479
+ text: block.text.slice(0, keepHead) +
480
+ `\n\n[... ${omitted.toLocaleString()} bytes omitted from rehydrated offload preview ...]\n\n` +
481
+ block.text.slice(block.text.length - keepTail),
482
+ };
483
+ }
463
484
  if (block.text.length <= MAX_REHYDRATED_TEXT)
464
485
  return block;
465
486
  const omitted = block.text.length - MAX_REHYDRATED_TEXT;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Garbage collection for large-result offload files.
3
+ *
4
+ * Offloads live under `<configDir>/offloads/<sessionId>/*.log`. This sweep
5
+ * removes per-session directories older than `SPECTRAL_OFFLOAD_TTL_DAYS`
6
+ * (default 7) and, when the total still exceeds `SPECTRAL_OFFLOAD_MAX_MB`
7
+ * (default 500), evicts the oldest sessions first.
8
+ *
9
+ * Best-effort and synchronous: callers fire-and-forget it during preflight.
10
+ */
11
+ export declare const DEFAULT_OFFLOAD_TTL_DAYS = 7;
12
+ export declare const DEFAULT_OFFLOAD_MAX_MB = 500;
13
+ export declare function getOffloadTtlMs(): number;
14
+ export declare function getOffloadMaxBytes(): number;
15
+ export interface OffloadSweepResult {
16
+ removedSessions: number;
17
+ removedBytes: number;
18
+ }
19
+ /** Remove stale/over-budget offload directories. Never throws. */
20
+ export declare function sweepOffloads(root?: string): OffloadSweepResult;
21
+ //# sourceMappingURL=offload-gc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offload-gc.d.ts","sourceRoot":"","sources":["../../src/server/offload-gc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAsBD,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAoD/D"}