agent-embassy 1.7.0 → 1.8.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 (118) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +51 -47
  3. package/README.md +26 -9
  4. package/README.zh-CN.md +22 -9
  5. package/SECURITY.md +48 -35
  6. package/dist/src/gateway/acp-client.d.ts +12 -19
  7. package/dist/src/gateway/acp-client.js +90 -65
  8. package/dist/src/gateway/acp-client.js.map +1 -1
  9. package/dist/src/gateway/acp-provider.d.ts +11 -10
  10. package/dist/src/gateway/acp-provider.js +165 -53
  11. package/dist/src/gateway/acp-provider.js.map +1 -1
  12. package/dist/src/gateway/claude-helper-client.d.ts +2 -43
  13. package/dist/src/gateway/claude-helper-client.js +1 -211
  14. package/dist/src/gateway/claude-helper-client.js.map +1 -1
  15. package/dist/src/gateway/claude-helper-protocol.d.ts +16 -74
  16. package/dist/src/gateway/claude-helper-protocol.js +99 -378
  17. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  18. package/dist/src/gateway/claude-helper-supervisor.d.ts +56 -44
  19. package/dist/src/gateway/claude-helper-supervisor.js +265 -456
  20. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  21. package/dist/src/gateway/claude-helper.js +184 -210
  22. package/dist/src/gateway/claude-helper.js.map +1 -1
  23. package/dist/src/gateway/claude-peer.d.ts +20 -87
  24. package/dist/src/gateway/claude-peer.js +310 -1009
  25. package/dist/src/gateway/claude-peer.js.map +1 -1
  26. package/dist/src/gateway/claude-runtime.d.ts +13 -15
  27. package/dist/src/gateway/claude-runtime.js +14 -168
  28. package/dist/src/gateway/claude-runtime.js.map +1 -1
  29. package/dist/src/gateway/cli-copy.en.d.ts +2 -2
  30. package/dist/src/gateway/cli-copy.en.js +4 -2
  31. package/dist/src/gateway/cli-copy.en.js.map +1 -1
  32. package/dist/src/gateway/cli-copy.zh-CN.d.ts +2 -2
  33. package/dist/src/gateway/cli-copy.zh-CN.js +4 -2
  34. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
  35. package/dist/src/gateway/cli.d.ts +7 -2
  36. package/dist/src/gateway/cli.js +277 -575
  37. package/dist/src/gateway/cli.js.map +1 -1
  38. package/dist/src/gateway/codex-app-server.d.ts +5 -227
  39. package/dist/src/gateway/codex-app-server.js +18 -1463
  40. package/dist/src/gateway/codex-app-server.js.map +1 -1
  41. package/dist/src/gateway/codex-doctor.d.ts +36 -0
  42. package/dist/src/gateway/codex-doctor.js +127 -0
  43. package/dist/src/gateway/codex-doctor.js.map +1 -0
  44. package/dist/src/gateway/codex-local-transport.d.ts +6 -6
  45. package/dist/src/gateway/codex-local-transport.js +13 -10
  46. package/dist/src/gateway/codex-local-transport.js.map +1 -1
  47. package/dist/src/gateway/codex-stateless-transport.d.ts +114 -0
  48. package/dist/src/gateway/codex-stateless-transport.js +1108 -0
  49. package/dist/src/gateway/codex-stateless-transport.js.map +1 -0
  50. package/dist/src/gateway/config.d.ts +2 -9
  51. package/dist/src/gateway/config.js +62 -100
  52. package/dist/src/gateway/config.js.map +1 -1
  53. package/dist/src/gateway/control.d.ts +36 -90
  54. package/dist/src/gateway/control.js +465 -1123
  55. package/dist/src/gateway/control.js.map +1 -1
  56. package/dist/src/gateway/dashboard-copy.d.ts +1 -1
  57. package/dist/src/gateway/dashboard-copy.en.d.ts +8 -26
  58. package/dist/src/gateway/dashboard-copy.en.js +19 -37
  59. package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
  60. package/dist/src/gateway/dashboard-copy.js +8 -26
  61. package/dist/src/gateway/dashboard-copy.js.map +1 -1
  62. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +8 -26
  63. package/dist/src/gateway/dashboard-copy.zh-CN.js +19 -37
  64. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
  65. package/dist/src/gateway/dashboard-model.d.ts +8 -22
  66. package/dist/src/gateway/dashboard-model.js +44 -130
  67. package/dist/src/gateway/dashboard-model.js.map +1 -1
  68. package/dist/src/gateway/dashboard.d.ts +0 -6
  69. package/dist/src/gateway/dashboard.js +3 -7
  70. package/dist/src/gateway/dashboard.js.map +1 -1
  71. package/dist/src/gateway/live-dashboard-app/app.js +20 -34
  72. package/dist/src/gateway/live-dashboard-command.js +2 -2
  73. package/dist/src/gateway/live-dashboard-command.js.map +1 -1
  74. package/dist/src/gateway/live-dashboard-http.d.ts +1 -1
  75. package/dist/src/gateway/live-dashboard-http.js +2 -2
  76. package/dist/src/gateway/live-dashboard-http.js.map +1 -1
  77. package/dist/src/gateway/progress-watch-machine.d.ts +1 -37
  78. package/dist/src/gateway/progress-watch-machine.js +4 -15
  79. package/dist/src/gateway/progress-watch-machine.js.map +1 -1
  80. package/dist/src/gateway/providers.d.ts +58 -191
  81. package/dist/src/gateway/providers.js +637 -2434
  82. package/dist/src/gateway/providers.js.map +1 -1
  83. package/dist/src/gateway/server.d.ts +16 -25
  84. package/dist/src/gateway/server.js +165 -240
  85. package/dist/src/gateway/server.js.map +1 -1
  86. package/dist/src/gateway/service.d.ts +149 -427
  87. package/dist/src/gateway/service.js +1639 -5662
  88. package/dist/src/gateway/service.js.map +1 -1
  89. package/dist/src/gateway/state-v2-to-v3.d.ts +23 -0
  90. package/dist/src/gateway/state-v2-to-v3.js +994 -0
  91. package/dist/src/gateway/state-v2-to-v3.js.map +1 -0
  92. package/dist/src/gateway/store.d.ts +54 -309
  93. package/dist/src/gateway/store.js +1560 -3510
  94. package/dist/src/gateway/store.js.map +1 -1
  95. package/dist/src/gateway/types.d.ts +181 -225
  96. package/dist/src/gateway/types.js +115 -251
  97. package/dist/src/gateway/types.js.map +1 -1
  98. package/docs/CONFIGURATION.md +22 -9
  99. package/docs/CONFIGURATION.zh-CN.md +21 -9
  100. package/docs/DASHBOARD.md +10 -10
  101. package/docs/DASHBOARD.zh-CN.md +3 -3
  102. package/docs/DELIVERY.md +4 -4
  103. package/docs/DELIVERY.zh-CN.md +4 -4
  104. package/docs/GATEWAY-ARCHITECTURE.md +158 -187
  105. package/package.json +1 -1
  106. package/skills/embassy-peer/SKILL.md +28 -22
  107. package/dist/src/gateway/codex-registration-generation.d.ts +0 -5
  108. package/dist/src/gateway/codex-registration-generation.js +0 -19
  109. package/dist/src/gateway/codex-registration-generation.js.map +0 -1
  110. package/dist/src/gateway/codex-registration-succession.d.ts +0 -209
  111. package/dist/src/gateway/codex-registration-succession.js +0 -588
  112. package/dist/src/gateway/codex-registration-succession.js.map +0 -1
  113. package/dist/src/gateway/compatibility.d.ts +0 -22
  114. package/dist/src/gateway/compatibility.js +0 -23
  115. package/dist/src/gateway/compatibility.js.map +0 -1
  116. package/dist/src/gateway/delivery-machine.d.ts +0 -235
  117. package/dist/src/gateway/delivery-machine.js +0 -540
  118. package/dist/src/gateway/delivery-machine.js.map +0 -1
@@ -1,537 +1,346 @@
1
+ import { fork } from "node:child_process";
1
2
  import { randomBytes } from "node:crypto";
3
+ import { fileURLToPath } from "node:url";
2
4
  import { BridgeError } from "../errors.js";
3
- import { createClaudeNativeHelper, } from "./claude-helper-client.js";
4
- import { isGatewayProvider, } from "./types.js";
5
- const TERMINAL_DELIVERY_STATES = new Set([
6
- "released",
7
- "unconfirmed",
8
- "denied",
9
- "expired",
10
- "ambiguous",
11
- "completed",
12
- "failed",
13
- "cancelled",
14
- ]);
15
- const PUBLIC_ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
16
- const CONVERSATION_ID = /^conv_[A-Za-z0-9_-]{16,64}$/;
17
- const MAX_RAW_BODY_BYTES = 16 * 1024;
5
+ import { gatewayRegistrationIngressPrefixes, isGatewayProvider } from "./types.js";
6
+ import { assertClaudeNativeHelperIpcSize, CLAUDE_NATIVE_HELPER_MAX_REQUESTS, isClaudeNativeHelperChildMessage, } from "./claude-helper-protocol.js";
7
+ const TIMEOUT = 5_000, CLOSE_TIMEOUT = 2_000;
8
+ const ALIAS = /^[a-z][a-z0-9_-]{0,31}@[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?$/;
9
+ const CONVERSATION = /^conv_[A-Za-z0-9_-]{16,64}$/;
18
10
  function fault(code, recoverable = false) {
19
- return new BridgeError(code, "The supervised native Claude advertisement could not complete its bounded operation.", recoverable);
11
+ return new BridgeError(code, "The supervised native Claude helper could not complete its bounded operation.", recoverable);
20
12
  }
21
- function ok(result) {
22
- if (!("ok" in result) || result.ok !== true) {
13
+ const id = () => randomBytes(18).toString("base64url");
14
+ function requireOk(result) {
15
+ if (!("ok" in result) || result.ok !== true)
23
16
  throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
17
+ }
18
+ export class ClaudeNativeHelperClient {
19
+ child;
20
+ callbacks;
21
+ pid;
22
+ registration;
23
+ generation = "";
24
+ #pending = new Map();
25
+ #exit;
26
+ #resolveExit;
27
+ #closed = false;
28
+ #exited = false;
29
+ constructor(child, registration, callbacks) {
30
+ this.child = child;
31
+ this.callbacks = callbacks;
32
+ if (!Number.isSafeInteger(child.pid) || child.pid <= 0)
33
+ throw fault("CLAUDE_NATIVE_HELPER_PID_INVALID");
34
+ this.pid = child.pid;
35
+ this.registration = registration;
36
+ this.#exit = new Promise((resolve) => { this.#resolveExit = resolve; });
37
+ child.on("message", (value) => this.#message(value));
38
+ child.once("error", () => this.#fail(fault("CLAUDE_NATIVE_HELPER_SPAWN_FAILED")));
39
+ child.once("exit", (code, signal) => {
40
+ if (this.#exited)
41
+ return;
42
+ this.#exited = true;
43
+ this.#fail(fault("CLAUDE_NATIVE_HELPER_EXITED"));
44
+ this.#resolveExit();
45
+ callbacks.onExit({ code, signal });
46
+ });
47
+ }
48
+ static async start(options) {
49
+ const child = fork(options.entryPath ?? fileURLToPath(new URL("./claude-helper.js", import.meta.url)), [], {
50
+ cwd: "/", env: { LANG: "C", LC_ALL: "C", TZ: "UTC" }, execPath: process.execPath,
51
+ execArgv: [], serialization: "json", stdio: ["ignore", "ignore", "ignore", "ipc"],
52
+ });
53
+ const client = new ClaudeNativeHelperClient(child, options.registration, options.callbacks);
54
+ const init = { protocolVersion: 1, type: "initialize", requestId: id(),
55
+ runtime: options.runtime, hostId: options.hostId, locale: options.locale, deliveryNotices: options.deliveryNotices,
56
+ maxPendingMessages: options.maxPendingMessages, registration: options.registration };
57
+ try {
58
+ const result = await client.#send(init);
59
+ if (!("generation" in result))
60
+ throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
61
+ client.generation = result.generation;
62
+ return client;
63
+ }
64
+ catch (error) {
65
+ await client.forceClose();
66
+ throw error;
67
+ }
68
+ }
69
+ request(command, timeoutMs = TIMEOUT) {
70
+ return this.#send({ protocolVersion: 1, type: "request", requestId: id(), command }, timeoutMs);
71
+ }
72
+ async close() {
73
+ if (!this.#closed) {
74
+ try {
75
+ await this.request({ method: "close" }, CLOSE_TIMEOUT);
76
+ }
77
+ catch {
78
+ this.child.kill("SIGTERM");
79
+ }
80
+ this.#closed = true;
81
+ }
82
+ await this.#awaitExit();
83
+ }
84
+ async forceClose() { this.#closed = true; if (!this.#exited)
85
+ this.child.kill("SIGTERM"); await this.#awaitExit(); }
86
+ async #awaitExit() {
87
+ const timer = setTimeout(() => this.child.kill("SIGKILL"), CLOSE_TIMEOUT);
88
+ timer.unref();
89
+ await this.#exit;
90
+ clearTimeout(timer);
91
+ }
92
+ async #send(message, timeoutMs = TIMEOUT) {
93
+ if (this.#closed || this.#exited || !this.child.connected || this.child.killed)
94
+ throw fault("CLAUDE_NATIVE_HELPER_UNAVAILABLE", true);
95
+ if (this.#pending.size >= CLAUDE_NATIVE_HELPER_MAX_REQUESTS)
96
+ throw fault("CLAUDE_NATIVE_HELPER_REQUEST_CAPACITY", true);
97
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 60_000)
98
+ throw fault("CLAUDE_NATIVE_HELPER_TIMEOUT_INVALID");
99
+ assertClaudeNativeHelperIpcSize(message);
100
+ const pending = new Promise((resolve, reject) => {
101
+ const timer = setTimeout(() => {
102
+ this.#pending.delete(message.requestId);
103
+ reject(fault("CLAUDE_NATIVE_HELPER_REQUEST_TIMEOUT"));
104
+ }, timeoutMs);
105
+ timer.unref();
106
+ this.#pending.set(message.requestId, { resolve, reject, timer });
107
+ });
108
+ this.child.send(message, (error) => {
109
+ if (error === null)
110
+ return;
111
+ const entry = this.#pending.get(message.requestId);
112
+ if (entry) {
113
+ clearTimeout(entry.timer);
114
+ this.#pending.delete(message.requestId);
115
+ entry.reject(fault("CLAUDE_NATIVE_HELPER_IPC_FAILED", true));
116
+ }
117
+ });
118
+ return await pending;
119
+ }
120
+ #message(value) {
121
+ try {
122
+ assertClaudeNativeHelperIpcSize(value);
123
+ if (!isClaudeNativeHelperChildMessage(value))
124
+ throw fault("CLAUDE_NATIVE_HELPER_PROTOCOL_INVALID");
125
+ }
126
+ catch (error) {
127
+ this.#fail(error);
128
+ this.child.kill("SIGTERM");
129
+ return;
130
+ }
131
+ const message = value;
132
+ if (message.type === "event") {
133
+ this.callbacks.onEvent(message.value);
134
+ return;
135
+ }
136
+ const pending = this.#pending.get(message.requestId);
137
+ if (!pending)
138
+ return;
139
+ clearTimeout(pending.timer);
140
+ this.#pending.delete(message.requestId);
141
+ if (message.ok)
142
+ pending.resolve(message.result);
143
+ else
144
+ pending.reject(fault(message.error.code, message.error.recoverable));
24
145
  }
146
+ #fail(error) { for (const pending of this.#pending.values()) {
147
+ clearTimeout(pending.timer);
148
+ pending.reject(error);
149
+ } this.#pending.clear(); }
25
150
  }
151
+ export const createClaudeNativeHelper = ClaudeNativeHelperClient.start;
26
152
  export class ClaudeNativeHelperSupervisor {
27
- #identity;
28
- #runtime;
29
- #locale;
30
- #deliveryNotices;
31
- #maxPendingMessages;
32
- #maxHelpers;
33
- #callbacks;
153
+ options;
154
+ #helpers = new Map();
155
+ #receipts = new Map();
156
+ #preparations = new Set();
34
157
  #factory;
35
- #helpersByAlias = new Map();
36
- #helpersByGeneration = new Map();
37
- #receiptOwners = new Map();
38
- #pendingDispatches = new Map();
39
158
  #closed = false;
40
159
  constructor(options) {
41
- this.#identity = { ...options.identity };
42
- this.#runtime = { ...options.runtime };
43
- this.#locale = options.locale;
44
- this.#deliveryNotices = options.deliveryNotices;
45
- this.#maxPendingMessages = options.maxPendingMessages;
46
- this.#maxHelpers = options.maxHelpers;
47
- this.#callbacks = options.callbacks;
160
+ this.options = options;
48
161
  this.#factory = options.factory ?? createClaudeNativeHelper;
49
162
  }
50
- get size() {
51
- return this.#helpersByAlias.size;
52
- }
163
+ get size() { return this.#helpers.size; }
53
164
  async advertise(input) {
54
- this.#assertOpen();
55
- if (!isGatewayProvider(input.sourceProvider)) {
165
+ if (this.#closed)
166
+ throw fault("CLAUDE_NATIVE_HELPER_SUPERVISOR_CLOSED");
167
+ if (!isGatewayProvider(input.sourceProvider) || !gatewayRegistrationIngressPrefixes[input.sourceProvider] ||
168
+ !input.alias.startsWith(gatewayRegistrationIngressPrefixes[input.sourceProvider]))
56
169
  throw fault("PROVENANCE_ENVELOPE_INVALID");
57
- }
58
- const incumbent = this.#helpersByAlias.get(input.alias);
59
- if (incumbent !== undefined) {
60
- if (incumbent.sourceProvider !== input.sourceProvider) {
170
+ const old = this.#helpers.get(input.alias);
171
+ if (old) {
172
+ if (old.sourceProvider !== input.sourceProvider)
61
173
  throw fault("PROVENANCE_ENVELOPE_INVALID");
62
- }
63
174
  return;
64
175
  }
65
- if (this.#helpersByAlias.size >= this.#maxHelpers) {
176
+ if (this.#helpers.size >= this.options.maxHelpers)
66
177
  throw fault("CLAUDE_NATIVE_HELPER_CAPACITY", true);
67
- }
68
- let record;
178
+ let helper, earlyExit = false;
69
179
  const buffered = [];
70
- let exitedBeforeReady = false;
71
- const client = await this.#factory({
72
- runtime: this.#runtime,
73
- hostId: "this-mac",
74
- locale: this.#locale,
75
- deliveryNotices: this.#deliveryNotices,
76
- maxPendingMessages: this.#maxPendingMessages,
77
- registration: input,
78
- callbacks: {
79
- onEvent: (event) => {
80
- if (record === undefined)
81
- buffered.push(event);
82
- else
83
- this.#onEvent(record, event);
84
- },
85
- onExit: () => {
86
- if (record === undefined)
87
- exitedBeforeReady = true;
88
- else
89
- this.#onExit(record);
90
- },
91
- },
92
- });
93
- if (this.#closed || exitedBeforeReady) {
180
+ const client = await this.#factory({ runtime: this.options.runtime, hostId: "this-mac", locale: this.options.locale,
181
+ deliveryNotices: this.options.deliveryNotices, maxPendingMessages: this.options.maxPendingMessages, registration: input,
182
+ callbacks: { onEvent: (event) => helper ? this.#event(helper, event) : buffered.push(event),
183
+ onExit: () => helper ? this.#exit(helper) : earlyExit = true } });
184
+ if (this.#closed || earlyExit) {
94
185
  await client.forceClose().catch(() => undefined);
95
186
  throw fault("CLAUDE_NATIVE_HELPER_UNAVAILABLE", true);
96
187
  }
97
- record = {
98
- client,
99
- activeAlias: input.alias,
100
- sourceProvider: input.sourceProvider,
101
- activeGeneration: client.generation,
102
- authorizedRoutes: new Map(),
103
- closing: false,
104
- };
105
- this.#helpersByAlias.set(input.alias, record);
106
- this.#helpersByGeneration.set(client.generation, record);
188
+ helper = { client, alias: input.alias, sourceProvider: input.sourceProvider, closing: false };
189
+ this.#helpers.set(input.alias, helper);
107
190
  for (const event of buffered)
108
- this.#onEvent(record, event);
109
- try {
110
- ok(await client.request({
111
- method: "resume_generation",
112
- generation: client.generation,
113
- }));
114
- }
115
- catch (error) {
116
- // The child may have activated before the response was lost. Preserve
117
- // exact ownership in the supervisor so ordinary rollback can close it.
118
- throw error;
119
- }
120
- }
121
- currentGeneration(alias) {
122
- const helper = this.#helpersByAlias.get(alias);
123
- if (helper === undefined || helper.closing) {
124
- throw fault("CODEX_PEER_GENERATION_MISMATCH");
125
- }
126
- return helper.activeGeneration;
191
+ this.#event(helper, event);
127
192
  }
128
193
  async updateStatus(alias, status) {
129
- const helper = this.#helpersByAlias.get(alias);
130
- if (helper === undefined || helper.closing)
131
- return;
132
- ok(await helper.client.request({ method: "update_status", alias, status }));
194
+ const helper = this.#helpers.get(alias);
195
+ if (helper && !helper.closing)
196
+ requireOk(await helper.client.request({ method: "update_status", alias, status }));
133
197
  }
134
198
  async unadvertise(alias) {
135
- const helper = this.#helpersByAlias.get(alias);
136
- if (helper === undefined)
199
+ const helper = this.#helpers.get(alias);
200
+ if (!helper)
137
201
  return;
138
202
  helper.closing = true;
139
203
  let failure;
140
204
  try {
141
- ok(await helper.client.request({ method: "unadvertise", alias }));
205
+ requireOk(await helper.client.request({ method: "unadvertise", alias }));
142
206
  }
143
207
  catch (error) {
144
208
  failure = error;
145
209
  }
146
- await helper.client.close().catch((error) => {
147
- failure ??= error;
148
- });
149
- this.#removeHelper(helper);
150
- if (failure !== undefined)
210
+ await helper.client.close().catch((error) => { failure ??= error; });
211
+ this.#remove(helper);
212
+ if (failure)
151
213
  throw failure;
152
214
  }
153
- async dispatch(input) {
154
- if (typeof input.sourceAlias !== "string" ||
155
- !PUBLIC_ALIAS.test(input.sourceAlias) ||
156
- !isGatewayProvider(input.sourceProvider) ||
157
- typeof input.targetAlias !== "string" ||
158
- !PUBLIC_ALIAS.test(input.targetAlias) ||
159
- typeof input.conversationId !== "string" ||
160
- !CONVERSATION_ID.test(input.conversationId) ||
161
- typeof input.text !== "string") {
162
- return { state: "failed", safeErrorCode: "PROVENANCE_ENVELOPE_INVALID" };
163
- }
164
- if (Buffer.byteLength(input.text, "utf8") > MAX_RAW_BODY_BYTES) {
165
- return {
166
- state: "failed",
167
- safeErrorCode: "PROVENANCE_ENVELOPE_TOO_LARGE",
168
- };
169
- }
170
- const helper = this.#helpersByAlias.get(input.sourceAlias);
171
- if (helper === undefined || helper.closing) {
172
- return { state: "failed", safeErrorCode: "CLAUDE_NATIVE_HELPER_UNAVAILABLE" };
173
- }
174
- if (helper.sourceProvider !== input.sourceProvider) {
175
- return { state: "failed", safeErrorCode: "PROVENANCE_ENVELOPE_INVALID" };
176
- }
177
- if (input.authorization === "selected_route") {
178
- if (input.selectedAlias === undefined ||
179
- input.stateRoot === undefined) {
180
- return { state: "failed", safeErrorCode: "CLAUDE_ROUTE_UNAVAILABLE" };
181
- }
182
- const authority = `${input.selectedAlias}\0${input.stateRoot}`;
183
- if (helper.authorizedRoutes.get(input.binding.routeHandle) !== authority) {
184
- ok(await helper.client.request({
185
- method: "authorize_route",
186
- alias: input.selectedAlias,
187
- routeHandle: input.binding.routeHandle,
188
- stateRoot: input.stateRoot,
189
- }));
190
- helper.authorizedRoutes.set(input.binding.routeHandle, authority);
215
+ async prepareDispatch(input) {
216
+ if (!ALIAS.test(input.sourceAlias) || !ALIAS.test(input.targetAlias) || !CONVERSATION.test(input.conversationId) ||
217
+ !isGatewayProvider(input.sourceProvider))
218
+ throw fault("PROVENANCE_ENVELOPE_INVALID");
219
+ if (Buffer.byteLength(input.text) > 16 * 1024)
220
+ throw fault("PROVENANCE_ENVELOPE_TOO_LARGE");
221
+ const helper = this.#helpers.get(input.sourceAlias);
222
+ if (!helper || helper.closing)
223
+ throw fault("CLAUDE_NATIVE_HELPER_UNAVAILABLE", true);
224
+ if (helper.sourceProvider !== input.sourceProvider)
225
+ throw fault("PROVENANCE_ENVELOPE_INVALID");
226
+ if (input.authorization === "selected_route" && !input.stateRoot)
227
+ throw fault("CLAUDE_ROUTE_UNAVAILABLE", true);
228
+ const result = await helper.client.request({ method: "prepare_dispatch", binding: input.binding,
229
+ authorization: input.authorization, ...(input.authorization === "selected_route" ? { stateRoot: input.stateRoot } : {}),
230
+ messageId: input.messageId, sourceAlias: helper.alias, sourceProvider: helper.sourceProvider,
231
+ targetAlias: input.targetAlias, conversationId: input.conversationId, text: input.text,
232
+ expectsReply: input.expectsReply, deadlineAt: input.deadlineAt,
233
+ ...(input.progressWatchActive ? { progressWatchActive: true } : {}) }, this.#deadline(input.deadlineAt));
234
+ if (!("preparationId" in result))
235
+ throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
236
+ let state = "prepared";
237
+ let tracked;
238
+ const cancel = async () => {
239
+ if (state !== "prepared")
240
+ return;
241
+ state = "consumed";
242
+ this.#preparations.delete(tracked);
243
+ try {
244
+ requireOk(await helper.client.request({ method: "cancel_dispatch", preparationId: result.preparationId }));
191
245
  }
192
- }
193
- this.#pendingDispatches.set(input.messageId, {
194
- helper,
195
- evidence: "none",
196
- });
197
- try {
198
- const result = await helper.client.request({
199
- method: "dispatch",
200
- binding: input.binding,
201
- authorization: input.authorization,
202
- messageId: input.messageId,
203
- sourceAlias: helper.activeAlias,
204
- sourceProvider: helper.sourceProvider,
205
- targetAlias: input.targetAlias,
206
- conversationId: input.conversationId,
207
- text: input.text,
208
- expectsReply: input.expectsReply,
209
- deadlineAt: input.deadlineAt,
210
- ...(input.progressWatchActive === true
211
- ? { progressWatchActive: true }
212
- : {}),
213
- }, Math.max(1, Math.min(60_000, Date.parse(input.deadlineAt) - Date.now())));
214
- if (!("state" in result) ||
215
- ![
216
- "pending",
217
- "accepted",
218
- "deferred",
219
- "delivered",
220
- "failed",
221
- "ambiguous",
222
- "cancelled",
223
- ].includes(String(result.state))) {
224
- throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
246
+ catch (error) {
247
+ if (!(error instanceof BridgeError) || error.code !== "CLAUDE_NATIVE_PREPARATION_UNKNOWN")
248
+ throw error;
225
249
  }
226
- if (result.state !== "pending")
227
- this.#pendingDispatches.delete(input.messageId);
228
- return result;
229
- }
230
- catch (error) {
231
- const pending = this.#pendingDispatches.get(input.messageId);
232
- if (pending?.evidence === "none")
233
- this.#pendingDispatches.delete(input.messageId);
234
- throw error;
235
- }
236
- }
237
- async releaseRoute(routeHandle) {
238
- const results = await Promise.allSettled([...new Set(this.#helpersByAlias.values())].map(async (helper) => {
239
- helper.authorizedRoutes.delete(routeHandle);
240
- ok(await helper.client.request({ method: "release_route", routeHandle }));
241
- }));
242
- const failed = results.find((result) => result.status === "rejected");
243
- if (failed !== undefined)
244
- throw failed.reason;
250
+ };
251
+ const perform = () => {
252
+ if (state !== "prepared")
253
+ return Promise.resolve({ state: "failed", safeErrorCode: "CLAUDE_NATIVE_PREPARATION_CONSUMED" });
254
+ state = "consumed";
255
+ this.#preparations.delete(tracked);
256
+ return helper.client.request({ method: "perform_dispatch", preparationId: result.preparationId }, this.#deadline(input.deadlineAt)).then((value) => "state" in value ? value : { state: "ambiguous", safeErrorCode: "CLAUDE_NATIVE_HELPER_INVALID_RESPONSE" }, (error) => error instanceof BridgeError && ["CLAUDE_NATIVE_PREPARATION_UNKNOWN", "CLAUDE_NATIVE_PREPARATION_MISMATCH", "CLAUDE_PEER_PREPARATION_CONSUMED"].includes(error.code)
257
+ ? { state: "failed", safeErrorCode: error.code } : { state: "ambiguous", safeErrorCode: "CLAUDE_NATIVE_HELPER_PERFORM_UNCERTAIN" });
258
+ };
259
+ tracked = { helper, cancel };
260
+ this.#preparations.add(tracked);
261
+ return Object.freeze({ frameBytes: result.frameBytes, sha256: result.sha256, perform, cancel });
245
262
  }
246
263
  async updateInboundStatus(receiptHandle, status, diagnosticCode) {
247
- const owner = this.#requireReceipt(receiptHandle);
264
+ const owner = this.#receipt(receiptHandle);
248
265
  try {
249
- ok(await owner.helper.client.request({
250
- method: "update_inbound_status",
251
- receiptHandle: owner.childHandle,
252
- status,
253
- ...(diagnosticCode === undefined ? {} : { diagnosticCode }),
254
- }));
266
+ requireOk(await owner.helper.client.request({ method: "update_inbound_status",
267
+ receiptHandle: owner.childHandle, status, ...(diagnosticCode ? { diagnosticCode } : {}) }));
255
268
  if (status !== "held")
256
- this.#receiptOwners.delete(receiptHandle);
269
+ this.#receipts.delete(receiptHandle);
257
270
  }
258
271
  catch (error) {
259
- if (status !== "held" &&
260
- (!(error instanceof BridgeError) || !error.recoverable)) {
261
- this.#receiptOwners.delete(receiptHandle);
262
- }
272
+ if (status !== "held" && (!(error instanceof BridgeError) || !error.recoverable))
273
+ this.#receipts.delete(receiptHandle);
263
274
  throw error;
264
275
  }
265
276
  }
266
277
  async notifyInboundProgress(receiptHandle, progress) {
267
- const owner = this.#requireReceipt(receiptHandle);
268
- ok(await owner.helper.client.request({
269
- method: "notify_inbound_progress",
270
- receiptHandle: owner.childHandle,
271
- progress,
272
- }));
278
+ const owner = this.#receipt(receiptHandle);
279
+ requireOk(await owner.helper.client.request({ method: "notify_inbound_progress", receiptHandle: owner.childHandle, progress }));
273
280
  }
274
281
  async releaseInboundReceipt(receiptHandle) {
275
- const owner = this.#receiptOwners.get(receiptHandle);
276
- if (owner === undefined)
282
+ const owner = this.#receipts.get(receiptHandle);
283
+ if (!owner)
277
284
  return false;
278
- this.#receiptOwners.delete(receiptHandle);
279
- const result = await owner.helper.client.request({
280
- method: "release_inbound_receipt",
281
- receiptHandle: owner.childHandle,
282
- });
283
- return "released" in result && result.released === true;
284
- }
285
- async quiesceAll() {
286
- const results = await Promise.allSettled([...new Set(this.#helpersByAlias.values())].map(async (helper) => {
287
- ok(await helper.client.request({
288
- method: "quiesce_generation",
289
- generation: helper.activeGeneration,
290
- }));
291
- }));
292
- const failed = results.find((result) => result.status === "rejected");
293
- if (failed !== undefined)
294
- throw failed.reason;
295
- }
296
- async quiesceGeneration(generation) {
297
- const helper = this.#requireGeneration(generation);
298
- ok(await helper.client.request({ method: "quiesce_generation", generation }));
299
- }
300
- async observeBarrier(generation) {
301
- const helper = this.#requireGeneration(generation);
302
- const result = await helper.client.request({ method: "observe_barrier", generation });
303
- if (!("clean" in result) || !("generation" in result)) {
304
- throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
305
- }
306
- return result;
307
- }
308
- async prepareGeneration(input) {
309
- const helper = this.#helpersByGeneration.get(input.currentGeneration);
310
- if (helper === undefined ||
311
- helper.activeGeneration !== input.currentGeneration ||
312
- helper.prepared !== undefined) {
313
- throw fault("CODEX_PEER_SUCCESSION_CAPACITY");
314
- }
315
- ok(await helper.client.request({
316
- method: "prepare_generation",
317
- alias: input.alias,
318
- cwd: input.cwd,
319
- generation: input.generation,
320
- }));
321
- helper.prepared = { alias: input.alias, generation: input.generation };
322
- this.#helpersByGeneration.set(input.generation, helper);
323
- }
324
- async publishPrepared(input) {
325
- const helper = this.#requireGeneration(input.currentGeneration);
326
- if (this.#requireGeneration(input.preparedGeneration) !== helper) {
327
- throw fault("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH");
328
- }
329
- const result = await helper.client.request({
330
- method: "publish_prepared",
331
- ...input,
332
- });
333
- if (!("publication" in result)) {
334
- throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
335
- }
336
- return result.publication;
337
- }
338
- async activatePrepared(generation) {
339
- const helper = this.#requireGeneration(generation);
340
- const prepared = helper.prepared;
341
- if (prepared?.generation !== generation) {
342
- throw fault("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH");
343
- }
344
- const oldAlias = helper.activeAlias;
345
- const oldGeneration = helper.activeGeneration;
346
- ok(await helper.client.request({ method: "activate_prepared", generation }));
347
- this.#helpersByAlias.delete(oldAlias);
348
- helper.retiredGeneration = oldGeneration;
349
- helper.activeAlias = prepared.alias;
350
- helper.activeGeneration = prepared.generation;
351
- delete helper.prepared;
352
- helper.authorizedRoutes.clear();
353
- this.#helpersByAlias.set(helper.activeAlias, helper);
354
- }
355
- async cleanupPrepared(generation) {
356
- const helper = this.#requireGeneration(generation);
357
- ok(await helper.client.request({ method: "cleanup_prepared", generation }));
358
- if (helper.prepared?.generation === generation)
359
- delete helper.prepared;
360
- this.#helpersByGeneration.delete(generation);
361
- }
362
- async resumeGeneration(generation) {
363
- const helper = this.#requireGeneration(generation);
364
- ok(await helper.client.request({ method: "resume_generation", generation }));
365
- }
366
- async rollbackPrepared(input) {
367
- const helper = this.#requireGeneration(input.resumeGeneration);
368
- if (this.#requireGeneration(input.preparedGeneration) !== helper) {
369
- throw fault("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH");
370
- }
371
- ok(await helper.client.request({ method: "rollback_prepared", ...input }));
372
- if (helper.prepared?.generation === input.preparedGeneration) {
373
- delete helper.prepared;
374
- }
375
- this.#helpersByGeneration.delete(input.preparedGeneration);
376
- }
377
- async retireGeneration(input) {
378
- const helper = this.#requireGeneration(input.protectedActiveGeneration);
379
- if (this.#requireGeneration(input.retiredGeneration) !== helper) {
380
- throw fault("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH");
381
- }
382
- ok(await helper.client.request({ method: "retire_generation", ...input }));
383
- if (helper.retiredGeneration === input.retiredGeneration) {
384
- delete helper.retiredGeneration;
385
- }
386
- this.#helpersByGeneration.delete(input.retiredGeneration);
387
- }
388
- async purgeGenerationReplies(generation) {
389
- const helper = this.#requireGeneration(generation);
390
- const result = await helper.client.request({
391
- method: "purge_generation_replies",
392
- generation,
393
- });
394
- if (!("purged" in result) || !Number.isSafeInteger(result.purged)) {
395
- throw fault("CLAUDE_NATIVE_HELPER_INVALID_RESPONSE");
396
- }
397
- return result.purged;
285
+ this.#receipts.delete(receiptHandle);
286
+ const result = await owner.helper.client.request({ method: "release_inbound_receipt", receiptHandle: owner.childHandle });
287
+ return "released" in result && result.released;
398
288
  }
399
289
  async close() {
400
290
  if (this.#closed)
401
291
  return;
402
292
  this.#closed = true;
403
- const helpers = [...new Set(this.#helpersByAlias.values())];
293
+ await Promise.allSettled([...this.#preparations].map((item) => item.cancel()));
294
+ const helpers = [...this.#helpers.values()];
404
295
  for (const helper of helpers)
405
296
  helper.closing = true;
406
- const results = await Promise.allSettled(helpers.map(async (helper) => await helper.client.close()));
297
+ const results = await Promise.allSettled(helpers.map((helper) => helper.client.close()));
407
298
  for (const helper of helpers)
408
- this.#removeHelper(helper);
299
+ this.#remove(helper);
409
300
  const failed = results.find((result) => result.status === "rejected");
410
- if (failed !== undefined)
301
+ if (failed)
411
302
  throw failed.reason;
412
303
  }
413
- #onEvent(helper, event) {
304
+ #event(helper, event) {
414
305
  if (helper.closing || this.#closed)
415
306
  return;
416
- const callbacks = this.#callbacks();
417
- if (event.event === "delivery") {
418
- const pending = this.#pendingDispatches.get(event.value.messageId);
419
- if (pending?.helper !== helper)
420
- return;
421
- if (event.value.state === "transport_uncertain") {
422
- pending.evidence = "transport_uncertain";
423
- }
424
- else if (event.value.state === "transport_written" ||
425
- event.value.state === "held") {
426
- pending.evidence = "transport_written";
427
- }
428
- if (TERMINAL_DELIVERY_STATES.has(event.value.state)) {
429
- this.#pendingDispatches.delete(event.value.messageId);
430
- }
431
- callbacks?.onDelivery({ ...event.value });
432
- return;
433
- }
434
- if (event.event === "claude_reply") {
435
- callbacks?.onClaudeReply({
436
- endpoint: { ...this.#identity, routeHandle: event.value.routeHandle },
437
- text: event.value.text,
438
- });
439
- return;
440
- }
307
+ const callbacks = this.options.callbacks();
441
308
  if (event.event === "protocol_notice") {
442
- callbacks?.onProtocolNotice?.({ code: event.value.code });
309
+ callbacks?.onProtocolNotice?.(event.value);
443
310
  return;
444
311
  }
445
- if (event.value.targetAlias !== helper.activeAlias) {
312
+ if (event.value.targetAlias !== helper.alias) {
446
313
  callbacks?.onProtocolNotice?.({ code: "CLAUDE_NATIVE_HELPER_TARGET_MISMATCH" });
447
314
  return;
448
315
  }
449
316
  let receiptHandle;
450
- if (event.value.receiptHandle !== undefined) {
451
- if (this.#receiptOwners.size >= this.#maxPendingMessages) {
452
- void helper.client
453
- .request({
454
- method: "update_inbound_status",
455
- receiptHandle: event.value.receiptHandle,
456
- status: "expired",
457
- diagnosticCode: "CLAUDE_NATIVE_INGRESS_CAPACITY",
458
- })
459
- .catch(() => undefined);
317
+ if (event.value.receiptHandle) {
318
+ if (this.#receipts.size >= this.options.maxPendingMessages) {
319
+ void helper.client.request({ method: "update_inbound_status",
320
+ receiptHandle: event.value.receiptHandle, status: "expired", diagnosticCode: "CLAUDE_NATIVE_INGRESS_CAPACITY" }).catch(() => undefined);
460
321
  return;
461
322
  }
462
- receiptHandle = `nrc_${randomBytes(18).toString("base64url")}`;
463
- this.#receiptOwners.set(receiptHandle, {
464
- helper,
465
- childHandle: event.value.receiptHandle,
466
- });
323
+ receiptHandle = `nrc_${id()}`;
324
+ this.#receipts.set(receiptHandle, { helper, childHandle: event.value.receiptHandle });
467
325
  }
468
- callbacks?.onClaudeMessage?.({
469
- endpoint: { ...this.#identity, routeHandle: event.value.routeHandle },
470
- sourceAlias: event.value.sourceAlias,
471
- targetAlias: event.value.targetAlias,
472
- text: event.value.text,
473
- ...(receiptHandle === undefined ? {} : { receiptHandle }),
474
- });
475
- }
476
- #onExit(helper) {
477
- const intentional = helper.closing || this.#closed;
478
- if (intentional) {
479
- this.#removeHelper(helper);
480
- return;
481
- }
482
- const callbacks = this.#callbacks();
483
- for (const [messageId, pending] of this.#pendingDispatches) {
484
- if (pending.helper !== helper)
485
- continue;
486
- this.#pendingDispatches.delete(messageId);
487
- callbacks?.onDelivery({
488
- messageId,
489
- state: pending.evidence === "transport_written"
490
- ? "unconfirmed"
491
- : pending.evidence === "transport_uncertain"
492
- ? "ambiguous"
493
- : "failed",
494
- safeErrorCode: "CLAUDE_NATIVE_HELPER_EXITED",
495
- });
496
- }
497
- callbacks?.onProtocolNotice?.({ code: "CLAUDE_NATIVE_HELPER_EXITED" });
498
- this.#removeHelper(helper);
326
+ callbacks?.onClaudeMessage?.({ endpoint: { ...this.options.identity, routeHandle: event.value.routeHandle },
327
+ sourceAlias: event.value.sourceAlias, targetAlias: event.value.targetAlias, text: event.value.text,
328
+ ...(receiptHandle ? { receiptHandle } : {}) });
499
329
  }
500
- #removeHelper(helper) {
501
- for (const [alias, candidate] of this.#helpersByAlias) {
502
- if (candidate === helper)
503
- this.#helpersByAlias.delete(alias);
504
- }
505
- for (const [generation, candidate] of this.#helpersByGeneration) {
506
- if (candidate === helper)
507
- this.#helpersByGeneration.delete(generation);
508
- }
509
- for (const [handle, owner] of this.#receiptOwners) {
330
+ #exit(helper) { if (!helper.closing && !this.#closed)
331
+ this.options.callbacks()?.onProtocolNotice?.({ code: "CLAUDE_NATIVE_HELPER_EXITED" }); this.#remove(helper); }
332
+ #remove(helper) {
333
+ if (this.#helpers.get(helper.alias) === helper)
334
+ this.#helpers.delete(helper.alias);
335
+ for (const [id, owner] of this.#receipts)
510
336
  if (owner.helper === helper)
511
- this.#receiptOwners.delete(handle);
512
- }
513
- for (const [messageId, pending] of this.#pendingDispatches) {
514
- if (pending.helper === helper)
515
- this.#pendingDispatches.delete(messageId);
516
- }
517
- }
518
- #requireGeneration(generation) {
519
- const helper = this.#helpersByGeneration.get(generation);
520
- if (helper === undefined || helper.closing) {
521
- throw fault("CODEX_PEER_SUCCESSION_GENERATION_MISMATCH");
522
- }
523
- return helper;
524
- }
525
- #requireReceipt(receiptHandle) {
526
- const owner = this.#receiptOwners.get(receiptHandle);
527
- if (owner === undefined || owner.helper.closing) {
528
- throw fault("CLAUDE_PEER_RECEIPT_UNKNOWN");
529
- }
530
- return owner;
531
- }
532
- #assertOpen() {
533
- if (this.#closed)
534
- throw fault("CLAUDE_NATIVE_HELPER_SUPERVISOR_CLOSED");
337
+ this.#receipts.delete(id);
338
+ for (const prepared of [...this.#preparations])
339
+ if (prepared.helper === helper)
340
+ this.#preparations.delete(prepared);
535
341
  }
342
+ #receipt(id) { const owner = this.#receipts.get(id); if (!owner || owner.helper.closing)
343
+ throw fault("CLAUDE_PEER_RECEIPT_UNKNOWN"); return owner; }
344
+ #deadline(value) { return Math.max(1, Math.min(60_000, Date.parse(value) - Date.now())); }
536
345
  }
537
346
  //# sourceMappingURL=claude-helper-supervisor.js.map