agent-embassy 1.4.1 → 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 +22 -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 +7 -4
- package/dist/src/gateway/claude-peer.js +80 -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,7 +9,7 @@ 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
|
|
@@ -20,23 +20,17 @@ export const CLAUDE_PEER_COMPATIBILITY = Object.freeze({
|
|
|
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
|
-
"2.1.227",
|
|
29
|
-
]);
|
|
23
|
+
const EMBASSY_ADVERTISEMENT_VERSION = 1;
|
|
30
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;
|
|
31
25
|
const ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
32
26
|
const CLAUDE_PEER_NOTICE_COPY = {
|
|
33
27
|
en: {
|
|
34
|
-
stall: "The local gateway is still waiting to deliver the preceding message. Run `embassy status` or inspect the dashboard for details.",
|
|
35
|
-
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.",
|
|
36
30
|
},
|
|
37
31
|
"zh-CN": {
|
|
38
|
-
stall: "本地网关仍在等待投递前一条消息。运行 `embassy status`
|
|
39
|
-
diagnostic: "本地网关无法投递前一条消息。运行 `embassy status`
|
|
32
|
+
stall: "本地网关仍在等待投递前一条消息。运行 `embassy status` 或查看仪表盘了解详情。排队邮件会在忙碌接收方的当前轮次结束后到达。",
|
|
33
|
+
diagnostic: "本地网关无法投递前一条消息。运行 `embassy status` 或查看仪表盘了解详情。排队邮件会在忙碌接收方的当前轮次结束后到达。",
|
|
40
34
|
},
|
|
41
35
|
};
|
|
42
36
|
const REGISTRY_FILE_PATTERN = /^([1-9][0-9]{0,9})\.json$/;
|
|
@@ -206,9 +200,25 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
206
200
|
"name",
|
|
207
201
|
"updatedAt",
|
|
208
202
|
];
|
|
209
|
-
|
|
210
|
-
if (!hasExactKeys(value, required, optional))
|
|
203
|
+
if (!required.every((key) => Object.hasOwn(value, key)))
|
|
211
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));
|
|
212
222
|
if (value.pid !== expectedPid)
|
|
213
223
|
return undefined;
|
|
214
224
|
if (typeof value.sessionId !== "string" ||
|
|
@@ -230,8 +240,7 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
230
240
|
!isBoundedString(value.procStart, 256) ||
|
|
231
241
|
value.procStart.length === 0 ||
|
|
232
242
|
value.procStart.includes("\0") ||
|
|
233
|
-
|
|
234
|
-
!sharesCompatibilityMajor(value.version, CLAUDE_PEER_COMPATIBILITY.claudeCodeVersion) ||
|
|
243
|
+
!versionCompatible ||
|
|
235
244
|
!isBoundedString(value.entrypoint, 64) ||
|
|
236
245
|
!/^[A-Za-z0-9._-]+$/.test(value.entrypoint) ||
|
|
237
246
|
(value.nameSource !== undefined &&
|
|
@@ -273,6 +282,7 @@ function parseRegistryRecord(value, expectedPid) {
|
|
|
273
282
|
// while the model turn is active. Treat that live process conservatively
|
|
274
283
|
// as busy.
|
|
275
284
|
status: (value.status ?? "busy"),
|
|
285
|
+
embassyAdvertisement,
|
|
276
286
|
};
|
|
277
287
|
}
|
|
278
288
|
function parseFrame(line, maxFrameBytes) {
|
|
@@ -451,6 +461,7 @@ export class ClaudePeerAdapter {
|
|
|
451
461
|
#listenerOwner = Object.freeze({});
|
|
452
462
|
#registryMutex = new KeyedMutex();
|
|
453
463
|
#registryPublicationHook;
|
|
464
|
+
#postBindHook;
|
|
454
465
|
#targets = new Map();
|
|
455
466
|
#workspacePolicies = new Map();
|
|
456
467
|
#listeners = new Set();
|
|
@@ -458,8 +469,9 @@ export class ClaudePeerAdapter {
|
|
|
458
469
|
if (process.platform === "win32" || process.getuid === undefined) {
|
|
459
470
|
throw new BridgeError("CLAUDE_PEER_PLATFORM_UNSUPPORTED", "Claude peer sockets are supported only on macOS and Linux.");
|
|
460
471
|
}
|
|
461
|
-
if (
|
|
462
|
-
|
|
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.");
|
|
463
475
|
}
|
|
464
476
|
if (options.locale !== undefined && !isDashboardLocale(options.locale)) {
|
|
465
477
|
throw new BridgeError("DASHBOARD_LOCALE_UNSUPPORTED", "The Claude peer notice locale is unsupported.");
|
|
@@ -473,7 +485,7 @@ export class ClaudePeerAdapter {
|
|
|
473
485
|
this.#deliveryNotices = options.deliveryNotices ?? "merged";
|
|
474
486
|
this.#sessionsDir = assertAbsoluteConfiguredPath(options.sessionsDir, "sessionsDir");
|
|
475
487
|
this.#socketDir = assertAbsoluteConfiguredPath(options.socketDir, "socketDir");
|
|
476
|
-
this.#expectedUid = process.getuid();
|
|
488
|
+
this.#expectedUid = testing.expectedUid ?? process.getuid();
|
|
477
489
|
if (!Number.isSafeInteger(this.#expectedUid) || this.#expectedUid < 0) {
|
|
478
490
|
throw new BridgeError("INVALID_PEER_UID", "expectedUid must be a non-negative integer.");
|
|
479
491
|
}
|
|
@@ -504,13 +516,23 @@ export class ClaudePeerAdapter {
|
|
|
504
516
|
...(testing.tempRoots ?? ["/tmp", "/private/tmp", os.tmpdir()]),
|
|
505
517
|
].map((root) => assertAbsoluteConfiguredPath(root, "tempRoot")));
|
|
506
518
|
this.#registryPublicationHook = testing.registryPublicationHook;
|
|
519
|
+
this.#postBindHook = testing.postBindHook;
|
|
507
520
|
}
|
|
508
|
-
async #
|
|
521
|
+
async #validateRealDirectory(directory) {
|
|
509
522
|
const stat = await lstat(directory);
|
|
510
523
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
511
524
|
throw new BridgeError("UNSAFE_PEER_DIRECTORY", "The Claude peer directory is not an accessible real directory.");
|
|
512
525
|
}
|
|
513
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
|
+
}
|
|
514
536
|
async #readRegistryFile(registryPath) {
|
|
515
537
|
const before = await lstat(registryPath);
|
|
516
538
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
@@ -580,6 +602,11 @@ export class ClaudePeerAdapter {
|
|
|
580
602
|
if (record === undefined) {
|
|
581
603
|
throw new BridgeError("REGISTRY_INVALID_SCHEMA", "Registry schema is incompatible.");
|
|
582
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
|
+
}
|
|
583
610
|
onParsed?.();
|
|
584
611
|
const processIdentity = await this.#inspectProcess(expectedPid);
|
|
585
612
|
if (processIdentity === undefined) {
|
|
@@ -588,9 +615,6 @@ export class ClaudePeerAdapter {
|
|
|
588
615
|
if (processIdentity.uid !== this.#expectedUid) {
|
|
589
616
|
throw new BridgeError("PID_OWNER_MISMATCH", "Registry process owner is unsafe.");
|
|
590
617
|
}
|
|
591
|
-
if (expectedPid === process.pid) {
|
|
592
|
-
throw new BridgeError("SELF_TARGET", "The gateway cannot target itself.");
|
|
593
|
-
}
|
|
594
618
|
const socketGeneration = await this.#validateSocket(record.messagingSocketPath, expectedPid);
|
|
595
619
|
return {
|
|
596
620
|
// Claude's native session UUID is the logical route identity. The
|
|
@@ -608,8 +632,8 @@ export class ClaudePeerAdapter {
|
|
|
608
632
|
}
|
|
609
633
|
async discover() {
|
|
610
634
|
await Promise.all([
|
|
611
|
-
this.#
|
|
612
|
-
this.#
|
|
635
|
+
this.#validateSessionsDirectory(),
|
|
636
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
613
637
|
]);
|
|
614
638
|
const previousTargets = [...this.#targets.values()];
|
|
615
639
|
const nextTargets = new Map();
|
|
@@ -682,6 +706,8 @@ export class ClaudePeerAdapter {
|
|
|
682
706
|
const code = error instanceof BridgeError
|
|
683
707
|
? error.code
|
|
684
708
|
: "REGISTRY_RACED";
|
|
709
|
+
if (code === "SELF_TARGET")
|
|
710
|
+
continue;
|
|
685
711
|
if (claudePeerRejectionCodes.includes(code)) {
|
|
686
712
|
reject(code);
|
|
687
713
|
}
|
|
@@ -708,8 +734,8 @@ export class ClaudePeerAdapter {
|
|
|
708
734
|
}
|
|
709
735
|
async #revalidateBinding(binding) {
|
|
710
736
|
await Promise.all([
|
|
711
|
-
this.#
|
|
712
|
-
this.#
|
|
737
|
+
this.#validateSessionsDirectory(),
|
|
738
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
713
739
|
]);
|
|
714
740
|
if (binding.expiresAt < this.#now()) {
|
|
715
741
|
this.#targets.delete(binding.targetId);
|
|
@@ -847,8 +873,8 @@ export class ClaudePeerAdapter {
|
|
|
847
873
|
}
|
|
848
874
|
async #resolveReplyAddress(address) {
|
|
849
875
|
await Promise.all([
|
|
850
|
-
this.#
|
|
851
|
-
this.#
|
|
876
|
+
this.#validateSessionsDirectory(),
|
|
877
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
852
878
|
]);
|
|
853
879
|
if (!address.startsWith("uds:")) {
|
|
854
880
|
throw new BridgeError("UNREGISTERED_REPLY_ADDRESS", "Only local registered UDS reply addresses are accepted.");
|
|
@@ -887,8 +913,8 @@ export class ClaudePeerAdapter {
|
|
|
887
913
|
*/
|
|
888
914
|
async resolveReplyAddress(address) {
|
|
889
915
|
await Promise.all([
|
|
890
|
-
this.#
|
|
891
|
-
this.#
|
|
916
|
+
this.#validateSessionsDirectory(),
|
|
917
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
892
918
|
]);
|
|
893
919
|
const binding = await this.#resolveReplyAddress(address);
|
|
894
920
|
return {
|
|
@@ -901,8 +927,8 @@ export class ClaudePeerAdapter {
|
|
|
901
927
|
}
|
|
902
928
|
async #listen(options, preparedGeneration) {
|
|
903
929
|
await Promise.all([
|
|
904
|
-
this.#
|
|
905
|
-
this.#
|
|
930
|
+
this.#validateSessionsDirectory(),
|
|
931
|
+
this.#validateRealDirectory(this.#socketDir),
|
|
906
932
|
]);
|
|
907
933
|
const generation = preparedGeneration ?? this.#createGeneration();
|
|
908
934
|
if (!isCodexRegistrationGeneration(generation)) {
|
|
@@ -950,6 +976,7 @@ export class ClaudePeerAdapter {
|
|
|
950
976
|
...(this.#registryPublicationHook === undefined
|
|
951
977
|
? {}
|
|
952
978
|
: { registryPublicationHook: this.#registryPublicationHook }),
|
|
979
|
+
postBindHook: this.#postBindHook,
|
|
953
980
|
onClosed: () => this.#listeners.delete(listener),
|
|
954
981
|
});
|
|
955
982
|
this.#listeners.add(listener);
|
|
@@ -1266,6 +1293,7 @@ export class ClaudePeerListener {
|
|
|
1266
1293
|
!sameSocketGeneration({ dev: attested.dev, ino: attested.ino }, createdGeneration)) {
|
|
1267
1294
|
throw new BridgeError("CLAUDE_PEER_CALLBACK_UNSAFE", "The gateway callback socket did not satisfy its ownership policy.");
|
|
1268
1295
|
}
|
|
1296
|
+
await options.postBindHook?.(socketPath);
|
|
1269
1297
|
const sessions = await lstat(options.sessionsDir);
|
|
1270
1298
|
if (sessions.isSymbolicLink() ||
|
|
1271
1299
|
!sessions.isDirectory() ||
|
|
@@ -1290,11 +1318,25 @@ export class ClaudePeerListener {
|
|
|
1290
1318
|
// net.Server.close(), which deletes by pathname.
|
|
1291
1319
|
}
|
|
1292
1320
|
}
|
|
1321
|
+
let closeConfirmed = false;
|
|
1293
1322
|
if (stillOwned) {
|
|
1294
|
-
|
|
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
|
+
}
|
|
1295
1336
|
}
|
|
1296
|
-
|
|
1337
|
+
if (!closeConfirmed) {
|
|
1297
1338
|
server.unref();
|
|
1339
|
+
throw new BridgeError("CLAUDE_PEER_CALLBACK_UNSAFE", "The newly bound callback socket could not be safely closed.");
|
|
1298
1340
|
}
|
|
1299
1341
|
throw error;
|
|
1300
1342
|
}
|
|
@@ -1315,7 +1357,8 @@ export class ClaudePeerListener {
|
|
|
1315
1357
|
return `${JSON.stringify(record)}\n`;
|
|
1316
1358
|
}
|
|
1317
1359
|
#recordBelongsToThisListener(record) {
|
|
1318
|
-
return (record.
|
|
1360
|
+
return (record.embassyAdvertisementVersion === EMBASSY_ADVERTISEMENT_VERSION &&
|
|
1361
|
+
record.pid === process.pid &&
|
|
1319
1362
|
UUID_PATTERN.test(record.sessionId) &&
|
|
1320
1363
|
record.messagingSocketPath === this.#socketPath &&
|
|
1321
1364
|
record.version === this.#attestedClaudeCodeVersion &&
|
|
@@ -1486,6 +1529,7 @@ export class ClaudePeerListener {
|
|
|
1486
1529
|
}
|
|
1487
1530
|
const now = Date.now();
|
|
1488
1531
|
return {
|
|
1532
|
+
embassyAdvertisementVersion: EMBASSY_ADVERTISEMENT_VERSION,
|
|
1489
1533
|
pid: process.pid,
|
|
1490
1534
|
sessionId,
|
|
1491
1535
|
cwd,
|