agent-relay-server 0.122.0 → 0.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +7 -8
  3. package/public/assets/{MessageBubble-DRdUD-kW.js → MessageBubble-BTeFExjj.js} +2 -2
  4. package/public/assets/{MessageBubble-DRdUD-kW.js.map → MessageBubble-BTeFExjj.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-siKMPCln.js → PlanGraphPanel-jU1DXg7w.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-siKMPCln.js.map → PlanGraphPanel-jU1DXg7w.js.map} +1 -1
  7. package/public/assets/{activity-D3MNcSaV.js → activity-DUig2iYW.js} +2 -2
  8. package/public/assets/{activity-D3MNcSaV.js.map → activity-DUig2iYW.js.map} +1 -1
  9. package/public/assets/{agent-profiles-CKzttq6G.js → agent-profiles-D-oFmb6S.js} +2 -2
  10. package/public/assets/{agent-profiles-CKzttq6G.js.map → agent-profiles-D-oFmb6S.js.map} +1 -1
  11. package/public/assets/{agent-type-icon-0ZJRP1Vy.js → agent-type-icon-Dan2l3Tm.js} +2 -2
  12. package/public/assets/{agent-type-icon-0ZJRP1Vy.js.map → agent-type-icon-Dan2l3Tm.js.map} +1 -1
  13. package/public/assets/{agents-CuVj_rfG.js → agents-BkC6mcTh.js} +2 -2
  14. package/public/assets/{agents-CuVj_rfG.js.map → agents-BkC6mcTh.js.map} +1 -1
  15. package/public/assets/{analytics-iob6C27d.js → analytics-C3Dlmqe9.js} +2 -2
  16. package/public/assets/{analytics-iob6C27d.js.map → analytics-C3Dlmqe9.js.map} +1 -1
  17. package/public/assets/{automation-B2ixxs4q.js → automation-AeMZ70WQ.js} +2 -2
  18. package/public/assets/{automation-B2ixxs4q.js.map → automation-AeMZ70WQ.js.map} +1 -1
  19. package/public/assets/{branch-state-badge-CafBJWKo.js → branch-state-badge-CNW10MTC.js} +2 -2
  20. package/public/assets/{branch-state-badge-CafBJWKo.js.map → branch-state-badge-CNW10MTC.js.map} +1 -1
  21. package/public/assets/{channels-C-WsbL2H.js → channels-Bc_mEd56.js} +2 -2
  22. package/public/assets/{channels-C-WsbL2H.js.map → channels-Bc_mEd56.js.map} +1 -1
  23. package/public/assets/{chat-DeoVTVob.js → chat-BCNCuTgw.js} +2 -2
  24. package/public/assets/{chat-DeoVTVob.js.map → chat-BCNCuTgw.js.map} +1 -1
  25. package/public/assets/{connectors-C2Ac03LJ.js → connectors-h0d5Mc6e.js} +2 -2
  26. package/public/assets/{connectors-C2Ac03LJ.js.map → connectors-h0d5Mc6e.js.map} +1 -1
  27. package/public/assets/{display-CJzSoTMq.js → display-GFbGUG0O.js} +2 -2
  28. package/public/assets/{display-CJzSoTMq.js.map → display-GFbGUG0O.js.map} +1 -1
  29. package/public/assets/{formatted-body-35XBOY5L.js → formatted-body-CVr3Iev9.js} +3 -3
  30. package/public/assets/{formatted-body-35XBOY5L.js.map → formatted-body-CVr3Iev9.js.map} +1 -1
  31. package/public/assets/{formatted-body-impl-RmDxI5Ux.js → formatted-body-impl-Cq5SRGpm.js} +2 -2
  32. package/public/assets/{formatted-body-impl-RmDxI5Ux.js.map → formatted-body-impl-Cq5SRGpm.js.map} +1 -1
  33. package/public/assets/{index-D0xMQQb_.js → index--9UMzCFf.js} +6 -6
  34. package/public/assets/{index-D0xMQQb_.js.map → index--9UMzCFf.js.map} +1 -1
  35. package/public/assets/{insights-B643SFvm.js → insights-C8lIBXg3.js} +2 -2
  36. package/public/assets/{insights-B643SFvm.js.map → insights-C8lIBXg3.js.map} +1 -1
  37. package/public/assets/{integrations-hFIHeCF4.js → integrations-7OFsJtY7.js} +2 -2
  38. package/public/assets/{integrations-hFIHeCF4.js.map → integrations-7OFsJtY7.js.map} +1 -1
  39. package/public/assets/{maintenance-CURvdp_Z.js → maintenance-HDpg20OY.js} +2 -2
  40. package/public/assets/{maintenance-CURvdp_Z.js.map → maintenance-HDpg20OY.js.map} +1 -1
  41. package/public/assets/{managed-agents-DrZtdlSn.js → managed-agents-muCIMxng.js} +2 -2
  42. package/public/assets/{managed-agents-DrZtdlSn.js.map → managed-agents-muCIMxng.js.map} +1 -1
  43. package/public/assets/{markdown-preview-impl-CVDO--ek.js → markdown-preview-impl-Cx7pCfA6.js} +2 -2
  44. package/public/assets/{markdown-preview-impl-CVDO--ek.js.map → markdown-preview-impl-Cx7pCfA6.js.map} +1 -1
  45. package/public/assets/{memory-Bm7dt_Qf.js → memory-CTO1FfJ-.js} +2 -2
  46. package/public/assets/{memory-Bm7dt_Qf.js.map → memory-CTO1FfJ-.js.map} +1 -1
  47. package/public/assets/{messages-DTq4i2KZ.js → messages-RWSXG2UM.js} +2 -2
  48. package/public/assets/{messages-DTq4i2KZ.js.map → messages-RWSXG2UM.js.map} +1 -1
  49. package/public/assets/{orchestrators-Dra318Ap.js → orchestrators-5aiYw3yx.js} +2 -2
  50. package/public/assets/{orchestrators-Dra318Ap.js.map → orchestrators-5aiYw3yx.js.map} +1 -1
  51. package/public/assets/{overview-yr1Vmk64.js → overview-Du6XLt5l.js} +2 -2
  52. package/public/assets/{overview-yr1Vmk64.js.map → overview-Du6XLt5l.js.map} +1 -1
  53. package/public/assets/{pairs-mnlKDBGp.js → pairs-BDRnlEta.js} +2 -2
  54. package/public/assets/{pairs-mnlKDBGp.js.map → pairs-BDRnlEta.js.map} +1 -1
  55. package/public/assets/{projects-Dzdf-gkK.js → projects-DwsWE9es.js} +2 -2
  56. package/public/assets/{projects-Dzdf-gkK.js.map → projects-DwsWE9es.js.map} +1 -1
  57. package/public/assets/{prompt-settings-ByL8SNCi.js → prompt-settings-CaJX_LbA.js} +2 -2
  58. package/public/assets/{prompt-settings-ByL8SNCi.js.map → prompt-settings-CaJX_LbA.js.map} +1 -1
  59. package/public/assets/{registry-CXWyOtpr.js → registry-C-uIL0Nl.js} +2 -2
  60. package/public/assets/{registry-CXWyOtpr.js.map → registry-C-uIL0Nl.js.map} +1 -1
  61. package/public/assets/{security-0SBsc_4W.js → security-BpVrdYhJ.js} +2 -2
  62. package/public/assets/{security-0SBsc_4W.js.map → security-BpVrdYhJ.js.map} +1 -1
  63. package/public/assets/{select-Bw5z2KJ_.js → select-DallbU93.js} +2 -2
  64. package/public/assets/{select-Bw5z2KJ_.js.map → select-DallbU93.js.map} +1 -1
  65. package/public/assets/{settings-78Cyq_tA.js → settings-BUQirdyI.js} +2 -2
  66. package/public/assets/{settings-78Cyq_tA.js.map → settings-BUQirdyI.js.map} +1 -1
  67. package/public/assets/{store-BPrVuKiv.js → store-DMZSTozC.js} +2 -2
  68. package/public/assets/{store-BPrVuKiv.js.map → store-DMZSTozC.js.map} +1 -1
  69. package/public/assets/{tasks-Cg2MMeri.js → tasks-k8mQDfKy.js} +2 -2
  70. package/public/assets/{tasks-Cg2MMeri.js.map → tasks-k8mQDfKy.js.map} +1 -1
  71. package/public/assets/{teams-DWv6UwFV.js → teams-DI-Lhhbl.js} +2 -2
  72. package/public/assets/{teams-DWv6UwFV.js.map → teams-DI-Lhhbl.js.map} +1 -1
  73. package/public/assets/{terminal-viewer-impl-DBLDjbIg.js → terminal-viewer-impl-BL853Fpi.js} +2 -2
  74. package/public/assets/{terminal-viewer-impl-DBLDjbIg.js.map → terminal-viewer-impl-BL853Fpi.js.map} +1 -1
  75. package/public/assets/types-CRptbSYh.js +2 -0
  76. package/public/assets/types-CRptbSYh.js.map +1 -0
  77. package/public/assets/{user-avatar-BsOLyVJj.js → user-avatar-D9xyGi3u.js} +2 -2
  78. package/public/assets/{user-avatar-BsOLyVJj.js.map → user-avatar-D9xyGi3u.js.map} +1 -1
  79. package/public/assets/{work-queue-DmwvnF6F.js → work-queue-DrLIdvxB.js} +2 -2
  80. package/public/assets/{work-queue-DmwvnF6F.js.map → work-queue-DrLIdvxB.js.map} +1 -1
  81. package/public/assets/{workspaces-dIBivJN8.js → workspaces-CnNL6T8Q.js} +2 -2
  82. package/public/assets/{workspaces-dIBivJN8.js.map → workspaces-CnNL6T8Q.js.map} +1 -1
  83. package/public/index.html +6 -6
  84. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  85. package/runner/src/adapters/claude-delivery.ts +127 -0
  86. package/runner/src/adapters/claude-quota-harvest.ts +92 -0
  87. package/runner/src/adapters/claude-session-probe.ts +225 -0
  88. package/runner/src/adapters/claude-status-detectors.ts +147 -0
  89. package/runner/src/adapters/claude-transcript-tail.ts +128 -0
  90. package/runner/src/adapters/claude-transcript.ts +414 -0
  91. package/runner/src/adapters/claude.ts +675 -0
  92. package/runner/src/adapters/codex-agent-message-capture.ts +139 -0
  93. package/runner/src/adapters/codex-client.ts +279 -0
  94. package/runner/src/adapters/codex.ts +1345 -0
  95. package/runner/src/attachment-cache.ts +189 -0
  96. package/runner/src/busy-reconciler.ts +102 -0
  97. package/runner/src/claim-tracker.ts +140 -0
  98. package/runner/src/claude-prompt-gates.ts +268 -0
  99. package/runner/src/codex-version.ts +63 -0
  100. package/runner/src/config.ts +7 -0
  101. package/runner/src/context-probe-state.ts +6 -0
  102. package/runner/src/continuation-archive.ts +179 -0
  103. package/runner/src/control-server.ts +639 -0
  104. package/runner/src/index.ts +396 -0
  105. package/runner/src/launch-assembly.ts +674 -0
  106. package/runner/src/liveness.ts +50 -0
  107. package/runner/src/logger.ts +99 -0
  108. package/runner/src/mcp-outbox.ts +120 -0
  109. package/runner/src/message-body-config/config.ts +3 -0
  110. package/runner/src/message-body-config.ts +1 -0
  111. package/runner/src/native-self-resume.ts +202 -0
  112. package/runner/src/outbox.ts +342 -0
  113. package/runner/src/process-meta.ts +9 -0
  114. package/runner/src/profile-home.ts +260 -0
  115. package/runner/src/profile-projection.ts +324 -0
  116. package/runner/src/providers.ts +20 -0
  117. package/runner/src/provisioning.ts +368 -0
  118. package/runner/src/quota.ts +40 -0
  119. package/runner/src/rate-limit.ts +189 -0
  120. package/runner/src/relay-injection-events.ts +105 -0
  121. package/runner/src/relay-instructions.ts +60 -0
  122. package/runner/src/relay-mcp-proxy.ts +383 -0
  123. package/runner/src/relay-mcp.ts +156 -0
  124. package/runner/src/reply-obligation-cache.ts +136 -0
  125. package/runner/src/response-capture-report.ts +63 -0
  126. package/runner/src/runner-core.ts +2409 -0
  127. package/runner/src/runner-helpers.ts +435 -0
  128. package/runner/src/runner-insights.ts +105 -0
  129. package/runner/src/runner.ts +14 -0
  130. package/runner/src/session-destroy.ts +22 -0
  131. package/runner/src/session-insights.ts +118 -0
  132. package/runner/src/session-scratch.ts +271 -0
  133. package/runner/src/template-resolver.ts +29 -0
  134. package/runner/src/version.ts +44 -0
  135. package/src/config-store.ts +2 -0
  136. package/src/db/provisioning-registry.ts +40 -9
  137. package/src/db/provisioning-resolve.ts +66 -5
  138. package/src/gate-resolver.ts +7 -12
  139. package/src/routes/provisioning-registry.ts +1 -1
  140. package/src/services/spawn-agent.ts +1 -0
  141. package/public/assets/types-BeJUSfDj.js +0 -2
  142. package/public/assets/types-BeJUSfDj.js.map +0 -1
@@ -0,0 +1,63 @@
1
+ // #1096 review, finding 4 — Codex hooks (CODEX_HOME/hooks.json with Claude-compatible
2
+ // event names) are only consumed by Codex >= 0.124. An older Codex silently ignores the
3
+ // file, so writing it and reporting `provisioning.hooks: applied` would be a lie. This
4
+ // module probes the installed Codex version once and answers whether hook delivery is
5
+ // actually honored, so the assembler can emit a truthful projection (a LOUD warning when
6
+ // the guard cannot fire) instead of a silent absence.
7
+ //
8
+ // TODO(#1096): a post-deploy LIVE check is still owed — assert that a Codex >= 0.124 actually
9
+ // executes CODEX_HOME/hooks.json for these event names (Stop/PreToolUse/…), not just that the
10
+ // version string clears the gate. The version probe is necessary but not sufficient.
11
+
12
+ import { codexVersionOverrideFromEnv } from "./config";
13
+
14
+ // Codex releases from this version on ship stable hooks with Claude-compatible events.
15
+ const CODEX_HOOKS_MIN_VERSION = [0, 124, 0] as const;
16
+
17
+ let cachedProbe: boolean | undefined;
18
+
19
+ export function codexVersionSupportsHooks(version: string): boolean {
20
+ const parts = version.split(".").map((n) => parseInt(n, 10));
21
+ const major = Number.isFinite(parts[0]) ? parts[0]! : 0;
22
+ const minor = Number.isFinite(parts[1]) ? parts[1]! : 0;
23
+ const patch = Number.isFinite(parts[2]) ? parts[2]! : 0;
24
+ const [rMajor, rMinor, rPatch] = CODEX_HOOKS_MIN_VERSION;
25
+ if (major !== rMajor) return major > rMajor;
26
+ if (minor !== rMinor) return minor > rMinor;
27
+ return patch >= rPatch;
28
+ }
29
+
30
+ export function parseCodexVersion(output: string): string | null {
31
+ const match = output.match(/(\d+)\.(\d+)\.(\d+)/);
32
+ return match ? match[0] : null;
33
+ }
34
+
35
+ function probeCodexVersion(command: string): string | null {
36
+ try {
37
+ const result = Bun.spawnSync([command, "--version"], { stdout: "pipe", stderr: "ignore" });
38
+ if (result.exitCode !== 0) return null;
39
+ return parseCodexVersion(result.stdout.toString());
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ // Whether the installed Codex actually consumes managed hooks. An explicit
46
+ // AGENT_RELAY_CODEX_VERSION override wins (deterministic tests + operator escape hatch);
47
+ // otherwise the real `codex --version` is probed once and cached. An UNKNOWN version (probe
48
+ // failed / Codex not installed) is treated as supported: we neither block launches nor cry
49
+ // wolf on a machine that can't be inspected — we only claim "not applied" when we can PROVE
50
+ // the version is too old.
51
+ export function codexHooksSupported(command = "codex"): boolean {
52
+ const override = codexVersionOverrideFromEnv();
53
+ if (override !== undefined) return override.trim() === "" ? true : codexVersionSupportsHooks(override.trim());
54
+ if (cachedProbe !== undefined) return cachedProbe;
55
+ const version = probeCodexVersion(command);
56
+ cachedProbe = version ? codexVersionSupportsHooks(version) : true;
57
+ return cachedProbe;
58
+ }
59
+
60
+ // Test-only: drop the memoized probe so a suite can re-probe under a changed environment.
61
+ export function resetCodexHooksProbeCache(): void {
62
+ cachedProbe = undefined;
63
+ }
@@ -82,6 +82,13 @@ function relayUrlFromEnv(): string | undefined {
82
82
  return process.env.AGENT_RELAY_URL;
83
83
  }
84
84
 
85
+ // #1096 — override for the Codex hooks version gate (finding 4). An explicit version string
86
+ // bypasses the `codex --version` probe: deterministic in tests, and an operator escape hatch
87
+ // when the probe can't run. Empty string means "assume supported".
88
+ export function codexVersionOverrideFromEnv(): string | undefined {
89
+ return process.env.AGENT_RELAY_CODEX_VERSION;
90
+ }
91
+
85
92
  function textEnvOrFile(name: string): string | undefined {
86
93
  const inline = process.env[name];
87
94
  if (inline) return inline;
@@ -0,0 +1,6 @@
1
+ import { readContextProbeState } from "agent-relay-sdk/context-probe";
2
+ import { contextStateDirFromEnv } from "./config";
3
+
4
+ export function readRunnerContextProbeState(agentId: string) {
5
+ return readContextProbeState(agentId, contextStateDirFromEnv());
6
+ }
@@ -0,0 +1,179 @@
1
+ import type { RelayHttpClient } from "agent-relay-sdk";
2
+ import type { OutboxRecord } from "./outbox";
3
+
4
+ const CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES = 64 * 1024;
5
+ const CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES = 56 * 1024;
6
+ const CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES = 8 * 1024 * 1024;
7
+ const encoder = new TextEncoder();
8
+ const fatalUtf8Decoder = new TextDecoder("utf-8", { fatal: true });
9
+
10
+ interface ContinuationArchiveOutboxPayload {
11
+ agentId: string;
12
+ segment: string;
13
+ generation?: number;
14
+ deliveredChunks?: number;
15
+ totalChunks?: number;
16
+ }
17
+
18
+ interface ContinuationArchiveResponse {
19
+ archive?: { generation?: unknown };
20
+ }
21
+
22
+ export function boundContinuationArchiveSegment(segment: string): { segment: string; keptBytes: number; droppedBytes: number } {
23
+ const encoded = encoder.encode(segment);
24
+ const bytes = encoded.byteLength;
25
+ if (bytes <= CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES) {
26
+ return { segment, keptBytes: bytes, droppedBytes: 0 };
27
+ }
28
+ const bounded = decodeUtf8Prefix(encoded, CONTINUATION_ARCHIVE_MAX_GENERATION_BYTES);
29
+ const keptBytes = utf8Bytes(bounded);
30
+ return { segment: bounded.trimEnd(), keptBytes, droppedBytes: bytes - keptBytes };
31
+ }
32
+
33
+ export async function deliverContinuationArchiveRecord(input: {
34
+ record: OutboxRecord;
35
+ http: Pick<RelayHttpClient, "recordContinuationArchive">;
36
+ updatePayload: (seq: number, payload: unknown) => void;
37
+ sessionLog: (message: string) => void;
38
+ }): Promise<void> {
39
+ const payload = input.record.payload as ContinuationArchiveOutboxPayload;
40
+ let segment = payload.segment;
41
+ if (!payload.agentId || typeof segment !== "string") throw new Error("invalid continuation archive outbox payload");
42
+
43
+ const bounded = boundContinuationArchiveSegment(segment);
44
+ if (bounded.droppedBytes > 0) {
45
+ segment = bounded.segment;
46
+ input.sessionLog(`continuation archive truncated at ${bounded.keptBytes} bytes; dropped ${bounded.droppedBytes} bytes before delivery`);
47
+ input.updatePayload(input.record.seq, { ...payload, segment, deliveredChunks: 0, totalChunks: undefined, generation: undefined });
48
+ }
49
+
50
+ const chunks = splitContinuationArchiveSegment({
51
+ agentId: payload.agentId,
52
+ segment,
53
+ occurredAt: input.record.occurredAt,
54
+ });
55
+ const knownGeneration = Number.isSafeInteger(payload.generation) ? payload.generation : undefined;
56
+ const deliveredChunks = knownGeneration === undefined ? 0 : Math.max(0, Math.min(payload.deliveredChunks ?? 0, chunks.length));
57
+ let generation = knownGeneration;
58
+
59
+ for (let index = deliveredChunks; index < chunks.length; index += 1) {
60
+ const request = {
61
+ agentId: payload.agentId,
62
+ segment: chunks[index]!,
63
+ occurredAt: input.record.occurredAt,
64
+ ...(generation !== undefined ? { generation } : {}),
65
+ };
66
+ assertContinuationArchivePostFits(request);
67
+ const response = await input.http.recordContinuationArchive(request) as ContinuationArchiveResponse;
68
+ const returnedGeneration = archiveGeneration(response);
69
+ if (generation === undefined) {
70
+ if (returnedGeneration === undefined) throw new Error("continuation archive response missing generation");
71
+ generation = returnedGeneration;
72
+ } else if (returnedGeneration !== undefined && returnedGeneration !== generation) {
73
+ throw new Error(`continuation archive generation mismatch: expected ${generation}, got ${returnedGeneration}`);
74
+ }
75
+ input.updatePayload(input.record.seq, {
76
+ ...payload,
77
+ segment,
78
+ generation,
79
+ deliveredChunks: index + 1,
80
+ totalChunks: chunks.length,
81
+ } satisfies ContinuationArchiveOutboxPayload);
82
+ }
83
+ }
84
+
85
+ function splitContinuationArchiveSegment(input: { agentId: string; segment: string; occurredAt: number }): string[] {
86
+ const chunks: string[] = [];
87
+ let current = "";
88
+ for (const line of transcriptLines(input.segment)) {
89
+ if (!line) continue;
90
+ if (fitsContinuationArchiveChunk(input.agentId, line, input.occurredAt)) {
91
+ const next = current ? current + line : line;
92
+ if (utf8Bytes(next) <= CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES && fitsContinuationArchiveChunk(input.agentId, next, input.occurredAt)) {
93
+ current = next;
94
+ continue;
95
+ }
96
+ if (current) chunks.push(current.trimEnd());
97
+ current = line;
98
+ continue;
99
+ }
100
+ if (current) {
101
+ chunks.push(current.trimEnd());
102
+ current = "";
103
+ }
104
+ chunks.push(...splitLongContinuationArchiveLine(input.agentId, line, input.occurredAt));
105
+ }
106
+ if (current) chunks.push(current.trimEnd());
107
+ return chunks.filter((chunk) => chunk.length > 0);
108
+ }
109
+
110
+ function transcriptLines(segment: string): string[] {
111
+ const lines = segment.match(/[^\n]*(?:\n|$)/g) ?? [segment];
112
+ return lines.filter((line) => line.length > 0);
113
+ }
114
+
115
+ function splitLongContinuationArchiveLine(agentId: string, line: string, occurredAt: number): string[] {
116
+ const chars = Array.from(line);
117
+ const chunks: string[] = [];
118
+ let offset = 0;
119
+ while (offset < chars.length) {
120
+ let low = 1;
121
+ let high = chars.length - offset;
122
+ let best = 0;
123
+ while (low <= high) {
124
+ const mid = Math.floor((low + high) / 2);
125
+ const candidate = chars.slice(offset, offset + mid).join("");
126
+ if (utf8Bytes(candidate) <= CONTINUATION_ARCHIVE_CHUNK_TARGET_BYTES && fitsContinuationArchiveChunk(agentId, candidate, occurredAt)) {
127
+ best = mid;
128
+ low = mid + 1;
129
+ } else {
130
+ high = mid - 1;
131
+ }
132
+ }
133
+ if (best === 0) throw new Error("continuation archive chunk budget cannot fit one character");
134
+ chunks.push(chars.slice(offset, offset + best).join(""));
135
+ offset += best;
136
+ }
137
+ return chunks;
138
+ }
139
+
140
+ function fitsContinuationArchiveChunk(agentId: string, segment: string, occurredAt: number): boolean {
141
+ return continuationArchivePostBytes({
142
+ agentId,
143
+ segment,
144
+ occurredAt,
145
+ generation: Number.MAX_SAFE_INTEGER,
146
+ }) < CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES;
147
+ }
148
+
149
+ function assertContinuationArchivePostFits(input: { agentId: string; segment: string; occurredAt: number; generation?: number }): void {
150
+ const bytes = continuationArchivePostBytes(input);
151
+ if (bytes >= CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES) {
152
+ throw new Error(`continuation archive chunk JSON body is ${bytes} bytes; max is ${CONTINUATION_ARCHIVE_MAX_POST_BODY_BYTES - 1}`);
153
+ }
154
+ }
155
+
156
+ function continuationArchivePostBytes(input: { agentId: string; segment: string; occurredAt: number; generation?: number }): number {
157
+ return utf8Bytes(JSON.stringify(input));
158
+ }
159
+
160
+ function archiveGeneration(response: ContinuationArchiveResponse): number | undefined {
161
+ const generation = response.archive?.generation;
162
+ return typeof generation === "number" && Number.isSafeInteger(generation) && generation >= 0 ? generation : undefined;
163
+ }
164
+
165
+ function decodeUtf8Prefix(bytes: Uint8Array, maxBytes: number): string {
166
+ let end = Math.min(maxBytes, bytes.byteLength);
167
+ while (end > 0) {
168
+ try {
169
+ return fatalUtf8Decoder.decode(bytes.subarray(0, end));
170
+ } catch {
171
+ end -= 1;
172
+ }
173
+ }
174
+ return "";
175
+ }
176
+
177
+ function utf8Bytes(value: string): number {
178
+ return encoder.encode(value).byteLength;
179
+ }