agent-embassy 1.4.0 → 1.5.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.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +61 -6
- package/README.md +6 -5
- package/README.zh-CN.md +4 -4
- package/SECURITY.md +128 -24
- package/dist/src/gateway/claude-peer.d.ts +8 -5
- package/dist/src/gateway/claude-peer.js +81 -36
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +4 -0
- package/dist/src/gateway/claude-runtime.js +50 -39
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.js +1 -1
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.js +1 -1
- package/dist/src/gateway/cli-copy.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +1 -1
- package/dist/src/gateway/cli.js +3 -7
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +2 -1
- package/dist/src/gateway/codex-app-server.js +1 -1
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +10 -6
- package/dist/src/gateway/codex-local-transport.js +101 -56
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/compatibility.d.ts +11 -0
- package/dist/src/gateway/compatibility.js +104 -22
- package/dist/src/gateway/compatibility.js.map +1 -1
- package/dist/src/gateway/control.js +28 -30
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +36 -17
- package/dist/src/gateway/dashboard-copy.en.js +50 -31
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +36 -17
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +36 -17
- package/dist/src/gateway/dashboard-copy.zh-CN.js +50 -31
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +176 -8
- package/dist/src/gateway/dashboard-model.js +420 -70
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.js +48 -135
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +148 -247
- package/dist/src/gateway/live-dashboard-assets.js +2 -1
- package/dist/src/gateway/live-dashboard-assets.js.map +1 -1
- package/dist/src/gateway/live-dashboard-stream.d.ts +2 -2
- package/dist/src/gateway/live-dashboard-stream.js +2 -2
- package/dist/src/gateway/live-dashboard-stream.js.map +1 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +50 -64
- package/dist/src/gateway/progress-watch-machine.js +42 -133
- package/dist/src/gateway/progress-watch-machine.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.d.ts +2 -0
- package/dist/src/gateway/provenance-envelope.js +13 -3
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +69 -15
- package/dist/src/gateway/providers.js +456 -109
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +8 -5
- package/dist/src/gateway/server.js +94 -13
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +25 -1
- package/dist/src/gateway/service.js +252 -69
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.d.ts +23 -28
- package/dist/src/gateway/store.js +479 -467
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +38 -11
- package/dist/src/gateway/types.js +141 -3
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +8 -8
- package/docs/CONFIGURATION.zh-CN.md +8 -8
- package/docs/DASHBOARD.md +29 -6
- package/docs/DASHBOARD.zh-CN.md +1 -1
- package/docs/GATEWAY-ARCHITECTURE.md +143 -57
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +2 -2
|
@@ -9,33 +9,28 @@ import { BridgeError } from "../errors.js";
|
|
|
9
9
|
import { KeyedMutex } from "../mutex.js";
|
|
10
10
|
import { createCodexRegistrationGeneration, isCodexRegistrationGeneration, } from "./codex-registration-generation.js";
|
|
11
11
|
import { isDashboardLocale } from "./locale.js";
|
|
12
|
-
import { sharesCompatibilityMajor } from "./compatibility.js";
|
|
12
|
+
import { isCompatibilityVersionEvidence, sharesCompatibilityMajor, } from "./compatibility.js";
|
|
13
13
|
/**
|
|
14
14
|
* This adapter intentionally pins the inspected, implementation-specific
|
|
15
15
|
* local peer boundary. The feature is documented in Claude Code, but its
|
|
16
16
|
* registry and NDJSON formats are not a stable public integration contract.
|
|
17
17
|
*/
|
|
18
18
|
export const CLAUDE_PEER_COMPATIBILITY = Object.freeze({
|
|
19
|
-
claudeCodeVersion: "2.1.
|
|
19
|
+
claudeCodeVersion: "2.1.227",
|
|
20
20
|
peerProtocol: 1,
|
|
21
21
|
messageVersion: 1,
|
|
22
22
|
});
|
|
23
|
-
|
|
24
|
-
export const CLAUDE_PEER_COMPATIBLE_SESSION_VERSIONS = Object.freeze([
|
|
25
|
-
"2.1.224",
|
|
26
|
-
"2.1.225",
|
|
27
|
-
"2.1.226",
|
|
28
|
-
]);
|
|
23
|
+
const EMBASSY_ADVERTISEMENT_VERSION = 1;
|
|
29
24
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
30
25
|
const ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
31
26
|
const CLAUDE_PEER_NOTICE_COPY = {
|
|
32
27
|
en: {
|
|
33
|
-
stall: "The local gateway is still waiting to deliver the preceding message. Run `embassy status` or inspect the dashboard for details.",
|
|
34
|
-
diagnostic: "The local gateway could not deliver the preceding message. Run `embassy status` or inspect the dashboard for details.",
|
|
28
|
+
stall: "The local gateway is still waiting to deliver the preceding message. Run `embassy status` or inspect the dashboard for details. Queued mail reaches a busy recipient when its turn ends.",
|
|
29
|
+
diagnostic: "The local gateway could not deliver the preceding message. Run `embassy status` or inspect the dashboard for details. Queued mail reaches a busy recipient when its turn ends.",
|
|
35
30
|
},
|
|
36
31
|
"zh-CN": {
|
|
37
|
-
stall: "本地网关仍在等待投递前一条消息。运行 `embassy status`
|
|
38
|
-
diagnostic: "本地网关无法投递前一条消息。运行 `embassy status`
|
|
32
|
+
stall: "本地网关仍在等待投递前一条消息。运行 `embassy status` 或查看仪表盘了解详情。排队邮件会在忙碌接收方的当前轮次结束后到达。",
|
|
33
|
+
diagnostic: "本地网关无法投递前一条消息。运行 `embassy status` 或查看仪表盘了解详情。排队邮件会在忙碌接收方的当前轮次结束后到达。",
|
|
39
34
|
},
|
|
40
35
|
};
|
|
41
36
|
const REGISTRY_FILE_PATTERN = /^([1-9][0-9]{0,9})\.json$/;
|
|
@@ -205,9 +200,25 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
205
200
|
"name",
|
|
206
201
|
"updatedAt",
|
|
207
202
|
];
|
|
208
|
-
|
|
209
|
-
if (!hasExactKeys(value, required, optional))
|
|
203
|
+
if (!required.every((key) => Object.hasOwn(value, key)))
|
|
210
204
|
return undefined;
|
|
205
|
+
const embassyAdvertisement = value.embassyAdvertisementVersion === EMBASSY_ADVERTISEMENT_VERSION &&
|
|
206
|
+
isCompatibilityVersionEvidence(value.version) &&
|
|
207
|
+
typeof value.name === "string" &&
|
|
208
|
+
value.name.startsWith("codex-") &&
|
|
209
|
+
ALIAS_PATTERN.test(value.name) &&
|
|
210
|
+
value.kind === "interactive" &&
|
|
211
|
+
value.entrypoint === "cli" &&
|
|
212
|
+
value.peerProtocol === CLAUDE_PEER_COMPATIBILITY.peerProtocol &&
|
|
213
|
+
value.status !== undefined &&
|
|
214
|
+
value.statusUpdatedAt !== undefined;
|
|
215
|
+
if (value.embassyAdvertisementVersion !== undefined &&
|
|
216
|
+
!embassyAdvertisement) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const versionCompatible = typeof value.version === "string" &&
|
|
220
|
+
(embassyAdvertisement ||
|
|
221
|
+
sharesCompatibilityMajor(value.version, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion));
|
|
211
222
|
if (value.pid !== expectedPid)
|
|
212
223
|
return undefined;
|
|
213
224
|
if (typeof value.sessionId !== "string" ||
|
|
@@ -229,8 +240,7 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
229
240
|
!isBoundedString(value.procStart, 256) ||
|
|
230
241
|
value.procStart.length === 0 ||
|
|
231
242
|
value.procStart.includes("\0") ||
|
|
232
|
-
|
|
233
|
-
!sharesCompatibilityMajor(value.version, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion) ||
|
|
243
|
+
!versionCompatible ||
|
|
234
244
|
!isBoundedString(value.entrypoint, 64) ||
|
|
235
245
|
!/^[A-Za-z0-9._-]+$/.test(value.entrypoint) ||
|
|
236
246
|
(value.nameSource !== undefined &&
|
|
@@ -272,6 +282,7 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
272
282
|
// while the model turn is active. Treat that live process conservatively
|
|
273
283
|
// as busy.
|
|
274
284
|
status: (value.status ?? "busy"),
|
|
285
|
+
embassyAdvertisement,
|
|
275
286
|
};
|
|
276
287
|
}
|
|
277
288
|
function parseFrame(line, maxFrameBytes) {
|
|
@@ -450,6 +461,7 @@ export class ClaudePeerAdapter {
|
|
|
450
461
|
#listenerOwner = Object.freeze({});
|
|
451
462
|
#registryMutex = new KeyedMutex();
|
|
452
463
|
#registryPublicationHook;
|
|
464
|
+
#postBindHook;
|
|
453
465
|
#targets = new Map();
|
|
454
466
|
#workspacePolicies = new Map();
|
|
455
467
|
#listeners = new Set();
|
|
@@ -457,8 +469,9 @@ export class ClaudePeerAdapter {
|
|
|
457
469
|
if (process.platform === "win32" || process.getuid === undefined) {
|
|
458
470
|
throw new BridgeError("CLAUDE_PEER_PLATFORM_UNSUPPORTED", "Claude peer sockets are supported only on macOS and Linux.");
|
|
459
471
|
}
|
|
460
|
-
if (
|
|
461
|
-
|
|
472
|
+
if (options.attestedClaudeCodeVersion !== "unknown" &&
|
|
473
|
+
!sharesCompatibilityMajor(options.attestedClaudeCodeVersion, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion)) {
|
|
474
|
+
throw new BridgeError("CLAUDE_PEER_VERSION_UNSUPPORTED", "Claude peer transport does not support this Claude Code major.");
|
|
462
475
|
}
|
|
463
476
|
if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
|
|
464
477
|
throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude peer notice locale is unsupported.");
|
|
@@ -472,7 +485,7 @@ export class ClaudePeerAdapter {
|
|
|
472
485
|
this.#deliveryNotices = options.deliveryNotices ?? "merged";
|
|
473
486
|
this.#sessionsDir = assertAbsoluteConfiguredPath(options.sessionsDir, "sessionsDir");
|
|
474
487
|
this.#socketDir = assertAbsoluteConfiguredPath(options.socketDir, "socketDir");
|
|
475
|
-
this.#expectedUid = process.getuid();
|
|
488
|
+
this.#expectedUid = testing.expectedUid ?? process.getuid();
|
|
476
489
|
if (!Number.isSafeInteger(this.#expectedUid) || this.#expectedUid < 0) {
|
|
477
490
|
throw new BridgeError("INVALID_PEER_UID", "expectedUid must be a non-negative integer.");
|
|
478
491
|
}
|
|
@@ -503,13 +516,23 @@ export class ClaudePeerAdapter {
|
|
|
503
516
|
...(testing.tempRoots ?? ["/tmp", "/private/tmp", os.tmpdir()]),
|
|
504
517
|
].map((root) => assertAbsoluteConfiguredPath(root, "tempRoot")));
|
|
505
518
|
this.#registryPublicationHook = testing.registryPublicationHook;
|
|
519
|
+
this.#postBindHook = testing.postBindHook;
|
|
506
520
|
}
|
|
507
|
-
async #
|
|
521
|
+
async #validateRealDirectory(directory) {
|
|
508
522
|
const stat = await lstat(directory);
|
|
509
523
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
510
524
|
throw new BridgeError("UNSAFE_PEER_DIRECTORY", "The Claude peer directory is not an accessible real directory.");
|
|
511
525
|
}
|
|
512
526
|
}
|
|
527
|
+
async #validateSessionsDirectory() {
|
|
528
|
+
const stat = await lstat(this.#sessionsDir);
|
|
529
|
+
if (stat.isSymbolicLink() ||
|
|
530
|
+
!stat.isDirectory() ||
|
|
531
|
+
stat.uid !== this.#expectedUid ||
|
|
532
|
+
exactMode(stat.mode) !== 0o700) {
|
|
533
|
+
throw new BridgeError("UNSAFE_PEER_DIRECTORY", "The Claude sessions directory failed its exact owner and mode policy.");
|
|
534
|
+
}
|
|
535
|
+
}
|
|
513
536
|
async #readRegistryFile(registryPath) {
|
|
514
537
|
const before = await lstat(registryPath);
|
|
515
538
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
@@ -579,6 +602,11 @@ export class ClaudePeerAdapter {
|
|
|
579
602
|
if (record === undefined) {
|
|
580
603
|
throw new BridgeError("REGISTRY_INVALID_SCHEMA", "Registry schema is incompatible.");
|
|
581
604
|
}
|
|
605
|
+
// Embassy advertisements are addressable by Claude, never Claude delivery
|
|
606
|
+
// targets or evidence that a Claude record parsed.
|
|
607
|
+
if (record.embassyAdvertisement || expectedPid === process.pid) {
|
|
608
|
+
throw new BridgeError("SELF_TARGET", "The gateway cannot target itself.");
|
|
609
|
+
}
|
|
582
610
|
onParsed?.();
|
|
583
611
|
const processIdentity = await this.#inspectProcess(expectedPid);
|
|
584
612
|
if (processIdentity === undefined) {
|
|
@@ -587,9 +615,6 @@ export class ClaudePeerAdapter {
|
|
|
587
615
|
if (processIdentity.uid !== this.#expectedUid) {
|
|
588
616
|
throw new BridgeError("PID_OWNER_MISMATCH", "Registry process owner is unsafe.");
|
|
589
617
|
}
|
|
590
|
-
if (expectedPid === process.pid) {
|
|
591
|
-
throw new BridgeError("SELF_TARGET", "The gateway cannot target itself.");
|
|
592
|
-
}
|
|
593
618
|
const socketGeneration = await this.#validateSocket(record.messagingSocketPath, expectedPid);
|
|
594
619
|
return {
|
|
595
620
|
// Claude's native session UUID is the logical route identity. The
|
|
@@ -607,8 +632,8 @@ export class ClaudePeerAdapter {
|
|
|
607
632
|
}
|
|
608
633
|
async discover() {
|
|
609
634
|
await Promise.all([
|
|
610
|
-
this.#
|
|
611
|
-
this.#
|
|
635
|
+
this.#validateSessionsDirectory(),
|
|
636
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
612
637
|
]);
|
|
613
638
|
const previousTargets = [...this.#targets.values()];
|
|
614
639
|
const nextTargets = new Map();
|
|
@@ -681,6 +706,8 @@ export class ClaudePeerAdapter {
|
|
|
681
706
|
const code = error instanceof BridgeError
|
|
682
707
|
? error.code
|
|
683
708
|
: "REGISTRY_RACED";
|
|
709
|
+
if (code === "SELF_TARGET")
|
|
710
|
+
continue;
|
|
684
711
|
if (claudePeerRejectionCodes.includes(code)) {
|
|
685
712
|
reject(code);
|
|
686
713
|
}
|
|
@@ -707,8 +734,8 @@ export class ClaudePeerAdapter {
|
|
|
707
734
|
}
|
|
708
735
|
async #revalidateBinding(binding) {
|
|
709
736
|
await Promise.all([
|
|
710
|
-
this.#
|
|
711
|
-
this.#
|
|
737
|
+
this.#validateSessionsDirectory(),
|
|
738
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
712
739
|
]);
|
|
713
740
|
if (binding.expiresAt < this.#now()) {
|
|
714
741
|
this.#targets.delete(binding.targetId);
|
|
@@ -846,8 +873,8 @@ export class ClaudePeerAdapter {
|
|
|
846
873
|
}
|
|
847
874
|
async #resolveReplyAddress(address) {
|
|
848
875
|
await Promise.all([
|
|
849
|
-
this.#
|
|
850
|
-
this.#
|
|
876
|
+
this.#validateSessionsDirectory(),
|
|
877
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
851
878
|
]);
|
|
852
879
|
if (!address.startsWith("uds:")) {
|
|
853
880
|
throw new BridgeError("UNREGISTERED_REPLY_ADDRESS", "Only local registered UDS reply addresses are accepted.");
|
|
@@ -886,8 +913,8 @@ export class ClaudePeerAdapter {
|
|
|
886
913
|
*/
|
|
887
914
|
async resolveReplyAddress(address) {
|
|
888
915
|
await Promise.all([
|
|
889
|
-
this.#
|
|
890
|
-
this.#
|
|
916
|
+
this.#validateSessionsDirectory(),
|
|
917
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
891
918
|
]);
|
|
892
919
|
const binding = await this.#resolveReplyAddress(address);
|
|
893
920
|
return {
|
|
@@ -900,8 +927,8 @@ export class ClaudePeerAdapter {
|
|
|
900
927
|
}
|
|
901
928
|
async #listen(options, preparedGeneration) {
|
|
902
929
|
await Promise.all([
|
|
903
|
-
this.#
|
|
904
|
-
this.#
|
|
930
|
+
this.#validateSessionsDirectory(),
|
|
931
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
905
932
|
]);
|
|
906
933
|
const generation = preparedGeneration ?? this.#createGeneration();
|
|
907
934
|
if (!isCodexRegistrationGeneration(generation)) {
|
|
@@ -949,6 +976,7 @@ export class ClaudePeerAdapter {
|
|
|
949
976
|
...(this.#registryPublicationHook === undefined
|
|
950
977
|
? {}
|
|
951
978
|
: { registryPublicationHook: this.#registryPublicationHook }),
|
|
979
|
+
postBindHook: this.#postBindHook,
|
|
952
980
|
onClosed: () => this.#listeners.delete(listener),
|
|
953
981
|
});
|
|
954
982
|
this.#listeners.add(listener);
|
|
@@ -1265,6 +1293,7 @@ export class ClaudePeerListener {
|
|
|
1265
1293
|
!sameSocketGeneration({ dev: attested.dev, ino: attested.ino }, createdGeneration)) {
|
|
1266
1294
|
throw new BridgeError("CLAUDE_PEER_CALLBACK_UNSAFE", "The gateway callback socket did not satisfy its ownership policy.");
|
|
1267
1295
|
}
|
|
1296
|
+
await options.postBindHook?.(socketPath);
|
|
1268
1297
|
const sessions = await lstat(options.sessionsDir);
|
|
1269
1298
|
if (sessions.isSymbolicLink() ||
|
|
1270
1299
|
!sessions.isDirectory() ||
|
|
@@ -1289,11 +1318,25 @@ export class ClaudePeerListener {
|
|
|
1289
1318
|
// net.Server.close(), which deletes by pathname.
|
|
1290
1319
|
}
|
|
1291
1320
|
}
|
|
1321
|
+
let closeConfirmed = false;
|
|
1292
1322
|
if (stillOwned) {
|
|
1293
|
-
|
|
1323
|
+
try {
|
|
1324
|
+
await new Promise((resolve, reject) => server.close((closeError) => closeError === undefined ? resolve() : reject(closeError)));
|
|
1325
|
+
try {
|
|
1326
|
+
await lstat(socketPath);
|
|
1327
|
+
}
|
|
1328
|
+
catch (closeError) {
|
|
1329
|
+
closeConfirmed =
|
|
1330
|
+
closeError.code === "ENOENT";
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
catch {
|
|
1334
|
+
// An unconfirmed close cannot safely downgrade a callback failure.
|
|
1335
|
+
}
|
|
1294
1336
|
}
|
|
1295
|
-
|
|
1337
|
+
if (!closeConfirmed) {
|
|
1296
1338
|
server.unref();
|
|
1339
|
+
throw new BridgeError("CLAUDE_PEER_CALLBACK_UNSAFE", "The newly bound callback socket could not be safely closed.");
|
|
1297
1340
|
}
|
|
1298
1341
|
throw error;
|
|
1299
1342
|
}
|
|
@@ -1314,7 +1357,8 @@ export class ClaudePeerListener {
|
|
|
1314
1357
|
return `${JSON.stringify(record)}\n`;
|
|
1315
1358
|
}
|
|
1316
1359
|
#recordBelongsToThisListener(record) {
|
|
1317
|
-
return (record.
|
|
1360
|
+
return (record.embassyAdvertisementVersion === EMBASSY_ADVERTISEMENT_VERSION &&
|
|
1361
|
+
record.pid === process.pid &&
|
|
1318
1362
|
UUID_PATTERN.test(record.sessionId) &&
|
|
1319
1363
|
record.messagingSocketPath === this.#socketPath &&
|
|
1320
1364
|
record.version === this.#attestedClaudeCodeVersion &&
|
|
@@ -1485,6 +1529,7 @@ export class ClaudePeerListener {
|
|
|
1485
1529
|
}
|
|
1486
1530
|
const now = Date.now();
|
|
1487
1531
|
return {
|
|
1532
|
+
embassyAdvertisementVersion: EMBASSY_ADVERTISEMENT_VERSION,
|
|
1488
1533
|
pid: process.pid,
|
|
1489
1534
|
sessionId,
|
|
1490
1535
|
cwd,
|