pi-radar 0.6.0 → 0.9.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/README.md +15 -1
- package/dist/extension/baseline.js +395 -125
- package/dist/extension/radar-children.js +240 -0
- package/dist/extension/radar-client.js +42 -8
- package/dist/extension/radar-execution.js +11 -10
- package/dist/extension/radar-launch.js +23 -24
- package/dist/extension/radar-publication.js +10 -9
- package/dist/extension/radar-runtime.js +1 -1
- package/dist/index.js +1772 -1615
- package/extension/baseline.ts +2 -0
- package/extension/generated-contracts.ts +12 -3
- package/extension/radar-children.ts +176 -0
- package/extension/radar-client.ts +60 -6
- package/extension/radar-execution.ts +5 -5
- package/extension/radar-launch.ts +42 -63
- package/extension/radar-publication.ts +12 -13
- package/extension/radar-runtime.ts +4 -1
- package/index.ts +1 -0
- package/package.json +8 -3
- package/schema/public/v1/observation-get-response.deserialize.schema.json +9 -1
- package/schema/public/v1/observation-get-response.serialize.schema.json +9 -1
- package/schema/public/v1/observation-snapshot.deserialize.schema.json +9 -1
- package/schema/public/v1/observation-snapshot.serialize.schema.json +9 -1
- package/schema/public/v1/presence-answer.deserialize.schema.json +1 -0
- package/schema/public/v1/presence-answer.serialize.schema.json +1 -0
- package/schema/public/v1/presence-report.deserialize.schema.json +0 -7
- package/schema/public/v1/presence-report.serialize.schema.json +0 -8
- package/schema/public/v1/spawn-list-page.deserialize.schema.json +7 -0
- package/schema/public/v1/spawn-list-page.serialize.schema.json +7 -0
package/dist/index.js
CHANGED
|
@@ -4,6 +4,69 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// extension/radar-children.ts
|
|
8
|
+
var radar_children_exports = {};
|
|
9
|
+
__export(radar_children_exports, {
|
|
10
|
+
createRadarChildren: () => createRadarChildren
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// extension/radar-inspection.ts
|
|
14
|
+
var radar_inspection_exports = {};
|
|
15
|
+
__export(radar_inspection_exports, {
|
|
16
|
+
inspectRadarChild: () => inspectRadarChild,
|
|
17
|
+
provedRead: () => provedRead
|
|
18
|
+
});
|
|
19
|
+
var INSPECTION_LINES = 80;
|
|
20
|
+
var INSPECTION_OUTPUT_BYTES = 16 * 1024;
|
|
21
|
+
async function provedRead(client, target) {
|
|
22
|
+
const read = await client.agentGet(target.agentId);
|
|
23
|
+
return read.ok === true && read.value.verification === "verified" && read.value.location?.pane === target.paneId && (target.session === void 0 || read.value.contextSession === target.session) ? read.value : void 0;
|
|
24
|
+
}
|
|
25
|
+
async function inspectRadarChild(client, target) {
|
|
26
|
+
const before = await provedRead(client, target);
|
|
27
|
+
if (before === void 0)
|
|
28
|
+
throw new Error("Inspection target identity did not match");
|
|
29
|
+
const output = await client.output({
|
|
30
|
+
pane_id: target.paneId,
|
|
31
|
+
source: "recent_unwrapped",
|
|
32
|
+
lines: INSPECTION_LINES
|
|
33
|
+
});
|
|
34
|
+
if (output.ok === false)
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Inspection output unavailable (${output.code}: ${output.message})`
|
|
37
|
+
);
|
|
38
|
+
const foreground = await client.processInfo({ pane_id: target.paneId });
|
|
39
|
+
const after = await provedRead(client, target);
|
|
40
|
+
if (after === void 0)
|
|
41
|
+
throw new Error("Inspection target changed during capture");
|
|
42
|
+
const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
|
|
43
|
+
const bounded2 = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
capturedAt: Date.now(),
|
|
46
|
+
recentOutputTruncated: output.value.truncated || bounded2.truncated,
|
|
47
|
+
...bounded2.text ? { recentOutput: bounded2.text } : {},
|
|
48
|
+
...foreground.ok === true ? { foreground: foreground.value } : {},
|
|
49
|
+
...after.activity !== void 0 ? { activity: after.activity } : {}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function utf8Tail(value, maxBytes) {
|
|
53
|
+
const bytes = Buffer.from(value);
|
|
54
|
+
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
55
|
+
let start = bytes.length - maxBytes;
|
|
56
|
+
while (start < bytes.length && (bytes[start] & 192) === 128) start++;
|
|
57
|
+
return { text: bytes.subarray(start).toString(), truncated: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// extension/radar-launch.ts
|
|
61
|
+
var radar_launch_exports = {};
|
|
62
|
+
__export(radar_launch_exports, {
|
|
63
|
+
launchRadarWorker: () => launchRadarWorker,
|
|
64
|
+
observeRadarChild: () => observeRadarChild,
|
|
65
|
+
readProcessGone: () => readProcessGone,
|
|
66
|
+
relaunchRefusalForChild: () => relaunchRefusalForChild
|
|
67
|
+
});
|
|
68
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
69
|
+
|
|
7
70
|
// extension/radar-client.ts
|
|
8
71
|
var radar_client_exports = {};
|
|
9
72
|
__export(radar_client_exports, {
|
|
@@ -11,11 +74,13 @@ __export(radar_client_exports, {
|
|
|
11
74
|
RADAR_COMMUNICATION_CONTRACT: () => RADAR_COMMUNICATION_CONTRACT,
|
|
12
75
|
RADAR_EVENTS_CONTRACT: () => RADAR_EVENTS_CONTRACT,
|
|
13
76
|
RADAR_EVENT_RETRY_MS: () => RADAR_EVENT_RETRY_MS,
|
|
77
|
+
RADAR_LIFECYCLE_RESTART_CONTRACT: () => RADAR_LIFECYCLE_RESTART_CONTRACT,
|
|
14
78
|
RADAR_MAX_AWAITED: () => RADAR_MAX_AWAITED,
|
|
15
79
|
RADAR_MAX_BACKGROUND_TASKS: () => RADAR_MAX_BACKGROUND_TASKS,
|
|
16
80
|
RADAR_MAX_LINE_BYTES: () => RADAR_MAX_LINE_BYTES,
|
|
17
81
|
RADAR_MINIMUM_REVISION: () => RADAR_MINIMUM_REVISION,
|
|
18
82
|
RADAR_PROTOCOL_VERSION: () => RADAR_PROTOCOL_VERSION,
|
|
83
|
+
RADAR_REGISTRATION_ENV: () => RADAR_REGISTRATION_ENV,
|
|
19
84
|
RADAR_REGISTRY_CONTRACT: () => RADAR_REGISTRY_CONTRACT,
|
|
20
85
|
RADAR_REGISTRY_FACTS_CONTRACT: () => RADAR_REGISTRY_FACTS_CONTRACT,
|
|
21
86
|
RADAR_REQUESTER: () => RADAR_REQUESTER,
|
|
@@ -23,7 +88,6 @@ __export(radar_client_exports, {
|
|
|
23
88
|
RADAR_SPAWN_CONTRACT: () => RADAR_SPAWN_CONTRACT,
|
|
24
89
|
RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT: () => RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT,
|
|
25
90
|
RADAR_SPAWN_LIST_CONTRACT: () => RADAR_SPAWN_LIST_CONTRACT,
|
|
26
|
-
RADAR_SPAWN_TOKEN_ENV: () => RADAR_SPAWN_TOKEN_ENV,
|
|
27
91
|
boundedRadarAssignment: () => boundedRadarAssignment,
|
|
28
92
|
boundedRadarFacts: () => boundedRadarFacts,
|
|
29
93
|
boundedRadarSnapshot: () => boundedRadarSnapshot,
|
|
@@ -40,12 +104,13 @@ import { lstatSync } from "node:fs";
|
|
|
40
104
|
import { createConnection } from "node:net";
|
|
41
105
|
import { dirname, join } from "node:path";
|
|
42
106
|
var RADAR_PROTOCOL_VERSION = 2;
|
|
43
|
-
var RADAR_MINIMUM_REVISION =
|
|
107
|
+
var RADAR_MINIMUM_REVISION = 7;
|
|
44
108
|
var RADAR_REGISTRY_CONTRACT = "registry.v1";
|
|
45
109
|
var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
|
|
46
110
|
var RADAR_SPAWN_CONTRACT = "spawn.v1";
|
|
47
111
|
var RADAR_SPAWN_LIST_CONTRACT = "spawn.list.v1";
|
|
48
112
|
var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
|
|
113
|
+
var RADAR_LIFECYCLE_RESTART_CONTRACT = "lifecycle.restart.v1";
|
|
49
114
|
var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
|
|
50
115
|
var RADAR_EVENTS_CONTRACT = "events.v1";
|
|
51
116
|
var RADAR_COMMUNICATION_CONTRACT = "communication.handoff.v1";
|
|
@@ -151,7 +216,7 @@ function decodePresenceAnswer(value) {
|
|
|
151
216
|
const claimId = recordAt(value, "claim_id");
|
|
152
217
|
const standing = recordAt(value, "standing");
|
|
153
218
|
const subjectId = recordAt(value, "subject_id");
|
|
154
|
-
if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
|
|
219
|
+
if (!isUuid(registrationId) || !isUuid(claimId) || !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
|
|
155
220
|
return protocol("presence reply carries no valid claim standing");
|
|
156
221
|
return { ok: true, value };
|
|
157
222
|
}
|
|
@@ -300,7 +365,7 @@ function unresolvedOperation(requestId, detail) {
|
|
|
300
365
|
message: `focus ${requestId} is unresolved (${detail})`
|
|
301
366
|
};
|
|
302
367
|
}
|
|
303
|
-
var
|
|
368
|
+
var RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
|
|
304
369
|
var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
|
|
305
370
|
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
306
371
|
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
@@ -351,6 +416,7 @@ function spawnEdge(value) {
|
|
|
351
416
|
const source = stringAt(bound, "source");
|
|
352
417
|
const incarnation = stringAt(bound, "incarnation");
|
|
353
418
|
const agentId = stringAt(bound, "registration_id");
|
|
419
|
+
const subjectId = stringAt(bound, "subject_id");
|
|
354
420
|
const exit = exitAt(recordAt(value, "exit_observation"));
|
|
355
421
|
return {
|
|
356
422
|
ok: true,
|
|
@@ -364,7 +430,8 @@ function spawnEdge(value) {
|
|
|
364
430
|
bound: {
|
|
365
431
|
source,
|
|
366
432
|
incarnation,
|
|
367
|
-
...isUuid(agentId) ? { registration_id: agentId } : {}
|
|
433
|
+
...isUuid(agentId) ? { registration_id: agentId } : {},
|
|
434
|
+
...isUuid(subjectId) ? { subject_id: subjectId } : {}
|
|
368
435
|
}
|
|
369
436
|
} : {},
|
|
370
437
|
...exit !== void 0 ? { exit_observation: exit } : {}
|
|
@@ -436,6 +503,8 @@ function spawnTopology(value) {
|
|
|
436
503
|
function spawnRequestShape(request) {
|
|
437
504
|
if (!isUuid(request.request_id))
|
|
438
505
|
return "spawn requires a canonical request UUID";
|
|
506
|
+
if (request.continue_subject_id !== void 0 && !isUuid(request.continue_subject_id))
|
|
507
|
+
return "continue_subject_id must be a canonical UUID";
|
|
439
508
|
if (!isUuid(request.parent))
|
|
440
509
|
return "spawn requires the parent's canonical agent UUID";
|
|
441
510
|
if (!isUuid(request.parent_session_id))
|
|
@@ -449,8 +518,8 @@ function spawnRequestShape(request) {
|
|
|
449
518
|
const env = Object.entries(request.env ?? {});
|
|
450
519
|
if (env.length > 256) return "spawn env names more than 256 entries";
|
|
451
520
|
for (const [name, value] of env) {
|
|
452
|
-
if (name ===
|
|
453
|
-
return `spawn env cannot set the reserved ${
|
|
521
|
+
if (name === RADAR_REGISTRATION_ENV)
|
|
522
|
+
return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
|
|
454
523
|
if (RADAR_MANAGED_IDENTITY_ENV.has(name))
|
|
455
524
|
return `spawn env cannot set Radar-issued identity variable ${name}`;
|
|
456
525
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
|
|
@@ -902,7 +971,8 @@ function createRadarClient(options = {}) {
|
|
|
902
971
|
argv: request.argv,
|
|
903
972
|
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
904
973
|
...carriesContext && request.env !== void 0 ? { env: request.env } : {},
|
|
905
|
-
...request.managed !== void 0 ? { managed: request.managed } : {}
|
|
974
|
+
...request.managed !== void 0 ? { managed: request.managed } : {},
|
|
975
|
+
...request.continue_subject_id !== void 0 ? { continue_subject_id: request.continue_subject_id } : {}
|
|
906
976
|
},
|
|
907
977
|
requester
|
|
908
978
|
},
|
|
@@ -930,6 +1000,33 @@ function createRadarClient(options = {}) {
|
|
|
930
1000
|
}
|
|
931
1001
|
);
|
|
932
1002
|
},
|
|
1003
|
+
async restart(request) {
|
|
1004
|
+
if (!isUuid(request.request_id) || !isUuid(request.parent) || !isUuid(request.subject_id))
|
|
1005
|
+
return {
|
|
1006
|
+
ok: false,
|
|
1007
|
+
code: "bad_params",
|
|
1008
|
+
message: "restart requires canonical request, parent and subject UUIDs"
|
|
1009
|
+
};
|
|
1010
|
+
return withContract(RADAR_LIFECYCLE_RESTART_CONTRACT, async () => {
|
|
1011
|
+
const answer = await call(
|
|
1012
|
+
"lifecycle.restart",
|
|
1013
|
+
{
|
|
1014
|
+
request: {
|
|
1015
|
+
subject_id: request.subject_id,
|
|
1016
|
+
parent: request.parent,
|
|
1017
|
+
executable: request.executable,
|
|
1018
|
+
argv: request.argv,
|
|
1019
|
+
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
1020
|
+
...request.env !== void 0 ? { env: request.env } : {}
|
|
1021
|
+
},
|
|
1022
|
+
requester
|
|
1023
|
+
},
|
|
1024
|
+
request.request_id
|
|
1025
|
+
);
|
|
1026
|
+
if (answer.ok === false) return answer;
|
|
1027
|
+
return focusOutcome(recordAt(answer.value, "request"), request.request_id);
|
|
1028
|
+
});
|
|
1029
|
+
},
|
|
933
1030
|
async spawnList(request) {
|
|
934
1031
|
const registrationId = processRegistrationId;
|
|
935
1032
|
if (registrationId === void 0)
|
|
@@ -1297,1733 +1394,1792 @@ function boundedRadarSnapshot(snapshot) {
|
|
|
1297
1394
|
};
|
|
1298
1395
|
}
|
|
1299
1396
|
|
|
1300
|
-
// extension/radar-
|
|
1301
|
-
var
|
|
1302
|
-
__export(
|
|
1303
|
-
|
|
1397
|
+
// extension/radar-location.ts
|
|
1398
|
+
var radar_location_exports = {};
|
|
1399
|
+
__export(radar_location_exports, {
|
|
1400
|
+
radarLocalPane: () => radarLocalPane,
|
|
1401
|
+
radarPaneHandle: () => radarPaneHandle,
|
|
1402
|
+
resolveRadarProcessLocation: () => resolveRadarProcessLocation,
|
|
1403
|
+
tmuxServerName: () => tmuxServerName
|
|
1304
1404
|
});
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1405
|
+
import { basename } from "node:path";
|
|
1406
|
+
function radarLocalPane(env) {
|
|
1407
|
+
const pane = env.TMUX_PANE?.trim();
|
|
1408
|
+
return pane && /^%\d+$/.test(pane) ? pane : void 0;
|
|
1409
|
+
}
|
|
1410
|
+
function radarPaneHandle(qualified) {
|
|
1411
|
+
return qualified.slice(qualified.lastIndexOf(":") + 1);
|
|
1412
|
+
}
|
|
1413
|
+
function tmuxServerName(env) {
|
|
1414
|
+
const socket = env.TMUX?.trim().split(",")[0];
|
|
1415
|
+
return socket ? basename(socket) || void 0 : void 0;
|
|
1416
|
+
}
|
|
1417
|
+
function qualifiedOwnPane(env, backend, instance) {
|
|
1418
|
+
if (backend !== "tmux")
|
|
1419
|
+
return {
|
|
1420
|
+
absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
|
|
1421
|
+
};
|
|
1422
|
+
if (instance === void 0 || instance === "")
|
|
1423
|
+
return {
|
|
1424
|
+
pending: "the daemon did not name the tmux server it addresses"
|
|
1425
|
+
};
|
|
1426
|
+
const server = tmuxServerName(env);
|
|
1427
|
+
if (server === void 0)
|
|
1428
|
+
return {
|
|
1429
|
+
absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
|
|
1430
|
+
};
|
|
1431
|
+
if (server !== instance)
|
|
1432
|
+
return {
|
|
1433
|
+
absent: `this process is attached to tmux server ${server}, not to ${instance}`
|
|
1434
|
+
};
|
|
1435
|
+
const pane = radarLocalPane(env);
|
|
1436
|
+
if (pane === void 0)
|
|
1437
|
+
return {
|
|
1438
|
+
absent: "this process's own environment names no tmux pane of that server"
|
|
1439
|
+
};
|
|
1440
|
+
return { pane: `tmux:${instance}:${pane}` };
|
|
1441
|
+
}
|
|
1442
|
+
function observedContainment(inventory, paneId) {
|
|
1443
|
+
const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
|
|
1444
|
+
if (rows.length === 0) return void 0;
|
|
1445
|
+
if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
|
|
1446
|
+
const row = rows[0];
|
|
1447
|
+
const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
|
|
1448
|
+
if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
|
|
1449
|
+
return {
|
|
1450
|
+
ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
|
|
1451
|
+
};
|
|
1452
|
+
return { pane: row };
|
|
1453
|
+
}
|
|
1454
|
+
async function resolveRadarProcessLocation(client, env = process.env) {
|
|
1455
|
+
let ready;
|
|
1456
|
+
try {
|
|
1457
|
+
ready = await client.ping();
|
|
1458
|
+
} catch {
|
|
1459
|
+
return { state: "pending", reason: "the daemon did not answer this process" };
|
|
1460
|
+
}
|
|
1461
|
+
if (ready.ok === false)
|
|
1462
|
+
return {
|
|
1463
|
+
state: "pending",
|
|
1464
|
+
reason: `the daemon did not answer this process (${ready.code})`
|
|
1465
|
+
};
|
|
1466
|
+
const backend = ready.value.backend;
|
|
1467
|
+
if (backend === void 0 || backend === "none")
|
|
1468
|
+
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
1469
|
+
const instance = ready.value.instance ?? void 0;
|
|
1470
|
+
const own = qualifiedOwnPane(env, backend, instance);
|
|
1471
|
+
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
1472
|
+
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
1473
|
+
const paneId = own.pane;
|
|
1474
|
+
if (paneId === env.PI_HERDSMAN_PARENT_PANE_ID?.trim())
|
|
1475
|
+
return {
|
|
1476
|
+
state: "absent",
|
|
1477
|
+
reason: "this process is in the pane its launch was created beside, not in a pane of its own"
|
|
1478
|
+
};
|
|
1479
|
+
let inventory;
|
|
1480
|
+
try {
|
|
1481
|
+
inventory = await client.observe();
|
|
1482
|
+
} catch {
|
|
1483
|
+
return {
|
|
1484
|
+
state: "pending",
|
|
1485
|
+
reason: "the daemon's topology could not be read for this process's pane"
|
|
1486
|
+
};
|
|
1309
1487
|
}
|
|
1488
|
+
if (inventory.ok === false)
|
|
1489
|
+
return {
|
|
1490
|
+
state: "pending",
|
|
1491
|
+
reason: `the daemon's topology could not be read (${inventory.code})`
|
|
1492
|
+
};
|
|
1493
|
+
const containment = observedContainment(inventory.value, paneId);
|
|
1494
|
+
if (containment === void 0)
|
|
1495
|
+
return {
|
|
1496
|
+
state: "pending",
|
|
1497
|
+
reason: `the daemon's topology does not report pane ${paneId} yet`
|
|
1498
|
+
};
|
|
1499
|
+
if ("ambiguous" in containment)
|
|
1500
|
+
return { state: "pending", reason: containment.ambiguous };
|
|
1501
|
+
const row = containment.pane;
|
|
1310
1502
|
return {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
...request,
|
|
1319
|
-
sender_registration_id: identity.value,
|
|
1320
|
-
recipient_subject_id: parent
|
|
1321
|
-
});
|
|
1322
|
-
},
|
|
1323
|
-
async inbox() {
|
|
1324
|
-
const identity = await agentId();
|
|
1325
|
-
if (identity.ok === false) return identity;
|
|
1326
|
-
return client.communicationInbox({ registration_id: identity.value });
|
|
1327
|
-
},
|
|
1328
|
-
async get(handoff_id) {
|
|
1329
|
-
const identity = await agentId();
|
|
1330
|
-
if (identity.ok === false) return identity;
|
|
1331
|
-
return client.communicationGet({ registration_id: identity.value, handoff_id });
|
|
1332
|
-
},
|
|
1333
|
-
async ack(handoff_id) {
|
|
1334
|
-
const identity = await agentId();
|
|
1335
|
-
if (identity.ok === false) return identity;
|
|
1336
|
-
return client.communicationAck({ registration_id: identity.value, handoff_id });
|
|
1503
|
+
state: "resolved",
|
|
1504
|
+
location: {
|
|
1505
|
+
backend,
|
|
1506
|
+
...instance !== void 0 ? { instance } : {},
|
|
1507
|
+
workspace: row.workspace_id,
|
|
1508
|
+
tab: row.tab_id,
|
|
1509
|
+
pane: paneId
|
|
1337
1510
|
}
|
|
1338
1511
|
};
|
|
1339
1512
|
}
|
|
1340
1513
|
|
|
1341
|
-
// extension/radar-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
RADAR_SUBJECT_SOURCE: () => RADAR_SUBJECT_SOURCE,
|
|
1345
|
-
createRadarExecutionAdapter: () => createRadarExecutionAdapter,
|
|
1346
|
-
radarLocalRegistration: () => radarLocalRegistration
|
|
1347
|
-
});
|
|
1348
|
-
|
|
1349
|
-
// extension/radar-publication.ts
|
|
1350
|
-
var radar_publication_exports = {};
|
|
1351
|
-
__export(radar_publication_exports, {
|
|
1352
|
-
RADAR_HEARTBEAT_MS: () => RADAR_HEARTBEAT_MS,
|
|
1353
|
-
RADAR_LEASE_MS: () => RADAR_LEASE_MS,
|
|
1354
|
-
RADAR_PUBLICATION_VERSION: () => RADAR_PUBLICATION_VERSION,
|
|
1355
|
-
createPublicationStore: () => createPublicationStore,
|
|
1356
|
-
createRadarProcessSlot: () => createRadarProcessSlot,
|
|
1357
|
-
createRadarPublication: () => createRadarPublication,
|
|
1358
|
-
parseProcStat: () => parseProcStat,
|
|
1359
|
-
radarBindingKey: () => radarBindingKey,
|
|
1360
|
-
radarProcessIncarnation: () => radarProcessIncarnation,
|
|
1361
|
-
radarProcessSlot: () => radarProcessSlot,
|
|
1362
|
-
readProcessBirth: () => readProcessBirth
|
|
1363
|
-
});
|
|
1364
|
-
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
1365
|
-
import {
|
|
1366
|
-
closeSync,
|
|
1367
|
-
constants,
|
|
1368
|
-
fstatSync,
|
|
1369
|
-
fsyncSync,
|
|
1370
|
-
lstatSync as lstatSync2,
|
|
1371
|
-
mkdirSync,
|
|
1372
|
-
openSync,
|
|
1373
|
-
readFileSync,
|
|
1374
|
-
renameSync,
|
|
1375
|
-
unlinkSync,
|
|
1376
|
-
writeSync
|
|
1377
|
-
} from "node:fs";
|
|
1378
|
-
import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
|
|
1379
|
-
var DUPLICATE_SESSION_DIAGNOSTIC = "Radar rejected this session claim as a duplicate; reload or resume the session before retrying";
|
|
1380
|
-
var RADAR_PUBLICATION_VERSION = 1;
|
|
1381
|
-
var RADAR_LEASE_MS = 3e4;
|
|
1382
|
-
var RADAR_HEARTBEAT_MS = 15e3;
|
|
1383
|
-
function parseProcStat(text) {
|
|
1384
|
-
const close = text.lastIndexOf(")");
|
|
1385
|
-
const separator = text.indexOf(" ");
|
|
1386
|
-
if (close <= 0 || separator <= 0 || separator > close) return void 0;
|
|
1387
|
-
const pid = Number(text.slice(0, separator));
|
|
1388
|
-
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
1389
|
-
const startTicks = Number(fields[19]);
|
|
1390
|
-
if (!Number.isInteger(pid) || pid <= 0) return void 0;
|
|
1391
|
-
if (!Number.isInteger(startTicks) || startTicks <= 0) return void 0;
|
|
1392
|
-
return { pid, start_ticks: startTicks };
|
|
1514
|
+
// extension/radar-launch.ts
|
|
1515
|
+
function unknownChild(reason) {
|
|
1516
|
+
return { kind: "unknown", diagnostic: trajectory(reason) };
|
|
1393
1517
|
}
|
|
1394
|
-
function
|
|
1395
|
-
|
|
1396
|
-
const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
1397
|
-
if (!/^[0-9a-f-]{8,64}$/i.test(bootId)) return void 0;
|
|
1398
|
-
const stat = parseProcStat(readFileSync("/proc/self/stat", "utf8"));
|
|
1399
|
-
return stat === void 0 ? void 0 : { boot_id: bootId, pid: stat.pid, start_ticks: stat.start_ticks };
|
|
1400
|
-
} catch {
|
|
1401
|
-
return void 0;
|
|
1402
|
-
}
|
|
1518
|
+
function trajectory(reason) {
|
|
1519
|
+
return `the daemon's current record of this launch's child is ${reason}`;
|
|
1403
1520
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1521
|
+
async function observeRadarChild(client, provenance) {
|
|
1522
|
+
if (provenance.registration_id === void 0)
|
|
1523
|
+
return unknownChild("not bound to a child by this launch");
|
|
1524
|
+
if (provenance.pane_id === void 0)
|
|
1525
|
+
return unknownChild("without a pane this launch recorded for it");
|
|
1526
|
+
const read = await client.spawnGet(provenance.request_id);
|
|
1527
|
+
if (read.ok === false)
|
|
1528
|
+
return unknownChild(
|
|
1529
|
+
`not readable (${read.code}: ${read.message})`
|
|
1530
|
+
);
|
|
1531
|
+
const topology = read.value;
|
|
1532
|
+
const edge = topology.edge;
|
|
1533
|
+
if (edge.bound === void 0 || edge.bound.registration_id === void 0)
|
|
1534
|
+
return unknownChild("not bound to a child by the daemon");
|
|
1535
|
+
if (edge.bound.registration_id !== provenance.registration_id)
|
|
1536
|
+
return unknownChild(
|
|
1537
|
+
`bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
|
|
1538
|
+
);
|
|
1539
|
+
if (edge.location?.pane === void 0)
|
|
1540
|
+
return unknownChild("recorded without a pane");
|
|
1541
|
+
if (edge.location.pane !== provenance.pane_id)
|
|
1542
|
+
return unknownChild(
|
|
1543
|
+
`recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
|
|
1544
|
+
);
|
|
1545
|
+
if (edge.exit_observation?.state !== "unknown")
|
|
1546
|
+
return unknownChild("recorded with an observed process exit");
|
|
1547
|
+
if (topology.freshness !== "fresh")
|
|
1548
|
+
return unknownChild(
|
|
1549
|
+
"on a pane the daemon's own inventory does not currently report"
|
|
1550
|
+
);
|
|
1551
|
+
if (topology.state !== "bound")
|
|
1552
|
+
return unknownChild(
|
|
1553
|
+
`not currently placed (state ${topology.state ?? "unreported"})`
|
|
1554
|
+
);
|
|
1555
|
+
const reply = await client.agentGet(edge.bound.registration_id);
|
|
1556
|
+
if (reply.ok === false)
|
|
1557
|
+
return unknownChild(
|
|
1558
|
+
`process verdict not readable (${reply.code}: ${reply.message})`
|
|
1559
|
+
);
|
|
1560
|
+
const { verification, location, contextSession } = reply.value;
|
|
1561
|
+
if (verification === "absent" || verification === "mismatched")
|
|
1562
|
+
return {
|
|
1563
|
+
kind: "gone",
|
|
1564
|
+
verification,
|
|
1565
|
+
diagnostic: trajectory(`the registered process is ${verification}`)
|
|
1566
|
+
};
|
|
1567
|
+
if (verification !== "verified")
|
|
1568
|
+
return unknownChild(`process verdict is ${verification}`);
|
|
1569
|
+
if (location?.pane !== provenance.pane_id)
|
|
1570
|
+
return unknownChild(
|
|
1571
|
+
`registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
|
|
1572
|
+
);
|
|
1573
|
+
if (provenance.session_id === void 0)
|
|
1574
|
+
return unknownChild("without a session this launch recorded for it");
|
|
1575
|
+
if (contextSession !== provenance.session_id)
|
|
1576
|
+
return unknownChild(
|
|
1577
|
+
`publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
|
|
1578
|
+
);
|
|
1455
1579
|
return {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
if (error.code === "ENOENT") return void 0;
|
|
1464
|
-
throw error;
|
|
1465
|
-
}
|
|
1466
|
-
let descriptor;
|
|
1467
|
-
try {
|
|
1468
|
-
descriptor = openSync(
|
|
1469
|
-
path,
|
|
1470
|
-
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
1471
|
-
);
|
|
1472
|
-
const info = fstatSync(descriptor);
|
|
1473
|
-
if (!info.isFile() || info.uid !== uid || (info.mode & 511) !== RECORD_MODE)
|
|
1474
|
-
throw new Error(`unsafe Radar record: ${path}`);
|
|
1475
|
-
if (info.size > MAX_RECORD_BYTES)
|
|
1476
|
-
throw new Error(`oversized Radar record: ${path}`);
|
|
1477
|
-
const contents = readFileSync(descriptor, { encoding: "utf8" });
|
|
1478
|
-
if (Buffer.byteLength(contents, "utf8") > MAX_RECORD_BYTES)
|
|
1479
|
-
throw new Error(`oversized Radar record: ${path}`);
|
|
1480
|
-
return JSON.parse(contents);
|
|
1481
|
-
} catch (error) {
|
|
1482
|
-
if (error.code === "ENOENT") return void 0;
|
|
1483
|
-
throw error;
|
|
1484
|
-
} finally {
|
|
1485
|
-
if (descriptor !== void 0) closeSync(descriptor);
|
|
1486
|
-
}
|
|
1487
|
-
},
|
|
1488
|
-
write(name, value) {
|
|
1489
|
-
const path = pathOf(name);
|
|
1490
|
-
const directory = dirname2(path);
|
|
1491
|
-
ensureDirectory(directory);
|
|
1492
|
-
const temporary = join2(
|
|
1493
|
-
directory,
|
|
1494
|
-
`.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID2()}.tmp`
|
|
1495
|
-
);
|
|
1496
|
-
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
1497
|
-
if (bytes.length > MAX_RECORD_BYTES)
|
|
1498
|
-
throw new Error(`Radar record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
1499
|
-
let descriptor;
|
|
1500
|
-
try {
|
|
1501
|
-
descriptor = openSync(temporary, "wx", RECORD_MODE);
|
|
1502
|
-
let offset = 0;
|
|
1503
|
-
while (offset < bytes.length)
|
|
1504
|
-
offset += writeSync(descriptor, bytes, offset, bytes.length - offset);
|
|
1505
|
-
fsyncSync(descriptor);
|
|
1506
|
-
closeSync(descriptor);
|
|
1507
|
-
descriptor = void 0;
|
|
1508
|
-
const info = lstatSync2(directory);
|
|
1509
|
-
if (info.isSymbolicLink() || !info.isDirectory() || info.uid !== uid || (info.mode & 511) !== PRIVATE_MODE)
|
|
1510
|
-
throw new Error(`unsafe Radar storage directory: ${directory}`);
|
|
1511
|
-
renameSync(temporary, path);
|
|
1512
|
-
const directoryFd = openSync(directory, constants.O_RDONLY);
|
|
1513
|
-
try {
|
|
1514
|
-
fsyncSync(directoryFd);
|
|
1515
|
-
} finally {
|
|
1516
|
-
closeSync(directoryFd);
|
|
1517
|
-
}
|
|
1518
|
-
} finally {
|
|
1519
|
-
if (descriptor !== void 0) closeSync(descriptor);
|
|
1520
|
-
try {
|
|
1521
|
-
unlinkSync(temporary);
|
|
1522
|
-
} catch (error) {
|
|
1523
|
-
if (error.code !== "ENOENT") throw error;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
},
|
|
1527
|
-
remove(name) {
|
|
1528
|
-
try {
|
|
1529
|
-
unlinkSync(pathOf(name));
|
|
1530
|
-
} catch (error) {
|
|
1531
|
-
if (error.code !== "ENOENT") throw error;
|
|
1532
|
-
}
|
|
1580
|
+
kind: "live",
|
|
1581
|
+
evidence: {
|
|
1582
|
+
request_id: provenance.request_id,
|
|
1583
|
+
registration_id: edge.bound.registration_id,
|
|
1584
|
+
source: edge.bound.source,
|
|
1585
|
+
incarnation: edge.bound.incarnation,
|
|
1586
|
+
pane_id: edge.location.pane
|
|
1533
1587
|
}
|
|
1534
1588
|
};
|
|
1535
1589
|
}
|
|
1536
|
-
function
|
|
1537
|
-
|
|
1590
|
+
async function readProcessGone(client, agentId) {
|
|
1591
|
+
const read = await client.agentGet(agentId);
|
|
1592
|
+
if (read.ok === false) return void 0;
|
|
1593
|
+
const { verification } = read.value;
|
|
1594
|
+
return verification === "absent" || verification === "mismatched" ? verification : void 0;
|
|
1538
1595
|
}
|
|
1539
|
-
function
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1596
|
+
async function relaunchRefusalForChild(client, agentId) {
|
|
1597
|
+
const read = await client.agentGet(agentId);
|
|
1598
|
+
if (read.ok === false)
|
|
1599
|
+
return `the child's process cannot be read (${read.code}: ${read.message})`;
|
|
1600
|
+
const { verification } = read.value;
|
|
1601
|
+
if (verification === "absent" || verification === "mismatched") return void 0;
|
|
1602
|
+
return `the child's registered process is ${verification === "verified" ? "still running" : "not proved gone"}`;
|
|
1542
1603
|
}
|
|
1543
|
-
function
|
|
1544
|
-
return
|
|
1604
|
+
function refusedLaunch(message) {
|
|
1605
|
+
return { kind: "refused", message };
|
|
1545
1606
|
}
|
|
1546
|
-
function
|
|
1547
|
-
return
|
|
1607
|
+
function unresolvedLaunch(requestId, message) {
|
|
1608
|
+
return {
|
|
1609
|
+
kind: "unresolved",
|
|
1610
|
+
request_id: requestId,
|
|
1611
|
+
message: `spawn ${requestId} is unresolved (${message})`
|
|
1612
|
+
};
|
|
1548
1613
|
}
|
|
1549
|
-
function
|
|
1550
|
-
if (
|
|
1551
|
-
|
|
1614
|
+
function edgeStanding(edge) {
|
|
1615
|
+
if (edge.created === "refused" || edge.launched === "refused")
|
|
1616
|
+
return "refused";
|
|
1617
|
+
return edge.created === "completed" && edge.launched === "completed" ? "launched" : "unknown";
|
|
1552
1618
|
}
|
|
1553
|
-
function
|
|
1554
|
-
|
|
1555
|
-
return typeof found === "string" ? found : void 0;
|
|
1556
|
-
}
|
|
1557
|
-
function numberAt2(value, key) {
|
|
1558
|
-
const found = recordAt2(value, key);
|
|
1559
|
-
return typeof found === "number" && Number.isFinite(found) ? found : void 0;
|
|
1560
|
-
}
|
|
1561
|
-
var PENDING_KEYS = "lease_ms,observed_at,sequence,snapshot";
|
|
1562
|
-
var ACCEPTED_KEYS = "lease_ms,observed_at,sequence,sent_at,snapshot";
|
|
1563
|
-
var OUTCOME_KEYS = "detail,result";
|
|
1564
|
-
var OUTCOME_KEYS_WITHOUT_DETAIL = "result";
|
|
1565
|
-
var WRITER_KEYS = "generation,handle";
|
|
1566
|
-
function exactKeys(value, expected) {
|
|
1567
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === expected;
|
|
1619
|
+
function edgeMessage(edge) {
|
|
1620
|
+
return `the daemon's spawn record is created ${edge.created} and launched ${edge.launched}`;
|
|
1568
1621
|
}
|
|
1569
|
-
var
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
"
|
|
1573
|
-
"
|
|
1574
|
-
"
|
|
1575
|
-
"
|
|
1576
|
-
"
|
|
1577
|
-
"
|
|
1578
|
-
"
|
|
1622
|
+
var PRE_EFFECT_CODES = [
|
|
1623
|
+
"absent",
|
|
1624
|
+
"unsupported",
|
|
1625
|
+
"bad_version",
|
|
1626
|
+
"bad_request",
|
|
1627
|
+
"bad_params",
|
|
1628
|
+
"unknown_method",
|
|
1629
|
+
"refused",
|
|
1630
|
+
"not_found",
|
|
1631
|
+
"busy",
|
|
1632
|
+
"backend_unavailable"
|
|
1579
1633
|
];
|
|
1580
|
-
function
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
const
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1634
|
+
function spawnFailure(result, requestId) {
|
|
1635
|
+
return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
|
|
1636
|
+
}
|
|
1637
|
+
async function launchRadarWorker(options) {
|
|
1638
|
+
const { client, launch, signal } = options;
|
|
1639
|
+
const pollMs = options.pollMs ?? 250;
|
|
1640
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
1641
|
+
const remaining = () => Math.max(0, deadline - Date.now());
|
|
1642
|
+
const parent = await options.publication.subject();
|
|
1643
|
+
if (parent === void 0 || !isUuid(parent.registration_id))
|
|
1644
|
+
return refusedLaunch(
|
|
1645
|
+
"this process has no Radar registration, so a worker cannot be spawned under it"
|
|
1646
|
+
);
|
|
1647
|
+
if (parent.subject_id === void 0 || !isUuid(parent.subject_id))
|
|
1648
|
+
return refusedLaunch(
|
|
1649
|
+
"this process has no managed Radar subject, so a worker cannot be spawned under it"
|
|
1650
|
+
);
|
|
1651
|
+
const pane = parent.location?.pane;
|
|
1652
|
+
if (pane === void 0 || pane === "")
|
|
1653
|
+
return refusedLaunch(
|
|
1654
|
+
"this process's Radar registration names no pane, so a worker cannot be placed beside it"
|
|
1655
|
+
);
|
|
1656
|
+
if (pane !== launch.pane_id)
|
|
1657
|
+
return refusedLaunch(
|
|
1658
|
+
`this process's Radar registration places it in pane ${pane}, not in the pane this launch would split`
|
|
1659
|
+
);
|
|
1660
|
+
const spawned = await client.spawn({
|
|
1661
|
+
request_id: launch.request_id,
|
|
1662
|
+
parent: parent.registration_id,
|
|
1663
|
+
parent_session_id: launch.parent_session_id,
|
|
1664
|
+
executable: launch.executable,
|
|
1665
|
+
managed: { parent_subject_id: parent.subject_id },
|
|
1666
|
+
...launch.continues !== void 0 ? { continue_subject_id: launch.continues } : {},
|
|
1667
|
+
argv: launch.argv,
|
|
1668
|
+
...launch.cwd !== "" ? { cwd: launch.cwd } : {},
|
|
1669
|
+
...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
|
|
1670
|
+
});
|
|
1671
|
+
if (spawned.ok === false) {
|
|
1672
|
+
const refusal = spawnFailure(spawned, launch.request_id);
|
|
1673
|
+
if (refusal.kind === "refused") return refusal;
|
|
1674
|
+
}
|
|
1675
|
+
if (spawned.ok === true) {
|
|
1676
|
+
const standing = edgeStanding(spawned.value);
|
|
1677
|
+
if (standing === "refused")
|
|
1678
|
+
return refusedLaunch(edgeMessage(spawned.value));
|
|
1679
|
+
if (standing === "unknown")
|
|
1680
|
+
return unresolvedLaunch(launch.request_id, edgeMessage(spawned.value));
|
|
1681
|
+
}
|
|
1682
|
+
const bound = await waitForChild(client, launch.request_id, {
|
|
1683
|
+
remaining,
|
|
1684
|
+
pollMs,
|
|
1685
|
+
...signal !== void 0 ? { signal } : {}
|
|
1686
|
+
});
|
|
1687
|
+
if (bound.kind !== "bound") return bound;
|
|
1688
|
+
const agentId = bound.registration_id;
|
|
1689
|
+
try {
|
|
1690
|
+
const state = await options.readers.waitForState(
|
|
1691
|
+
launch.mailbox,
|
|
1692
|
+
(candidate) => options.readers.startupAckMatches(candidate, {
|
|
1693
|
+
generation: launch.generation,
|
|
1694
|
+
runId: launch.identity.runId,
|
|
1695
|
+
ownerSessionId: launch.identity.ownerSessionId,
|
|
1696
|
+
workspaceId: launch.identity.workspaceId,
|
|
1697
|
+
agentLabel: launch.identity.agentLabel,
|
|
1698
|
+
// The child writes the raw TMUX_PANE of the pane it runs in. Requiring
|
|
1699
|
+
// the pane the daemon created is what refuses a child that inherited
|
|
1700
|
+
// another process's coordinates.
|
|
1701
|
+
paneId: radarPaneHandle(bound.pane_id)
|
|
1702
|
+
}),
|
|
1703
|
+
{ timeoutMs: remaining(), ...signal !== void 0 ? { signal } : {} }
|
|
1704
|
+
);
|
|
1705
|
+
return {
|
|
1706
|
+
kind: "ready",
|
|
1707
|
+
value: {
|
|
1708
|
+
request_id: launch.request_id,
|
|
1709
|
+
subject_id: bound.subject_id,
|
|
1710
|
+
pane_id: bound.pane_id,
|
|
1711
|
+
state
|
|
1712
|
+
}
|
|
1598
1713
|
};
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
const claimed = options.readers.readState(launch.mailbox)?.paneId;
|
|
1716
|
+
return unresolvedLaunch(
|
|
1717
|
+
launch.request_id,
|
|
1718
|
+
claimed !== void 0 && claimed !== radarPaneHandle(bound.pane_id) ? `the bound child ${agentId} claims the pane ${claimed}, not the pane ${bound.pane_id} this launch created for it` : `the bound child ${agentId} has not acknowledged this launch's startup (${String(error)})`
|
|
1719
|
+
);
|
|
1599
1720
|
}
|
|
1600
|
-
return snapshot;
|
|
1601
|
-
}
|
|
1602
|
-
var executionCodec = {
|
|
1603
|
-
parse: (value) => snapshotAt(value),
|
|
1604
|
-
bound: (snapshot) => boundedRadarSnapshot(snapshot)
|
|
1605
|
-
};
|
|
1606
|
-
function assignmentAt(value) {
|
|
1607
|
-
const base = snapshotAt(value, ASSIGNMENT_KEY_LIST);
|
|
1608
|
-
if (base === void 0) return void 0;
|
|
1609
|
-
const request = textAt(value, "request");
|
|
1610
|
-
const awaitedValue = recordAt2(value, "awaited");
|
|
1611
|
-
if (awaitedValue !== void 0 && (!Array.isArray(awaitedValue) || awaitedValue.length > RADAR_MAX_AWAITED || !awaitedValue.every((item) => typeof item === "string")))
|
|
1612
|
-
return void 0;
|
|
1613
|
-
if (recordAt2(value, "request") !== void 0 && request === void 0)
|
|
1614
|
-
return void 0;
|
|
1615
|
-
return {
|
|
1616
|
-
...base,
|
|
1617
|
-
...request !== void 0 ? { request } : {},
|
|
1618
|
-
...awaitedValue !== void 0 ? { awaited: awaitedValue } : {}
|
|
1619
|
-
};
|
|
1620
1721
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
const
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1722
|
+
async function waitForChild(client, requestId, options) {
|
|
1723
|
+
let last = "the daemon has not answered";
|
|
1724
|
+
for (; ; ) {
|
|
1725
|
+
if (options.signal?.aborted)
|
|
1726
|
+
return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
|
|
1727
|
+
const answer = await client.spawnGet(requestId);
|
|
1728
|
+
if (answer.ok === true) {
|
|
1729
|
+
const { edge, state, freshness } = answer.value;
|
|
1730
|
+
const standing = edgeStanding(edge);
|
|
1731
|
+
if (standing === "refused")
|
|
1732
|
+
return refusedLaunch(`${edgeMessage(edge)}`);
|
|
1733
|
+
if (standing === "unknown")
|
|
1734
|
+
return unresolvedLaunch(requestId, edgeMessage(edge));
|
|
1735
|
+
const pane = edge.location?.pane;
|
|
1736
|
+
if (pane === void 0 || pane === "")
|
|
1737
|
+
return unresolvedLaunch(
|
|
1738
|
+
requestId,
|
|
1739
|
+
"the daemon's launch recorded no pane to verify the child against"
|
|
1740
|
+
);
|
|
1741
|
+
if (freshness !== "fresh")
|
|
1742
|
+
return unresolvedLaunch(
|
|
1743
|
+
requestId,
|
|
1744
|
+
state === "unresolved" ? `the daemon's inventory does not report the pane ${pane} its own record names for this child` : `the daemon cannot confirm the pane ${pane} it recorded for this child`
|
|
1745
|
+
);
|
|
1746
|
+
if (edge.exit_observation?.state === "exited")
|
|
1747
|
+
return unresolvedLaunch(
|
|
1748
|
+
requestId,
|
|
1749
|
+
"the daemon observed the launched child's process exit"
|
|
1750
|
+
);
|
|
1751
|
+
const agentId = edge.bound?.registration_id;
|
|
1752
|
+
if (agentId !== void 0 && isUuid(agentId)) {
|
|
1753
|
+
const source = edge.bound?.source;
|
|
1754
|
+
const incarnation = edge.bound?.incarnation;
|
|
1755
|
+
if (source === void 0 || source === "" || incarnation === void 0 || incarnation === "")
|
|
1756
|
+
return unresolvedLaunch(
|
|
1757
|
+
requestId,
|
|
1758
|
+
`the daemon bound the child ${agentId} without recording the process it registered as`
|
|
1759
|
+
);
|
|
1760
|
+
const subjectId = edge.bound?.subject_id;
|
|
1761
|
+
if (subjectId === void 0 || !isUuid(subjectId))
|
|
1762
|
+
return unresolvedLaunch(
|
|
1763
|
+
requestId,
|
|
1764
|
+
`the daemon bound the child ${agentId} without recording its subject`
|
|
1765
|
+
);
|
|
1766
|
+
return {
|
|
1767
|
+
kind: "bound",
|
|
1768
|
+
subject_id: subjectId,
|
|
1769
|
+
registration_id: agentId,
|
|
1770
|
+
pane_id: pane,
|
|
1771
|
+
source,
|
|
1772
|
+
incarnation
|
|
1773
|
+
};
|
|
1660
1774
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
if (key === "background_started_unix_ms") {
|
|
1665
|
-
if (!Number.isSafeInteger(found) || found < 0) return void 0;
|
|
1666
|
-
facts[key] = found;
|
|
1667
|
-
continue;
|
|
1775
|
+
last = "no child has registered with this launch's token yet";
|
|
1776
|
+
} else {
|
|
1777
|
+
last = answer.message;
|
|
1668
1778
|
}
|
|
1669
|
-
if (
|
|
1670
|
-
|
|
1779
|
+
if (options.remaining() <= 0)
|
|
1780
|
+
return unresolvedLaunch(requestId, last);
|
|
1781
|
+
await delay(options.pollMs, void 0, {
|
|
1782
|
+
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
1783
|
+
});
|
|
1671
1784
|
}
|
|
1672
|
-
return facts;
|
|
1673
1785
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
function
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
registration_id: agentId,
|
|
1705
|
-
channel,
|
|
1706
|
-
publisher,
|
|
1707
|
-
sequence: 0
|
|
1708
|
-
};
|
|
1709
|
-
}
|
|
1710
|
-
function loadRecord() {
|
|
1711
|
-
const value = store.read(recordName);
|
|
1712
|
-
const agentId = textAt(value, "registration_id");
|
|
1713
|
-
const storedPublisher = recordAt2(value, "publisher");
|
|
1714
|
-
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) || textAt(value, "channel") !== channel || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
|
|
1715
|
-
return blank("");
|
|
1716
|
-
const writer = recordAt2(value, "writer");
|
|
1717
|
-
const handle = textAt(writer, "handle");
|
|
1718
|
-
const generation = numberAt2(writer, "generation");
|
|
1719
|
-
if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
|
|
1720
|
-
return blank(agentId);
|
|
1721
|
-
const pending = pendingAt(recordAt2(value, "pending"), PENDING_KEYS, codec);
|
|
1722
|
-
const acceptedValue = recordAt2(value, "accepted");
|
|
1723
|
-
const accepted = pendingAt(acceptedValue, ACCEPTED_KEYS, codec);
|
|
1724
|
-
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
1725
|
-
return {
|
|
1726
|
-
version: RADAR_PUBLICATION_VERSION,
|
|
1727
|
-
registration_id: agentId,
|
|
1728
|
-
channel,
|
|
1729
|
-
publisher,
|
|
1730
|
-
...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
|
|
1731
|
-
...textAt(value, "retired_at") !== void 0 ? { retired_at: textAt(value, "retired_at") } : {},
|
|
1732
|
-
sequence: numberAt2(value, "sequence") ?? 0,
|
|
1733
|
-
...pending !== void 0 ? { pending } : {},
|
|
1734
|
-
...accepted !== void 0 && sentAt !== void 0 ? { accepted: { ...accepted, sent_at: sentAt } } : {},
|
|
1735
|
-
...textAt(value, "diagnostic") !== void 0 ? { diagnostic: textAt(value, "diagnostic") } : {}
|
|
1736
|
-
};
|
|
1737
|
-
}
|
|
1738
|
-
function note(message) {
|
|
1739
|
-
if (record.diagnostic !== void 0) return;
|
|
1740
|
-
record.diagnostic = message.slice(0, 256);
|
|
1741
|
-
save();
|
|
1742
|
-
}
|
|
1743
|
-
function save() {
|
|
1744
|
-
store.write(recordName, record);
|
|
1745
|
-
}
|
|
1746
|
-
function sameSnapshot(left, right) {
|
|
1747
|
-
return JSON.stringify(codec.bound(left)) === JSON.stringify(codec.bound(right));
|
|
1748
|
-
}
|
|
1749
|
-
function fence(message) {
|
|
1750
|
-
fenced = true;
|
|
1751
|
-
stopPump();
|
|
1752
|
-
note(`channel ${channel} stopped: ${message}`);
|
|
1753
|
-
}
|
|
1754
|
-
function startPump() {
|
|
1755
|
-
if (pump !== void 0 || stopped || fenced || retired) return;
|
|
1756
|
-
pump = scheduler.setInterval(() => kick(), input.heartbeatMs);
|
|
1757
|
-
pump.unref?.();
|
|
1758
|
-
}
|
|
1759
|
-
function stopPump() {
|
|
1760
|
-
if (pump === void 0) return;
|
|
1761
|
-
scheduler.clearInterval(pump);
|
|
1762
|
-
pump = void 0;
|
|
1763
|
-
}
|
|
1764
|
-
async function ensureWriter() {
|
|
1765
|
-
if (record.writer !== void 0) return true;
|
|
1766
|
-
const agentId = await input.subject(recovering);
|
|
1767
|
-
if (agentId === void 0) return false;
|
|
1768
|
-
if (record.registration_id !== agentId) {
|
|
1769
|
-
record = blank(agentId);
|
|
1770
|
-
save();
|
|
1771
|
-
}
|
|
1772
|
-
const answer = await client.acquire({
|
|
1773
|
-
registration_id: agentId,
|
|
1774
|
-
channel,
|
|
1775
|
-
publisher
|
|
1776
|
-
});
|
|
1777
|
-
if (answer.ok === false) {
|
|
1778
|
-
if (answer.code === "not_found") {
|
|
1779
|
-
recovering = true;
|
|
1780
|
-
return false;
|
|
1786
|
+
|
|
1787
|
+
// extension/radar-children.ts
|
|
1788
|
+
var PAGE = 100;
|
|
1789
|
+
var MAX_PAGES = 20;
|
|
1790
|
+
function createRadarChildren(client, publication) {
|
|
1791
|
+
const held = /* @__PURE__ */ new Map();
|
|
1792
|
+
async function resolveAll() {
|
|
1793
|
+
const newest = /* @__PURE__ */ new Map();
|
|
1794
|
+
let after;
|
|
1795
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
1796
|
+
const read = await client.spawnList({ ...after !== void 0 ? { after } : {}, limit: PAGE });
|
|
1797
|
+
if (read.ok === false) return read;
|
|
1798
|
+
for (const edge of read.value.spawns) {
|
|
1799
|
+
const bound = edge.bound;
|
|
1800
|
+
if (!bound?.subject_id || !bound.registration_id || edge.launched !== "completed") continue;
|
|
1801
|
+
const at = Date.parse(bound.bound_at);
|
|
1802
|
+
if (Number.isNaN(at)) continue;
|
|
1803
|
+
const current = newest.get(bound.subject_id);
|
|
1804
|
+
if (current !== void 0 && current.at >= at) continue;
|
|
1805
|
+
newest.set(bound.subject_id, {
|
|
1806
|
+
at,
|
|
1807
|
+
registration_id: bound.registration_id,
|
|
1808
|
+
child: {
|
|
1809
|
+
subject_id: bound.subject_id,
|
|
1810
|
+
request_id: edge.request_id,
|
|
1811
|
+
...edge.location?.pane ? { pane_id: edge.location.pane } : {},
|
|
1812
|
+
state: edge.state,
|
|
1813
|
+
exited: edge.exit_observation != null && edge.exit_observation.state !== "unknown"
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1781
1816
|
}
|
|
1782
|
-
if (
|
|
1783
|
-
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
recovering = false;
|
|
1787
|
-
record.writer = {
|
|
1788
|
-
handle: answer.value.handle,
|
|
1789
|
-
generation: answer.value.generation
|
|
1790
|
-
};
|
|
1791
|
-
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
1792
|
-
save();
|
|
1793
|
-
startPump();
|
|
1794
|
-
return true;
|
|
1795
|
-
}
|
|
1796
|
-
function heartbeatDue() {
|
|
1797
|
-
return record.accepted !== void 0 && scheduler.now() - record.accepted.sent_at >= input.heartbeatMs;
|
|
1798
|
-
}
|
|
1799
|
-
async function send(pending) {
|
|
1800
|
-
const writer = record.writer;
|
|
1801
|
-
if (writer === void 0) return;
|
|
1802
|
-
const answer = await client.publish({
|
|
1803
|
-
registration_id: record.registration_id,
|
|
1804
|
-
channel,
|
|
1805
|
-
writer_handle: writer.handle,
|
|
1806
|
-
sequence: pending.sequence,
|
|
1807
|
-
lease_ms: pending.lease_ms,
|
|
1808
|
-
observed_at: pending.observed_at,
|
|
1809
|
-
snapshot: pending.snapshot
|
|
1810
|
-
});
|
|
1811
|
-
if (answer.ok === true) {
|
|
1812
|
-
recovering = false;
|
|
1813
|
-
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
1814
|
-
record.accepted = { ...pending, sent_at: scheduler.now() };
|
|
1815
|
-
record.pending = void 0;
|
|
1816
|
-
save();
|
|
1817
|
-
return;
|
|
1818
|
-
}
|
|
1819
|
-
if (answer.code === "refused" || answer.code === "bad_params") {
|
|
1820
|
-
fence(answer.message);
|
|
1821
|
-
return;
|
|
1822
|
-
}
|
|
1823
|
-
if (answer.code === "not_found") {
|
|
1824
|
-
record.writer = void 0;
|
|
1825
|
-
recovering = true;
|
|
1826
|
-
save();
|
|
1827
|
-
return;
|
|
1817
|
+
if (!read.value.next) return { ok: true, value: newest };
|
|
1818
|
+
after = read.value.next;
|
|
1828
1819
|
}
|
|
1820
|
+
return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
|
|
1829
1821
|
}
|
|
1830
|
-
async function
|
|
1831
|
-
const
|
|
1832
|
-
|
|
1833
|
-
snapshot: codec.bound(snapshot),
|
|
1834
|
-
lease_ms: input.leaseMs,
|
|
1835
|
-
observed_at: new Date(scheduler.now()).toISOString()
|
|
1836
|
-
};
|
|
1837
|
-
record.pending = pending;
|
|
1838
|
-
save();
|
|
1839
|
-
await send(pending);
|
|
1840
|
-
}
|
|
1841
|
-
async function drainOnce() {
|
|
1842
|
-
if (fenced || retired) return;
|
|
1843
|
-
if (!await ensureWriter()) return;
|
|
1844
|
-
if (fenced) return;
|
|
1845
|
-
if (record.pending !== void 0) {
|
|
1846
|
-
await send(record.pending);
|
|
1847
|
-
return;
|
|
1848
|
-
}
|
|
1849
|
-
if (desired !== void 0 && (record.accepted === void 0 || !sameSnapshot(desired, record.accepted.snapshot))) {
|
|
1850
|
-
await stage(desired);
|
|
1851
|
-
return;
|
|
1852
|
-
}
|
|
1853
|
-
if (heartbeatDue() && record.accepted !== void 0)
|
|
1854
|
-
await stage(record.accepted.snapshot);
|
|
1822
|
+
async function resolve2(subject) {
|
|
1823
|
+
const all = await resolveAll();
|
|
1824
|
+
return all.ok ? all.value.get(subject) : void 0;
|
|
1855
1825
|
}
|
|
1856
|
-
async function
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
} catch (error) {
|
|
1862
|
-
fenced = true;
|
|
1863
|
-
stopPump();
|
|
1864
|
-
try {
|
|
1865
|
-
note(`channel ${channel} storage failure: ${String(error).slice(0, 180)}`);
|
|
1866
|
-
} catch {
|
|
1867
|
-
}
|
|
1868
|
-
} finally {
|
|
1869
|
-
running = false;
|
|
1870
|
-
if (again && !stopped && !fenced && !retired) {
|
|
1871
|
-
again = false;
|
|
1872
|
-
kick();
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1826
|
+
async function resolveAndFollow(subject) {
|
|
1827
|
+
const all = await resolveAll();
|
|
1828
|
+
if (!all.ok) return void 0;
|
|
1829
|
+
follow(all.value);
|
|
1830
|
+
return all.value.get(subject);
|
|
1875
1831
|
}
|
|
1876
|
-
function
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
return;
|
|
1832
|
+
function follow(all) {
|
|
1833
|
+
for (const [subject, entry] of held) {
|
|
1834
|
+
const target = all.get(subject);
|
|
1835
|
+
if (target !== void 0 && entry.published !== target.registration_id)
|
|
1836
|
+
entry.publish?.(target);
|
|
1882
1837
|
}
|
|
1883
|
-
void drain().catch(() => void 0);
|
|
1884
1838
|
}
|
|
1885
1839
|
return {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1840
|
+
async list() {
|
|
1841
|
+
const all = await resolveAll();
|
|
1842
|
+
if (!all.ok) return all;
|
|
1843
|
+
follow(all.value);
|
|
1844
|
+
return { ok: true, value: [...all.value.values()].map((entry) => entry.child) };
|
|
1890
1845
|
},
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1846
|
+
assignment(subject, owner) {
|
|
1847
|
+
const existing = held.get(subject);
|
|
1848
|
+
if (existing !== void 0) return existing.writer;
|
|
1849
|
+
let latest;
|
|
1850
|
+
const entry = { writer: void 0 };
|
|
1851
|
+
const send = (target) => {
|
|
1852
|
+
if (latest === void 0) return;
|
|
1853
|
+
if (entry.published !== void 0 && entry.published !== target.registration_id)
|
|
1854
|
+
publication.retireAssignment(entry.published);
|
|
1855
|
+
entry.published = target.registration_id;
|
|
1856
|
+
publication.assignment(target.registration_id, owner).update(latest);
|
|
1857
|
+
};
|
|
1858
|
+
entry.publish = send;
|
|
1859
|
+
entry.writer = {
|
|
1860
|
+
update(snapshot) {
|
|
1861
|
+
latest = snapshot;
|
|
1862
|
+
void resolve2(subject).then((target) => {
|
|
1863
|
+
if (target !== void 0) send(target);
|
|
1864
|
+
});
|
|
1865
|
+
},
|
|
1866
|
+
retire() {
|
|
1867
|
+
latest = void 0;
|
|
1868
|
+
if (entry.published !== void 0) publication.retireAssignment(entry.published);
|
|
1869
|
+
entry.published = void 0;
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
held.set(subject, entry);
|
|
1873
|
+
return entry.writer;
|
|
1910
1874
|
},
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1875
|
+
retire(subject) {
|
|
1876
|
+
held.get(subject)?.writer.retire();
|
|
1877
|
+
held.delete(subject);
|
|
1914
1878
|
},
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
if (
|
|
1879
|
+
async observe(subject, session_id) {
|
|
1880
|
+
const target = await resolveAndFollow(subject);
|
|
1881
|
+
if (target === void 0)
|
|
1882
|
+
return { kind: "unknown", diagnostic: "the daemon records no bound launch for this child" };
|
|
1883
|
+
return observeRadarChild(client, {
|
|
1884
|
+
request_id: target.child.request_id,
|
|
1885
|
+
pane_id: target.child.pane_id,
|
|
1886
|
+
registration_id: target.registration_id,
|
|
1887
|
+
session_id
|
|
1888
|
+
});
|
|
1918
1889
|
},
|
|
1919
|
-
|
|
1890
|
+
async inspect(subject, session_id) {
|
|
1891
|
+
const target = await resolveAndFollow(subject);
|
|
1892
|
+
if (target?.child.pane_id === void 0)
|
|
1893
|
+
throw new Error("Inspection target is not a bound child with a pane");
|
|
1894
|
+
return inspectRadarChild(client, {
|
|
1895
|
+
agentId: target.registration_id,
|
|
1896
|
+
paneId: target.child.pane_id,
|
|
1897
|
+
...session_id !== void 0 ? { session: session_id } : {}
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1920
1900
|
};
|
|
1921
1901
|
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
diagnostic = `presence storage unavailable: ${String(error)}`;
|
|
1933
|
-
}
|
|
1934
|
-
let timer;
|
|
1935
|
-
let stopped = false;
|
|
1936
|
-
let running;
|
|
1937
|
-
function save() {
|
|
1938
|
-
try {
|
|
1939
|
-
if (state) store.write(recordName, state);
|
|
1940
|
-
return true;
|
|
1941
|
-
} catch (error) {
|
|
1942
|
-
diagnostic = `presence storage unavailable: ${String(error)}`;
|
|
1943
|
-
return false;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
async function releasePending() {
|
|
1947
|
-
if (!state) return;
|
|
1948
|
-
const registration = await input.registration();
|
|
1949
|
-
if (!registration?.process) return;
|
|
1950
|
-
for (const release of [...state.releases]) {
|
|
1951
|
-
const answer = await client.releasePresence({ ...release, process: registration.process });
|
|
1952
|
-
if (!answer.ok) return;
|
|
1953
|
-
state.releases = state.releases.filter((r) => r.claim_id !== release.claim_id);
|
|
1954
|
-
save();
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
function schedule() {
|
|
1958
|
-
if (stopped || timer) return;
|
|
1959
|
-
timer = scheduler.setTimeout(() => {
|
|
1960
|
-
timer = void 0;
|
|
1961
|
-
void ensure();
|
|
1962
|
-
}, input.heartbeatMs);
|
|
1963
|
-
timer.unref?.();
|
|
1964
|
-
}
|
|
1965
|
-
async function ensure() {
|
|
1966
|
-
if (running) return running;
|
|
1967
|
-
if (stopped || !state) return void 0;
|
|
1968
|
-
const claim = state;
|
|
1969
|
-
if (!save()) return void 0;
|
|
1970
|
-
running = (async () => {
|
|
1971
|
-
await releasePending();
|
|
1972
|
-
const registration = await input.registration();
|
|
1973
|
-
if (!registration?.process || state !== claim || stopped) return void 0;
|
|
1974
|
-
if (claim.initial_load) {
|
|
1975
|
-
if (registration.spawn_token) claim.claim_id = registration.spawn_token;
|
|
1976
|
-
claim.initial_load = false;
|
|
1977
|
-
if (!save()) return void 0;
|
|
1978
|
-
}
|
|
1979
|
-
const answer = await client.presence({
|
|
1980
|
-
...registration,
|
|
1981
|
-
process: registration.process,
|
|
1982
|
-
session_id: claim.session,
|
|
1983
|
-
claim_id: claim.claim_id,
|
|
1984
|
-
...claim.registration_id ? { registration_id: claim.registration_id } : {},
|
|
1985
|
-
...claim.subject_id ? { subject_id: claim.subject_id } : {}
|
|
1986
|
-
});
|
|
1987
|
-
if (state !== claim) return void 0;
|
|
1988
|
-
if (!answer.ok) {
|
|
1989
|
-
if (!["absent", "timeout", "transport"].includes(answer.code)) diagnostic = answer.message;
|
|
1990
|
-
return void 0;
|
|
1991
|
-
}
|
|
1992
|
-
claim.registration_id = answer.value.registration_id;
|
|
1993
|
-
claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
|
|
1994
|
-
standing = answer.value.standing === "granted" ? { state: "managed", subject_id: claim.subject_id } : answer.value.standing === "duplicate" ? { state: "duplicate" } : answer.value.standing === "pending" ? { state: "pending" } : { state: "unmanaged" };
|
|
1995
|
-
diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
|
|
1996
|
-
save();
|
|
1997
|
-
return claim.registration_id;
|
|
1998
|
-
})();
|
|
1999
|
-
try {
|
|
2000
|
-
return await running;
|
|
2001
|
-
} catch (error) {
|
|
2002
|
-
diagnostic = `presence unavailable: ${String(error)}`;
|
|
2003
|
-
return void 0;
|
|
2004
|
-
} finally {
|
|
2005
|
-
running = void 0;
|
|
2006
|
-
if (!stopped && state !== claim) void ensure();
|
|
2007
|
-
else schedule();
|
|
2008
|
-
}
|
|
1902
|
+
|
|
1903
|
+
// extension/radar-communication.ts
|
|
1904
|
+
var radar_communication_exports = {};
|
|
1905
|
+
__export(radar_communication_exports, {
|
|
1906
|
+
createRadarHandoffPort: () => createRadarHandoffPort
|
|
1907
|
+
});
|
|
1908
|
+
function createRadarHandoffPort(client, publication, parentSubjectId = () => process.env.PI_RADAR_MANAGED_PARENT_SUBJECT_ID) {
|
|
1909
|
+
async function agentId() {
|
|
1910
|
+
const subject = await publication.subject();
|
|
1911
|
+
return subject === void 0 ? { ok: false, code: "absent", message: "this process has no current Radar registration" } : { ok: true, value: subject.registration_id };
|
|
2009
1912
|
}
|
|
2010
1913
|
return {
|
|
2011
|
-
|
|
2012
|
-
const
|
|
2013
|
-
if (
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
ensure,
|
|
2023
|
-
async release() {
|
|
2024
|
-
const releasing = state;
|
|
2025
|
-
standing = void 0;
|
|
2026
|
-
stopped = true;
|
|
2027
|
-
if (timer) scheduler.clearTimeout(timer);
|
|
2028
|
-
timer = void 0;
|
|
2029
|
-
if (running) await running;
|
|
2030
|
-
if (state === releasing) standing = void 0;
|
|
2031
|
-
if (releasing?.registration_id && !releasing.releases.some((r) => r.claim_id === releasing.claim_id)) {
|
|
2032
|
-
releasing.releases.push({ registration_id: releasing.registration_id, claim_id: releasing.claim_id });
|
|
2033
|
-
if (!save()) return;
|
|
2034
|
-
}
|
|
2035
|
-
await releasePending();
|
|
1914
|
+
async publish(request) {
|
|
1915
|
+
const identity = await agentId();
|
|
1916
|
+
if (identity.ok === false) return identity;
|
|
1917
|
+
const parent = parentSubjectId();
|
|
1918
|
+
if (parent === void 0)
|
|
1919
|
+
return { ok: false, code: "absent", message: "this managed execution has no Radar parent subject" };
|
|
1920
|
+
return client.communicationPublish({
|
|
1921
|
+
...request,
|
|
1922
|
+
sender_registration_id: identity.value,
|
|
1923
|
+
recipient_subject_id: parent
|
|
1924
|
+
});
|
|
2036
1925
|
},
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
if (
|
|
2040
|
-
|
|
1926
|
+
async inbox() {
|
|
1927
|
+
const identity = await agentId();
|
|
1928
|
+
if (identity.ok === false) return identity;
|
|
1929
|
+
return client.communicationInbox({ registration_id: identity.value });
|
|
2041
1930
|
},
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
1931
|
+
async get(handoff_id) {
|
|
1932
|
+
const identity = await agentId();
|
|
1933
|
+
if (identity.ok === false) return identity;
|
|
1934
|
+
return client.communicationGet({ registration_id: identity.value, handoff_id });
|
|
2045
1935
|
},
|
|
2046
|
-
|
|
2047
|
-
|
|
1936
|
+
async ack(handoff_id) {
|
|
1937
|
+
const identity = await agentId();
|
|
1938
|
+
if (identity.ok === false) return identity;
|
|
1939
|
+
return client.communicationAck({ registration_id: identity.value, handoff_id });
|
|
1940
|
+
}
|
|
2048
1941
|
};
|
|
2049
1942
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
1943
|
+
|
|
1944
|
+
// extension/radar-execution.ts
|
|
1945
|
+
var radar_execution_exports = {};
|
|
1946
|
+
__export(radar_execution_exports, {
|
|
1947
|
+
RADAR_SUBJECT_SOURCE: () => RADAR_SUBJECT_SOURCE,
|
|
1948
|
+
createRadarExecutionAdapter: () => createRadarExecutionAdapter,
|
|
1949
|
+
radarLocalRegistration: () => radarLocalRegistration
|
|
1950
|
+
});
|
|
1951
|
+
|
|
1952
|
+
// extension/radar-publication.ts
|
|
1953
|
+
var radar_publication_exports = {};
|
|
1954
|
+
__export(radar_publication_exports, {
|
|
1955
|
+
RADAR_HEARTBEAT_MS: () => RADAR_HEARTBEAT_MS,
|
|
1956
|
+
RADAR_LEASE_MS: () => RADAR_LEASE_MS,
|
|
1957
|
+
RADAR_PUBLICATION_VERSION: () => RADAR_PUBLICATION_VERSION,
|
|
1958
|
+
createPublicationStore: () => createPublicationStore,
|
|
1959
|
+
createRadarProcessSlot: () => createRadarProcessSlot,
|
|
1960
|
+
createRadarPublication: () => createRadarPublication,
|
|
1961
|
+
parseProcStat: () => parseProcStat,
|
|
1962
|
+
radarBindingKey: () => radarBindingKey,
|
|
1963
|
+
radarProcessIncarnation: () => radarProcessIncarnation,
|
|
1964
|
+
radarProcessSlot: () => radarProcessSlot,
|
|
1965
|
+
readProcessBirth: () => readProcessBirth
|
|
1966
|
+
});
|
|
1967
|
+
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
1968
|
+
import {
|
|
1969
|
+
closeSync,
|
|
1970
|
+
constants,
|
|
1971
|
+
fstatSync,
|
|
1972
|
+
fsyncSync,
|
|
1973
|
+
lstatSync as lstatSync2,
|
|
1974
|
+
mkdirSync,
|
|
1975
|
+
openSync,
|
|
1976
|
+
readFileSync,
|
|
1977
|
+
renameSync,
|
|
1978
|
+
unlinkSync,
|
|
1979
|
+
writeSync
|
|
1980
|
+
} from "node:fs";
|
|
1981
|
+
import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
|
|
1982
|
+
var DUPLICATE_SESSION_DIAGNOSTIC = "Radar rejected this session claim as a duplicate; reload or resume the session before retrying";
|
|
1983
|
+
var RADAR_PUBLICATION_VERSION = 1;
|
|
1984
|
+
var RADAR_LEASE_MS = 3e4;
|
|
1985
|
+
var RADAR_HEARTBEAT_MS = 15e3;
|
|
1986
|
+
function parseProcStat(text) {
|
|
1987
|
+
const close = text.lastIndexOf(")");
|
|
1988
|
+
const separator = text.indexOf(" ");
|
|
1989
|
+
if (close <= 0 || separator <= 0 || separator > close) return void 0;
|
|
1990
|
+
const pid = Number(text.slice(0, separator));
|
|
1991
|
+
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
1992
|
+
const startTicks = Number(fields[19]);
|
|
1993
|
+
if (!Number.isInteger(pid) || pid <= 0) return void 0;
|
|
1994
|
+
if (!Number.isInteger(startTicks) || startTicks <= 0) return void 0;
|
|
1995
|
+
return { pid, start_ticks: startTicks };
|
|
2057
1996
|
}
|
|
2058
|
-
function
|
|
1997
|
+
function readProcessBirth() {
|
|
2059
1998
|
try {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
1999
|
+
const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
2000
|
+
if (!/^[0-9a-f-]{8,64}$/i.test(bootId)) return void 0;
|
|
2001
|
+
const stat = parseProcStat(readFileSync("/proc/self/stat", "utf8"));
|
|
2002
|
+
return stat === void 0 ? void 0 : { boot_id: bootId, pid: stat.pid, start_ticks: stat.start_ticks };
|
|
2003
|
+
} catch {
|
|
2004
|
+
return void 0;
|
|
2063
2005
|
}
|
|
2064
2006
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2007
|
+
var RECORD_NAME = /^(?:registrations|presence|subjects|channels|context|bindings)\/[a-z0-9-]{1,64}\.json$/;
|
|
2008
|
+
var MAX_RECORD_BYTES = 64 * 1024;
|
|
2009
|
+
var PRIVATE_MODE = 448;
|
|
2010
|
+
var RECORD_MODE = 384;
|
|
2011
|
+
var PROCESS_SLOT = /* @__PURE__ */ Symbol.for("pi-radar.radar.process-slot.v1");
|
|
2012
|
+
function createPublicationStore(root) {
|
|
2013
|
+
const uid = process.getuid?.();
|
|
2014
|
+
if (uid === void 0) throw new Error("private Radar storage requires an effective uid");
|
|
2015
|
+
const ensureDirectory = (directory) => {
|
|
2016
|
+
let existed = true;
|
|
2017
|
+
try {
|
|
2018
|
+
lstatSync2(directory);
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
if (error.code !== "ENOENT") throw error;
|
|
2021
|
+
existed = false;
|
|
2022
|
+
}
|
|
2023
|
+
mkdirSync(directory, { recursive: true, mode: PRIVATE_MODE });
|
|
2024
|
+
const absolute = resolve(directory);
|
|
2025
|
+
const rootPath = parse(absolute).root;
|
|
2026
|
+
let current = rootPath;
|
|
2027
|
+
for (const part of absolute.slice(rootPath.length).split(/[\\/]+/u).filter(Boolean)) {
|
|
2028
|
+
current = join2(current, part);
|
|
2029
|
+
const info2 = lstatSync2(current);
|
|
2030
|
+
if (info2.isSymbolicLink() || !info2.isDirectory())
|
|
2031
|
+
throw new Error(`unsafe Radar storage directory: ${current}`);
|
|
2032
|
+
}
|
|
2033
|
+
const info = lstatSync2(absolute);
|
|
2034
|
+
if (info.uid !== uid) throw new Error(`Radar storage directory has wrong owner: ${absolute}`);
|
|
2035
|
+
if (existed && (info.mode & 511) !== PRIVATE_MODE)
|
|
2036
|
+
throw new Error(`Radar storage directory is not mode 0700: ${absolute}`);
|
|
2073
2037
|
};
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
ensure: async () => void 0,
|
|
2079
|
-
release: async () => void 0,
|
|
2080
|
-
stop: () => void 0,
|
|
2081
|
-
reopen: () => void 0,
|
|
2082
|
-
management: () => void 0,
|
|
2083
|
-
diagnostic: () => diagnostic
|
|
2084
|
-
},
|
|
2085
|
-
facts: { ...writer, update: () => void 0 },
|
|
2086
|
-
assignment: () => writer,
|
|
2087
|
-
binding: () => void 0,
|
|
2088
|
-
subject: async () => void 0,
|
|
2089
|
-
retireAssignment: () => void 0,
|
|
2090
|
-
diagnostic: () => diagnostic,
|
|
2091
|
-
stop: () => void 0,
|
|
2092
|
-
reopen: () => void 0
|
|
2038
|
+
const pathOf = (name) => {
|
|
2039
|
+
if (!RECORD_NAME.test(name))
|
|
2040
|
+
throw new Error(`invalid radar record name: ${name}`);
|
|
2041
|
+
return join2(root, name);
|
|
2093
2042
|
};
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
cached.reopen();
|
|
2107
|
-
return cached;
|
|
2108
|
-
}
|
|
2109
|
-
const registrationName = `registrations/${createHash("sha256").update(`${options.registration.source}\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
2110
|
-
const channelRecord = (channel, target) => `channels/${createHash("sha256").update(`${channel}\0${incarnation}\0${target ?? ""}`).digest("hex").slice(0, 32)}.json`;
|
|
2111
|
-
const presenceRecord = `presence/${createHash("sha256").update(`presence\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
2112
|
-
function readRegistration() {
|
|
2113
|
-
const value = store.read(registrationName);
|
|
2114
|
-
const registration = recordAt2(value, "registration");
|
|
2115
|
-
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || textAt(registration, "source") !== options.registration.source || textAt(registration, "incarnation") !== incarnation)
|
|
2116
|
-
return void 0;
|
|
2117
|
-
const storedAgentId = textAt(value, "registration_id");
|
|
2118
|
-
return {
|
|
2119
|
-
version: RADAR_PUBLICATION_VERSION,
|
|
2120
|
-
registration,
|
|
2121
|
-
...storedAgentId !== void 0 && isUuid(storedAgentId) ? { registration_id: storedAgentId } : {}
|
|
2122
|
-
};
|
|
2123
|
-
}
|
|
2124
|
-
const stored = readRegistration();
|
|
2125
|
-
let processRecord = stored;
|
|
2126
|
-
let registrationDiagnostic;
|
|
2127
|
-
let registrationRecord;
|
|
2128
|
-
async function ensureRegistration() {
|
|
2129
|
-
if (processRecord !== void 0) return processRecord;
|
|
2130
|
-
registrationRecord ??= (async () => {
|
|
2131
|
-
const resolution = options.location ? await options.location() : { state: "absent", reason: "this process publishes no location" };
|
|
2132
|
-
const built = {
|
|
2133
|
-
version: RADAR_PUBLICATION_VERSION,
|
|
2134
|
-
registration: {
|
|
2135
|
-
...options.registration,
|
|
2136
|
-
...resolution.state === "resolved" ? { location: resolution.location } : {},
|
|
2137
|
-
incarnation,
|
|
2138
|
-
...options.birth !== void 0 ? { process: options.birth } : {}
|
|
2139
|
-
}
|
|
2140
|
-
};
|
|
2141
|
-
try {
|
|
2142
|
-
store.write(registrationName, built);
|
|
2143
|
-
} catch (error) {
|
|
2144
|
-
registrationDiagnostic ??= `private storage unavailable: ${String(error)}`.slice(0, 256);
|
|
2145
|
-
return void 0;
|
|
2146
|
-
}
|
|
2147
|
-
registrationDiagnostic = void 0;
|
|
2148
|
-
processRecord = built;
|
|
2149
|
-
return built;
|
|
2150
|
-
})();
|
|
2151
|
-
try {
|
|
2152
|
-
return await registrationRecord;
|
|
2153
|
-
} finally {
|
|
2154
|
-
registrationRecord = void 0;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
let agentId = stored?.registration_id;
|
|
2158
|
-
let subjectReady;
|
|
2159
|
-
async function ensureSubject(recheck) {
|
|
2160
|
-
if (!recheck && agentId !== void 0) return agentId;
|
|
2161
|
-
subjectReady ??= (async () => {
|
|
2162
|
-
const ready = await client.ping();
|
|
2163
|
-
if (ready.ok === false) {
|
|
2164
|
-
if (ready.code !== "absent" && ready.code !== "timeout" && ready.code !== "transport")
|
|
2165
|
-
registrationDiagnostic ??= `daemon negotiation failed: ${ready.message}`.slice(0, 256);
|
|
2166
|
-
return void 0;
|
|
2167
|
-
}
|
|
2168
|
-
const record = await ensureRegistration();
|
|
2169
|
-
if (record === void 0) return void 0;
|
|
2170
|
-
const registered = await presence.ensure();
|
|
2171
|
-
if (registered === void 0) return void 0;
|
|
2172
|
-
agentId = registered;
|
|
2173
|
-
processRecord = { ...record, registration_id: agentId };
|
|
2174
|
-
store.write(registrationName, processRecord);
|
|
2175
|
-
if (options.binding !== void 0) writeBinding(options.binding);
|
|
2176
|
-
return agentId;
|
|
2177
|
-
})();
|
|
2178
|
-
try {
|
|
2179
|
-
return await subjectReady;
|
|
2180
|
-
} finally {
|
|
2181
|
-
subjectReady = void 0;
|
|
2043
|
+
const ensureTrustedDirectory = (directory) => {
|
|
2044
|
+
const absoluteRoot = resolve(root);
|
|
2045
|
+
const absoluteDirectory = resolve(directory);
|
|
2046
|
+
if (absoluteDirectory !== absoluteRoot && !absoluteDirectory.startsWith(`${absoluteRoot}${sep}`))
|
|
2047
|
+
throw new Error(`unsafe Radar storage directory: ${directory}`);
|
|
2048
|
+
const rootInfo = lstatSync2(absoluteRoot);
|
|
2049
|
+
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory() || rootInfo.uid !== uid || (rootInfo.mode & 511) !== PRIVATE_MODE)
|
|
2050
|
+
throw new Error(`unsafe Radar storage directory: ${absoluteRoot}`);
|
|
2051
|
+
if (absoluteDirectory !== absoluteRoot) {
|
|
2052
|
+
const info = lstatSync2(absoluteDirectory);
|
|
2053
|
+
if (info.isSymbolicLink() || !info.isDirectory() || info.uid !== uid || (info.mode & 511) !== PRIVATE_MODE)
|
|
2054
|
+
throw new Error(`unsafe Radar storage directory: ${absoluteDirectory}`);
|
|
2182
2055
|
}
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
run: binding.run,
|
|
2191
|
-
owner: binding.owner,
|
|
2192
|
-
label: binding.label,
|
|
2193
|
-
observed_at: new Date(scheduler.now()).toISOString()
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
const publisher = (source, reportingOwner) => ({
|
|
2197
|
-
source,
|
|
2198
|
-
incarnation,
|
|
2199
|
-
...reportingOwner !== void 0 ? { reporting_owner: reportingOwner } : {}
|
|
2200
|
-
});
|
|
2201
|
-
const execution = createChannelWriter({
|
|
2202
|
-
store,
|
|
2203
|
-
client,
|
|
2204
|
-
scheduler,
|
|
2205
|
-
recordName: channelRecord("execution"),
|
|
2206
|
-
channel: "execution",
|
|
2207
|
-
publisher: publisher("herdsman-pi"),
|
|
2208
|
-
subject: ensureSubject,
|
|
2209
|
-
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2210
|
-
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2211
|
-
});
|
|
2212
|
-
const presence = createPresenceReporter({
|
|
2213
|
-
store,
|
|
2214
|
-
client,
|
|
2215
|
-
scheduler,
|
|
2216
|
-
recordName: presenceRecord,
|
|
2217
|
-
registration: async () => {
|
|
2218
|
-
const record = await ensureRegistration();
|
|
2219
|
-
if (!record) return void 0;
|
|
2220
|
-
const location = await options.location?.();
|
|
2221
|
-
const { location: _initialLocation, ...identity } = record.registration;
|
|
2222
|
-
return {
|
|
2223
|
-
...identity,
|
|
2224
|
-
...location?.state === "resolved" ? { location: location.location } : {},
|
|
2225
|
-
...spawnToken ? { spawn_token: spawnToken } : {}
|
|
2226
|
-
};
|
|
2227
|
-
},
|
|
2228
|
-
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2229
|
-
});
|
|
2230
|
-
const facts = createChannelWriter({
|
|
2231
|
-
store,
|
|
2232
|
-
client,
|
|
2233
|
-
scheduler,
|
|
2234
|
-
recordName: channelRecord("facts"),
|
|
2235
|
-
channel: "facts",
|
|
2236
|
-
publisher: publisher("pi"),
|
|
2237
|
-
codec: factsCodec,
|
|
2238
|
-
subject: ensureSubject,
|
|
2239
|
-
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2240
|
-
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2241
|
-
});
|
|
2242
|
-
const assignmentWriters = /* @__PURE__ */ new Map();
|
|
2243
|
-
const publication = {
|
|
2244
|
-
execution,
|
|
2245
|
-
presence,
|
|
2246
|
-
facts,
|
|
2247
|
-
assignment(target, reportingOwner) {
|
|
2248
|
-
const existing = assignmentWriters.get(target);
|
|
2249
|
-
if (existing !== void 0) return existing;
|
|
2056
|
+
};
|
|
2057
|
+
ensureDirectory(root);
|
|
2058
|
+
return {
|
|
2059
|
+
path: pathOf,
|
|
2060
|
+
read(name) {
|
|
2061
|
+
const path = pathOf(name);
|
|
2062
|
+
const directory = dirname2(path);
|
|
2250
2063
|
try {
|
|
2251
|
-
|
|
2252
|
-
store,
|
|
2253
|
-
client,
|
|
2254
|
-
scheduler,
|
|
2255
|
-
recordName: channelRecord("assignment", target),
|
|
2256
|
-
channel: "assignment",
|
|
2257
|
-
publisher: publisher("herdsman-owner", reportingOwner),
|
|
2258
|
-
codec: assignmentCodec,
|
|
2259
|
-
// The owner publishes about a subject it did not register: the exact
|
|
2260
|
-
// registration_id from the child's sidecar is the whole correlation.
|
|
2261
|
-
subject: async () => target,
|
|
2262
|
-
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2263
|
-
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2264
|
-
});
|
|
2265
|
-
assignmentWriters.set(target, writer);
|
|
2266
|
-
return writer;
|
|
2064
|
+
ensureTrustedDirectory(directory);
|
|
2267
2065
|
} catch (error) {
|
|
2268
|
-
|
|
2269
|
-
|
|
2066
|
+
if (error.code === "ENOENT") return void 0;
|
|
2067
|
+
throw error;
|
|
2270
2068
|
}
|
|
2271
|
-
|
|
2272
|
-
binding(key) {
|
|
2273
|
-
let value;
|
|
2069
|
+
let descriptor;
|
|
2274
2070
|
try {
|
|
2275
|
-
|
|
2071
|
+
descriptor = openSync(
|
|
2072
|
+
path,
|
|
2073
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
2074
|
+
);
|
|
2075
|
+
const info = fstatSync(descriptor);
|
|
2076
|
+
if (!info.isFile() || info.uid !== uid || (info.mode & 511) !== RECORD_MODE)
|
|
2077
|
+
throw new Error(`unsafe Radar record: ${path}`);
|
|
2078
|
+
if (info.size > MAX_RECORD_BYTES)
|
|
2079
|
+
throw new Error(`oversized Radar record: ${path}`);
|
|
2080
|
+
const contents = readFileSync(descriptor, { encoding: "utf8" });
|
|
2081
|
+
if (Buffer.byteLength(contents, "utf8") > MAX_RECORD_BYTES)
|
|
2082
|
+
throw new Error(`oversized Radar record: ${path}`);
|
|
2083
|
+
return JSON.parse(contents);
|
|
2276
2084
|
} catch (error) {
|
|
2277
|
-
|
|
2278
|
-
|
|
2085
|
+
if (error.code === "ENOENT") return void 0;
|
|
2086
|
+
throw error;
|
|
2087
|
+
} finally {
|
|
2088
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
2279
2089
|
}
|
|
2280
|
-
const bound = textAt(value, "registration_id");
|
|
2281
|
-
const boundIncarnation = textAt(value, "incarnation");
|
|
2282
|
-
const run = textAt(value, "run");
|
|
2283
|
-
const owner = textAt(value, "owner");
|
|
2284
|
-
const label = textAt(value, "label");
|
|
2285
|
-
const observedAt = textAt(value, "observed_at");
|
|
2286
|
-
if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
|
|
2287
|
-
return void 0;
|
|
2288
|
-
return {
|
|
2289
|
-
version: RADAR_PUBLICATION_VERSION,
|
|
2290
|
-
registration_id: bound,
|
|
2291
|
-
incarnation: boundIncarnation,
|
|
2292
|
-
run,
|
|
2293
|
-
owner,
|
|
2294
|
-
label,
|
|
2295
|
-
observed_at: observedAt
|
|
2296
|
-
};
|
|
2297
2090
|
},
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
const
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
const
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2091
|
+
write(name, value) {
|
|
2092
|
+
const path = pathOf(name);
|
|
2093
|
+
const directory = dirname2(path);
|
|
2094
|
+
ensureDirectory(directory);
|
|
2095
|
+
const temporary = join2(
|
|
2096
|
+
directory,
|
|
2097
|
+
`.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID2()}.tmp`
|
|
2098
|
+
);
|
|
2099
|
+
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
2100
|
+
if (bytes.length > MAX_RECORD_BYTES)
|
|
2101
|
+
throw new Error(`Radar record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
2102
|
+
let descriptor;
|
|
2103
|
+
try {
|
|
2104
|
+
descriptor = openSync(temporary, "wx", RECORD_MODE);
|
|
2105
|
+
let offset = 0;
|
|
2106
|
+
while (offset < bytes.length)
|
|
2107
|
+
offset += writeSync(descriptor, bytes, offset, bytes.length - offset);
|
|
2108
|
+
fsyncSync(descriptor);
|
|
2109
|
+
closeSync(descriptor);
|
|
2110
|
+
descriptor = void 0;
|
|
2111
|
+
const info = lstatSync2(directory);
|
|
2112
|
+
if (info.isSymbolicLink() || !info.isDirectory() || info.uid !== uid || (info.mode & 511) !== PRIVATE_MODE)
|
|
2113
|
+
throw new Error(`unsafe Radar storage directory: ${directory}`);
|
|
2114
|
+
renameSync(temporary, path);
|
|
2115
|
+
const directoryFd = openSync(directory, constants.O_RDONLY);
|
|
2116
|
+
try {
|
|
2117
|
+
fsyncSync(directoryFd);
|
|
2118
|
+
} finally {
|
|
2119
|
+
closeSync(directoryFd);
|
|
2120
|
+
}
|
|
2121
|
+
} finally {
|
|
2122
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
2123
|
+
try {
|
|
2124
|
+
unlinkSync(temporary);
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
if (error.code !== "ENOENT") throw error;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2322
2129
|
},
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2130
|
+
remove(name) {
|
|
2131
|
+
try {
|
|
2132
|
+
unlinkSync(pathOf(name));
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
if (error.code !== "ENOENT") throw error;
|
|
2135
|
+
}
|
|
2328
2136
|
}
|
|
2329
2137
|
};
|
|
2330
|
-
slot.publications.set(cacheKey, publication);
|
|
2331
|
-
return publication;
|
|
2332
2138
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
var RADAR_SUBJECT_SOURCE = "herdsman-pi";
|
|
2336
|
-
function radarLocalRegistration() {
|
|
2337
|
-
return {
|
|
2338
|
-
source: RADAR_SUBJECT_SOURCE,
|
|
2339
|
-
incarnation: radarProcessIncarnation()
|
|
2340
|
-
};
|
|
2139
|
+
function createRadarProcessSlot() {
|
|
2140
|
+
return { publications: /* @__PURE__ */ new Map() };
|
|
2341
2141
|
}
|
|
2342
|
-
function
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2142
|
+
function radarProcessSlot() {
|
|
2143
|
+
const host = globalThis;
|
|
2144
|
+
if (!host[PROCESS_SLOT]) {
|
|
2145
|
+
const slot = createRadarProcessSlot();
|
|
2146
|
+
slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
|
|
2147
|
+
delete process.env.PI_RADAR_REGISTRATION_ID;
|
|
2148
|
+
host[PROCESS_SLOT] = slot;
|
|
2149
|
+
}
|
|
2150
|
+
return host[PROCESS_SLOT];
|
|
2346
2151
|
}
|
|
2347
|
-
function
|
|
2348
|
-
|
|
2349
|
-
if (typeof message !== "object" || message === null) return {};
|
|
2350
|
-
const value = message;
|
|
2351
|
-
return value.role === "assistant" ? { stopReason: value.stopReason } : {};
|
|
2152
|
+
function radarProcessIncarnation(slot = radarProcessSlot()) {
|
|
2153
|
+
return slot.incarnation ??= randomUUID2();
|
|
2352
2154
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2155
|
+
function radarBindingKey(run, owner, label) {
|
|
2156
|
+
return createHash("sha256").update(`${run}\0${owner}\0${label}`).digest("hex").slice(0, 32);
|
|
2157
|
+
}
|
|
2158
|
+
function recordAt2(value, key) {
|
|
2159
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2160
|
+
return value[key];
|
|
2161
|
+
}
|
|
2162
|
+
function textAt(value, key) {
|
|
2163
|
+
const found = recordAt2(value, key);
|
|
2164
|
+
return typeof found === "string" ? found : void 0;
|
|
2165
|
+
}
|
|
2166
|
+
function numberAt2(value, key) {
|
|
2167
|
+
const found = recordAt2(value, key);
|
|
2168
|
+
return typeof found === "number" && Number.isFinite(found) ? found : void 0;
|
|
2169
|
+
}
|
|
2170
|
+
var PENDING_KEYS = "lease_ms,observed_at,sequence,snapshot";
|
|
2171
|
+
var ACCEPTED_KEYS = "lease_ms,observed_at,sequence,sent_at,snapshot";
|
|
2172
|
+
var OUTCOME_KEYS = "detail,result";
|
|
2173
|
+
var OUTCOME_KEYS_WITHOUT_DETAIL = "result";
|
|
2174
|
+
var WRITER_KEYS = "generation,handle";
|
|
2175
|
+
function exactKeys(value, expected) {
|
|
2176
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === expected;
|
|
2177
|
+
}
|
|
2178
|
+
var SNAPSHOT_KEY_LIST = ["activity", "last_outcome", "waiting_reason"];
|
|
2179
|
+
var ASSIGNMENT_KEY_LIST = [...SNAPSHOT_KEY_LIST, "request", "awaited"];
|
|
2180
|
+
var FACTS_KEY_LIST = [
|
|
2181
|
+
"model",
|
|
2182
|
+
"provider",
|
|
2183
|
+
"thinking",
|
|
2184
|
+
"context_window",
|
|
2185
|
+
"session_name",
|
|
2186
|
+
"background_tasks",
|
|
2187
|
+
"background_started_unix_ms"
|
|
2188
|
+
];
|
|
2189
|
+
function snapshotAt(value, keys = SNAPSHOT_KEY_LIST) {
|
|
2190
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Object.keys(value).every((key) => keys.includes(key)))
|
|
2191
|
+
return void 0;
|
|
2192
|
+
const activity = textAt(value, "activity");
|
|
2193
|
+
if (activity === void 0) return void 0;
|
|
2194
|
+
const snapshot = { activity };
|
|
2195
|
+
const reason = textAt(value, "waiting_reason");
|
|
2196
|
+
if (reason !== void 0) snapshot.waiting_reason = reason;
|
|
2197
|
+
const outcome = recordAt2(value, "last_outcome");
|
|
2198
|
+
if (outcome !== void 0) {
|
|
2199
|
+
if (!exactKeys(outcome, OUTCOME_KEYS) && !exactKeys(outcome, OUTCOME_KEYS_WITHOUT_DETAIL))
|
|
2200
|
+
return void 0;
|
|
2201
|
+
const result = textAt(outcome, "result");
|
|
2202
|
+
if (result === void 0) return void 0;
|
|
2203
|
+
const detail = textAt(outcome, "detail");
|
|
2204
|
+
snapshot.last_outcome = {
|
|
2205
|
+
result,
|
|
2206
|
+
...detail !== void 0 ? { detail } : {}
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
return snapshot;
|
|
2210
|
+
}
|
|
2211
|
+
var executionCodec = {
|
|
2212
|
+
parse: (value) => snapshotAt(value),
|
|
2213
|
+
bound: (snapshot) => boundedRadarSnapshot(snapshot)
|
|
2214
|
+
};
|
|
2215
|
+
function assignmentAt(value) {
|
|
2216
|
+
const base = snapshotAt(value, ASSIGNMENT_KEY_LIST);
|
|
2217
|
+
if (base === void 0) return void 0;
|
|
2218
|
+
const request = textAt(value, "request");
|
|
2219
|
+
const awaitedValue = recordAt2(value, "awaited");
|
|
2220
|
+
if (awaitedValue !== void 0 && (!Array.isArray(awaitedValue) || awaitedValue.length > RADAR_MAX_AWAITED || !awaitedValue.every((item) => typeof item === "string")))
|
|
2221
|
+
return void 0;
|
|
2222
|
+
if (recordAt2(value, "request") !== void 0 && request === void 0)
|
|
2223
|
+
return void 0;
|
|
2361
2224
|
return {
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
report: () => void 0,
|
|
2366
|
-
stop: () => void 0,
|
|
2367
|
-
reopen: () => void 0,
|
|
2368
|
-
management: () => void 0,
|
|
2369
|
-
ensure: async () => void 0,
|
|
2370
|
-
release: async () => void 0,
|
|
2371
|
-
diagnostic: () => "Radar private storage is unavailable"
|
|
2372
|
-
},
|
|
2373
|
-
assignment: () => writer,
|
|
2374
|
-
binding: () => void 0,
|
|
2375
|
-
subject: async () => void 0,
|
|
2376
|
-
retireAssignment: () => void 0,
|
|
2377
|
-
diagnostic: writer.diagnostic,
|
|
2378
|
-
stop: () => void 0,
|
|
2379
|
-
reopen: () => void 0
|
|
2225
|
+
...base,
|
|
2226
|
+
...request !== void 0 ? { request } : {},
|
|
2227
|
+
...awaitedValue !== void 0 ? { awaited: awaitedValue } : {}
|
|
2380
2228
|
};
|
|
2381
2229
|
}
|
|
2382
|
-
|
|
2230
|
+
var assignmentCodec = {
|
|
2231
|
+
parse: (value) => assignmentAt(value),
|
|
2232
|
+
bound: (snapshot) => boundedRadarAssignment(snapshot)
|
|
2233
|
+
};
|
|
2234
|
+
function factsAt(value) {
|
|
2235
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
2236
|
+
return void 0;
|
|
2237
|
+
const record = value;
|
|
2238
|
+
if (!Object.keys(record).every((key) => FACTS_KEY_LIST.includes(key)))
|
|
2239
|
+
return void 0;
|
|
2383
2240
|
const facts = {};
|
|
2384
|
-
const
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2241
|
+
for (const key of FACTS_KEY_LIST) {
|
|
2242
|
+
const found = record[key];
|
|
2243
|
+
if (found === void 0) continue;
|
|
2244
|
+
if (key === "context_window") {
|
|
2245
|
+
if (typeof found !== "object" || found === null || Array.isArray(found))
|
|
2246
|
+
return void 0;
|
|
2247
|
+
const window = found;
|
|
2248
|
+
if (Object.keys(window).sort().join(",") !== "tokens,window") return void 0;
|
|
2249
|
+
const tokens = window.tokens;
|
|
2250
|
+
const size = window.window;
|
|
2251
|
+
if (!Number.isSafeInteger(tokens) || tokens < 0 || !Number.isSafeInteger(size) || size <= 0 || tokens > size)
|
|
2252
|
+
return void 0;
|
|
2253
|
+
facts[key] = { tokens, window: size };
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
if (key === "background_tasks") {
|
|
2257
|
+
if (!Array.isArray(found) || found.length > RADAR_MAX_BACKGROUND_TASKS)
|
|
2258
|
+
return void 0;
|
|
2259
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2260
|
+
const tasks = [];
|
|
2261
|
+
for (const item of found) {
|
|
2262
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
2263
|
+
return void 0;
|
|
2264
|
+
const task = item;
|
|
2265
|
+
if (Object.keys(task).sort().join(",") !== "id,phase" || typeof task.id !== "string" || !task.id || typeof task.phase !== "string" || !task.phase || ids.has(task.id))
|
|
2266
|
+
return void 0;
|
|
2267
|
+
ids.add(task.id);
|
|
2268
|
+
tasks.push({ id: task.id, phase: task.phase });
|
|
2269
|
+
}
|
|
2270
|
+
facts[key] = tasks;
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
if (key === "background_started_unix_ms") {
|
|
2274
|
+
if (!Number.isSafeInteger(found) || found < 0) return void 0;
|
|
2275
|
+
facts[key] = found;
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
if (typeof found !== "string") return void 0;
|
|
2279
|
+
facts[key] = found;
|
|
2280
|
+
}
|
|
2395
2281
|
return facts;
|
|
2396
2282
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
} catch {
|
|
2421
|
-
publication = unavailablePublication2();
|
|
2422
|
-
}
|
|
2423
|
-
const disposers = [];
|
|
2424
|
-
const keep = (disposer) => {
|
|
2425
|
-
if (typeof disposer === "function")
|
|
2426
|
-
disposers.push(disposer);
|
|
2427
|
-
};
|
|
2428
|
-
const on = (name, handler) => {
|
|
2429
|
-
keep(
|
|
2430
|
-
pi.on(name, (event, context) => {
|
|
2431
|
-
if (!stopped) handler(event, context);
|
|
2432
|
-
})
|
|
2433
|
-
);
|
|
2434
|
-
};
|
|
2283
|
+
var factsCodec = {
|
|
2284
|
+
parse: (value) => factsAt(value),
|
|
2285
|
+
bound: (snapshot) => boundedRadarFacts(snapshot)
|
|
2286
|
+
};
|
|
2287
|
+
function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
|
|
2288
|
+
if (!exactKeys(value, keys)) return void 0;
|
|
2289
|
+
const sequence = numberAt2(value, "sequence");
|
|
2290
|
+
const lease = numberAt2(value, "lease_ms");
|
|
2291
|
+
const observed = textAt(value, "observed_at");
|
|
2292
|
+
const snapshot = codec.parse(recordAt2(value, "snapshot"));
|
|
2293
|
+
if (sequence === void 0 || sequence < 1) return void 0;
|
|
2294
|
+
if (lease === void 0 || observed === void 0 || snapshot === void 0)
|
|
2295
|
+
return void 0;
|
|
2296
|
+
return { sequence, snapshot, lease_ms: lease, observed_at: observed };
|
|
2297
|
+
}
|
|
2298
|
+
function createChannelWriter(input) {
|
|
2299
|
+
const { store, client, scheduler, recordName, channel, publisher } = input;
|
|
2300
|
+
const codec = input.codec ?? executionCodec;
|
|
2301
|
+
let record = loadRecord();
|
|
2302
|
+
let desired;
|
|
2303
|
+
let running = false;
|
|
2304
|
+
let again = false;
|
|
2305
|
+
let recovering = false;
|
|
2435
2306
|
let stopped = false;
|
|
2436
|
-
let
|
|
2437
|
-
let
|
|
2438
|
-
let
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2307
|
+
let fenced = false;
|
|
2308
|
+
let retired = false;
|
|
2309
|
+
let pump;
|
|
2310
|
+
function blank(agentId) {
|
|
2311
|
+
return {
|
|
2312
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2313
|
+
registration_id: agentId,
|
|
2314
|
+
channel,
|
|
2315
|
+
publisher,
|
|
2316
|
+
sequence: 0
|
|
2317
|
+
};
|
|
2444
2318
|
}
|
|
2445
|
-
function
|
|
2446
|
-
const
|
|
2319
|
+
function loadRecord() {
|
|
2320
|
+
const value = store.read(recordName);
|
|
2321
|
+
const agentId = textAt(value, "registration_id");
|
|
2322
|
+
const storedPublisher = recordAt2(value, "publisher");
|
|
2323
|
+
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) || textAt(value, "channel") !== channel || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
|
|
2324
|
+
return blank("");
|
|
2325
|
+
const writer = recordAt2(value, "writer");
|
|
2326
|
+
const handle = textAt(writer, "handle");
|
|
2327
|
+
const generation = numberAt2(writer, "generation");
|
|
2328
|
+
if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
|
|
2329
|
+
return blank(agentId);
|
|
2330
|
+
const pending = pendingAt(recordAt2(value, "pending"), PENDING_KEYS, codec);
|
|
2331
|
+
const acceptedValue = recordAt2(value, "accepted");
|
|
2332
|
+
const accepted = pendingAt(acceptedValue, ACCEPTED_KEYS, codec);
|
|
2333
|
+
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
2447
2334
|
return {
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2335
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2336
|
+
registration_id: agentId,
|
|
2337
|
+
channel,
|
|
2338
|
+
publisher,
|
|
2339
|
+
...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
|
|
2340
|
+
...textAt(value, "retired_at") !== void 0 ? { retired_at: textAt(value, "retired_at") } : {},
|
|
2341
|
+
sequence: numberAt2(value, "sequence") ?? 0,
|
|
2342
|
+
...pending !== void 0 ? { pending } : {},
|
|
2343
|
+
...accepted !== void 0 && sentAt !== void 0 ? { accepted: { ...accepted, sent_at: sentAt } } : {},
|
|
2344
|
+
...textAt(value, "diagnostic") !== void 0 ? { diagnostic: textAt(value, "diagnostic") } : {}
|
|
2451
2345
|
};
|
|
2452
2346
|
}
|
|
2453
|
-
function
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2347
|
+
function note(message) {
|
|
2348
|
+
if (record.diagnostic !== void 0) return;
|
|
2349
|
+
record.diagnostic = message.slice(0, 256);
|
|
2350
|
+
save();
|
|
2351
|
+
}
|
|
2352
|
+
function save() {
|
|
2353
|
+
store.write(recordName, record);
|
|
2354
|
+
}
|
|
2355
|
+
function sameSnapshot(left, right) {
|
|
2356
|
+
return JSON.stringify(codec.bound(left)) === JSON.stringify(codec.bound(right));
|
|
2357
|
+
}
|
|
2358
|
+
function fence(message) {
|
|
2359
|
+
fenced = true;
|
|
2360
|
+
stopPump();
|
|
2361
|
+
note(`channel ${channel} stopped: ${message}`);
|
|
2362
|
+
}
|
|
2363
|
+
function startPump() {
|
|
2364
|
+
if (pump !== void 0 || stopped || fenced || retired) return;
|
|
2365
|
+
pump = scheduler.setInterval(() => kick(), input.heartbeatMs);
|
|
2366
|
+
pump.unref?.();
|
|
2367
|
+
}
|
|
2368
|
+
function stopPump() {
|
|
2369
|
+
if (pump === void 0) return;
|
|
2370
|
+
scheduler.clearInterval(pump);
|
|
2371
|
+
pump = void 0;
|
|
2372
|
+
}
|
|
2373
|
+
async function ensureWriter() {
|
|
2374
|
+
if (record.writer !== void 0) return true;
|
|
2375
|
+
const agentId = await input.subject(recovering);
|
|
2376
|
+
if (agentId === void 0) return false;
|
|
2377
|
+
if (record.registration_id !== agentId) {
|
|
2378
|
+
record = blank(agentId);
|
|
2379
|
+
save();
|
|
2380
|
+
}
|
|
2381
|
+
const answer = await client.acquire({
|
|
2382
|
+
registration_id: agentId,
|
|
2383
|
+
channel,
|
|
2384
|
+
publisher
|
|
2385
|
+
});
|
|
2386
|
+
if (answer.ok === false) {
|
|
2387
|
+
if (answer.code === "not_found") {
|
|
2388
|
+
recovering = true;
|
|
2389
|
+
return false;
|
|
2390
|
+
}
|
|
2391
|
+
if (answer.code === "refused" || answer.code === "bad_params")
|
|
2392
|
+
fence(answer.message);
|
|
2393
|
+
return false;
|
|
2459
2394
|
}
|
|
2395
|
+
recovering = false;
|
|
2396
|
+
record.writer = {
|
|
2397
|
+
handle: answer.value.handle,
|
|
2398
|
+
generation: answer.value.generation
|
|
2399
|
+
};
|
|
2400
|
+
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
2401
|
+
save();
|
|
2402
|
+
startPump();
|
|
2403
|
+
return true;
|
|
2460
2404
|
}
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
const
|
|
2466
|
-
if (
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2405
|
+
function heartbeatDue() {
|
|
2406
|
+
return record.accepted !== void 0 && scheduler.now() - record.accepted.sent_at >= input.heartbeatMs;
|
|
2407
|
+
}
|
|
2408
|
+
async function send(pending) {
|
|
2409
|
+
const writer = record.writer;
|
|
2410
|
+
if (writer === void 0) return;
|
|
2411
|
+
const answer = await client.publish({
|
|
2412
|
+
registration_id: record.registration_id,
|
|
2413
|
+
channel,
|
|
2414
|
+
writer_handle: writer.handle,
|
|
2415
|
+
sequence: pending.sequence,
|
|
2416
|
+
lease_ms: pending.lease_ms,
|
|
2417
|
+
observed_at: pending.observed_at,
|
|
2418
|
+
snapshot: pending.snapshot
|
|
2419
|
+
});
|
|
2420
|
+
if (answer.ok === true) {
|
|
2421
|
+
recovering = false;
|
|
2422
|
+
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
2423
|
+
record.accepted = { ...pending, sent_at: scheduler.now() };
|
|
2424
|
+
record.pending = void 0;
|
|
2425
|
+
save();
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
if (answer.code === "refused" || answer.code === "bad_params") {
|
|
2429
|
+
fence(answer.message);
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
if (answer.code === "not_found") {
|
|
2433
|
+
record.writer = void 0;
|
|
2434
|
+
recovering = true;
|
|
2435
|
+
save();
|
|
2436
|
+
return;
|
|
2472
2437
|
}
|
|
2473
2438
|
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2439
|
+
async function stage(snapshot) {
|
|
2440
|
+
const pending = {
|
|
2441
|
+
sequence: record.sequence + 1,
|
|
2442
|
+
snapshot: codec.bound(snapshot),
|
|
2443
|
+
lease_ms: input.leaseMs,
|
|
2444
|
+
observed_at: new Date(scheduler.now()).toISOString()
|
|
2445
|
+
};
|
|
2446
|
+
record.pending = pending;
|
|
2447
|
+
save();
|
|
2448
|
+
await send(pending);
|
|
2481
2449
|
}
|
|
2482
|
-
function
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
if (
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2450
|
+
async function drainOnce() {
|
|
2451
|
+
if (fenced || retired) return;
|
|
2452
|
+
if (!await ensureWriter()) return;
|
|
2453
|
+
if (fenced) return;
|
|
2454
|
+
if (record.pending !== void 0) {
|
|
2455
|
+
await send(record.pending);
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (desired !== void 0 && (record.accepted === void 0 || !sameSnapshot(desired, record.accepted.snapshot))) {
|
|
2459
|
+
await stage(desired);
|
|
2460
|
+
return;
|
|
2491
2461
|
}
|
|
2462
|
+
if (heartbeatDue() && record.accepted !== void 0)
|
|
2463
|
+
await stage(record.accepted.snapshot);
|
|
2492
2464
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
pi.events?.on(options.questionnaireEvent, (event) => {
|
|
2512
|
-
if (!rootSession || typeof event !== "object" || event === null)
|
|
2513
|
-
return;
|
|
2514
|
-
const value = event;
|
|
2515
|
-
if (typeof value.active !== "boolean") return;
|
|
2516
|
-
questionnaire = value.active ? dialogLabel(event) || "questionnaire" : void 0;
|
|
2517
|
-
emit();
|
|
2518
|
-
})
|
|
2519
|
-
);
|
|
2520
|
-
on("session_start", (_event, context) => {
|
|
2521
|
-
rootSession = true;
|
|
2522
|
-
reportSession(context);
|
|
2523
|
-
publishFacts(context);
|
|
2524
|
-
publication.reopen();
|
|
2525
|
-
active = context?.isIdle?.() === false;
|
|
2526
|
-
emit();
|
|
2527
|
-
});
|
|
2528
|
-
on("agent_start", () => {
|
|
2529
|
-
if (!rootSession) return;
|
|
2530
|
-
active = true;
|
|
2531
|
-
emit();
|
|
2532
|
-
});
|
|
2533
|
-
on("agent_settled", (event, context) => {
|
|
2534
|
-
if (!rootSession) return;
|
|
2535
|
-
if (event?.aborted === true) lastOutcome = { result: "aborted" };
|
|
2536
|
-
if (context?.isIdle?.() === true) active = false;
|
|
2537
|
-
emit();
|
|
2538
|
-
});
|
|
2539
|
-
on("message_end", (event) => {
|
|
2540
|
-
if (!rootSession) return;
|
|
2541
|
-
const { stopReason } = assistantStop(event);
|
|
2542
|
-
if (stopReason === "error") {
|
|
2543
|
-
lastOutcome = { result: "error" };
|
|
2544
|
-
} else if (stopReason === "aborted") {
|
|
2545
|
-
lastOutcome = { result: "aborted" };
|
|
2546
|
-
} else if (stopReason === "stop") {
|
|
2547
|
-
lastOutcome = { result: "finished" };
|
|
2548
|
-
} else {
|
|
2549
|
-
return;
|
|
2465
|
+
async function drain() {
|
|
2466
|
+
if (running || stopped || fenced || retired) return;
|
|
2467
|
+
running = true;
|
|
2468
|
+
try {
|
|
2469
|
+
await drainOnce();
|
|
2470
|
+
} catch (error) {
|
|
2471
|
+
fenced = true;
|
|
2472
|
+
stopPump();
|
|
2473
|
+
try {
|
|
2474
|
+
note(`channel ${channel} storage failure: ${String(error).slice(0, 180)}`);
|
|
2475
|
+
} catch {
|
|
2476
|
+
}
|
|
2477
|
+
} finally {
|
|
2478
|
+
running = false;
|
|
2479
|
+
if (again && !stopped && !fenced && !retired) {
|
|
2480
|
+
again = false;
|
|
2481
|
+
kick();
|
|
2482
|
+
}
|
|
2550
2483
|
}
|
|
2551
|
-
emit();
|
|
2552
|
-
});
|
|
2553
|
-
on("session_shutdown", async () => {
|
|
2554
|
-
suspend();
|
|
2555
|
-
await publication.presence.release();
|
|
2556
|
-
});
|
|
2557
|
-
function suspend() {
|
|
2558
|
-
if (stopped) return;
|
|
2559
|
-
rootSession = false;
|
|
2560
|
-
active = false;
|
|
2561
|
-
questionnaire = void 0;
|
|
2562
|
-
permissionPrompts.clear();
|
|
2563
|
-
lastOutcome = void 0;
|
|
2564
|
-
lastPublished = void 0;
|
|
2565
|
-
publication.stop();
|
|
2566
2484
|
}
|
|
2567
|
-
function
|
|
2568
|
-
if (stopped) return;
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2485
|
+
function kick() {
|
|
2486
|
+
if (stopped || fenced || retired) return;
|
|
2487
|
+
startPump();
|
|
2488
|
+
if (running) {
|
|
2489
|
+
again = true;
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
void drain().catch(() => void 0);
|
|
2572
2493
|
}
|
|
2573
2494
|
return {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2495
|
+
update(snapshot) {
|
|
2496
|
+
if (stopped || fenced || retired) return;
|
|
2497
|
+
desired = snapshot;
|
|
2498
|
+
kick();
|
|
2499
|
+
},
|
|
2500
|
+
retire() {
|
|
2501
|
+
if (stopped || fenced || retired) return;
|
|
2502
|
+
retired = true;
|
|
2503
|
+
stopPump();
|
|
2504
|
+
const writer = record.writer;
|
|
2505
|
+
if (writer === void 0) return;
|
|
2506
|
+
const agentId = record.registration_id;
|
|
2507
|
+
try {
|
|
2508
|
+
void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
|
|
2509
|
+
if (answer.ok === true) {
|
|
2510
|
+
try {
|
|
2511
|
+
record.retired_at = new Date(scheduler.now()).toISOString();
|
|
2512
|
+
save();
|
|
2513
|
+
} catch {
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}).catch(() => void 0);
|
|
2517
|
+
} catch {
|
|
2518
|
+
}
|
|
2519
|
+
},
|
|
2520
|
+
stop() {
|
|
2521
|
+
stopped = true;
|
|
2522
|
+
stopPump();
|
|
2523
|
+
},
|
|
2524
|
+
reopen() {
|
|
2525
|
+
stopped = false;
|
|
2526
|
+
if (desired !== void 0 || record.pending !== void 0) kick();
|
|
2527
|
+
},
|
|
2528
|
+
diagnostic: () => record.diagnostic
|
|
2578
2529
|
};
|
|
2579
2530
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
provedRead: () => provedRead
|
|
2586
|
-
});
|
|
2587
|
-
var INSPECTION_LINES = 80;
|
|
2588
|
-
var INSPECTION_OUTPUT_BYTES = 16 * 1024;
|
|
2589
|
-
async function provedRead(client, target) {
|
|
2590
|
-
const read = await client.agentGet(target.agentId);
|
|
2591
|
-
return read.ok === true && read.value.verification === "verified" && read.value.location?.pane === target.paneId && (target.session === void 0 || read.value.contextSession === target.session) ? read.value : void 0;
|
|
2592
|
-
}
|
|
2593
|
-
async function inspectRadarChild(client, target) {
|
|
2594
|
-
const before = await provedRead(client, target);
|
|
2595
|
-
if (before === void 0)
|
|
2596
|
-
throw new Error("Inspection target identity did not match");
|
|
2597
|
-
const output = await client.output({
|
|
2598
|
-
pane_id: target.paneId,
|
|
2599
|
-
source: "recent_unwrapped",
|
|
2600
|
-
lines: INSPECTION_LINES
|
|
2601
|
-
});
|
|
2602
|
-
if (output.ok === false)
|
|
2603
|
-
throw new Error(
|
|
2604
|
-
`Inspection output unavailable (${output.code}: ${output.message})`
|
|
2605
|
-
);
|
|
2606
|
-
const foreground = await client.processInfo({ pane_id: target.paneId });
|
|
2607
|
-
const after = await provedRead(client, target);
|
|
2608
|
-
if (after === void 0)
|
|
2609
|
-
throw new Error("Inspection target changed during capture");
|
|
2610
|
-
const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
|
|
2611
|
-
const bounded2 = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
|
|
2612
|
-
return Object.freeze({
|
|
2613
|
-
capturedAt: Date.now(),
|
|
2614
|
-
recentOutputTruncated: output.value.truncated || bounded2.truncated,
|
|
2615
|
-
...bounded2.text ? { recentOutput: bounded2.text } : {},
|
|
2616
|
-
...foreground.ok === true ? { foreground: foreground.value } : {},
|
|
2617
|
-
...after.activity !== void 0 ? { activity: after.activity } : {}
|
|
2618
|
-
});
|
|
2619
|
-
}
|
|
2620
|
-
function utf8Tail(value, maxBytes) {
|
|
2621
|
-
const bytes = Buffer.from(value);
|
|
2622
|
-
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
2623
|
-
let start = bytes.length - maxBytes;
|
|
2624
|
-
while (start < bytes.length && (bytes[start] & 192) === 128) start++;
|
|
2625
|
-
return { text: bytes.subarray(start).toString(), truncated: true };
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
// extension/radar-launch.ts
|
|
2629
|
-
var radar_launch_exports = {};
|
|
2630
|
-
__export(radar_launch_exports, {
|
|
2631
|
-
launchRadarWorker: () => launchRadarWorker,
|
|
2632
|
-
observeRadarChild: () => observeRadarChild,
|
|
2633
|
-
readProcessGone: () => readProcessGone,
|
|
2634
|
-
relaunchRefusalForChild: () => relaunchRefusalForChild
|
|
2635
|
-
});
|
|
2636
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
2637
|
-
|
|
2638
|
-
// extension/radar-location.ts
|
|
2639
|
-
var radar_location_exports = {};
|
|
2640
|
-
__export(radar_location_exports, {
|
|
2641
|
-
radarLocalPane: () => radarLocalPane,
|
|
2642
|
-
radarPaneHandle: () => radarPaneHandle,
|
|
2643
|
-
resolveRadarProcessLocation: () => resolveRadarProcessLocation,
|
|
2644
|
-
tmuxServerName: () => tmuxServerName
|
|
2645
|
-
});
|
|
2646
|
-
import { basename } from "node:path";
|
|
2647
|
-
function radarLocalPane(env) {
|
|
2648
|
-
const pane = env.TMUX_PANE?.trim();
|
|
2649
|
-
return pane && /^%\d+$/.test(pane) ? pane : void 0;
|
|
2650
|
-
}
|
|
2651
|
-
function radarPaneHandle(qualified) {
|
|
2652
|
-
return qualified.slice(qualified.lastIndexOf(":") + 1);
|
|
2653
|
-
}
|
|
2654
|
-
function tmuxServerName(env) {
|
|
2655
|
-
const socket = env.TMUX?.trim().split(",")[0];
|
|
2656
|
-
return socket ? basename(socket) || void 0 : void 0;
|
|
2657
|
-
}
|
|
2658
|
-
function qualifiedOwnPane(env, backend, instance) {
|
|
2659
|
-
if (backend !== "tmux")
|
|
2660
|
-
return {
|
|
2661
|
-
absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
|
|
2662
|
-
};
|
|
2663
|
-
if (instance === void 0 || instance === "")
|
|
2664
|
-
return {
|
|
2665
|
-
pending: "the daemon did not name the tmux server it addresses"
|
|
2666
|
-
};
|
|
2667
|
-
const server = tmuxServerName(env);
|
|
2668
|
-
if (server === void 0)
|
|
2669
|
-
return {
|
|
2670
|
-
absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
|
|
2671
|
-
};
|
|
2672
|
-
if (server !== instance)
|
|
2673
|
-
return {
|
|
2674
|
-
absent: `this process is attached to tmux server ${server}, not to ${instance}`
|
|
2675
|
-
};
|
|
2676
|
-
const pane = radarLocalPane(env);
|
|
2677
|
-
if (pane === void 0)
|
|
2678
|
-
return {
|
|
2679
|
-
absent: "this process's own environment names no tmux pane of that server"
|
|
2680
|
-
};
|
|
2681
|
-
return { pane: `tmux:${instance}:${pane}` };
|
|
2682
|
-
}
|
|
2683
|
-
function observedContainment(inventory, paneId) {
|
|
2684
|
-
const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
|
|
2685
|
-
if (rows.length === 0) return void 0;
|
|
2686
|
-
if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
|
|
2687
|
-
const row = rows[0];
|
|
2688
|
-
const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
|
|
2689
|
-
if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
|
|
2690
|
-
return {
|
|
2691
|
-
ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
|
|
2692
|
-
};
|
|
2693
|
-
return { pane: row };
|
|
2694
|
-
}
|
|
2695
|
-
async function resolveRadarProcessLocation(client, env = process.env) {
|
|
2696
|
-
let ready;
|
|
2531
|
+
function createPresenceReporter(input) {
|
|
2532
|
+
const { store, client, scheduler, recordName } = input;
|
|
2533
|
+
let state;
|
|
2534
|
+
let standing;
|
|
2535
|
+
let diagnostic;
|
|
2697
2536
|
try {
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2537
|
+
const saved = store.read(recordName);
|
|
2538
|
+
if (saved?.version === 2 && isUuid(saved.claim_id) && isUuid(saved.session) && Array.isArray(saved.releases))
|
|
2539
|
+
state = saved;
|
|
2540
|
+
} catch (error) {
|
|
2541
|
+
diagnostic = `presence storage unavailable: ${String(error)}`;
|
|
2542
|
+
}
|
|
2543
|
+
let timer;
|
|
2544
|
+
let stopped = false;
|
|
2545
|
+
let running;
|
|
2546
|
+
function save() {
|
|
2547
|
+
try {
|
|
2548
|
+
if (state) store.write(recordName, state);
|
|
2549
|
+
return true;
|
|
2550
|
+
} catch (error) {
|
|
2551
|
+
diagnostic = `presence storage unavailable: ${String(error)}`;
|
|
2552
|
+
return false;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
async function releasePending() {
|
|
2556
|
+
if (!state) return;
|
|
2557
|
+
const registration = await input.registration();
|
|
2558
|
+
if (!registration?.process) return;
|
|
2559
|
+
for (const release of [...state.releases]) {
|
|
2560
|
+
const answer = await client.releasePresence({ ...release, process: registration.process });
|
|
2561
|
+
if (!answer.ok) return;
|
|
2562
|
+
state.releases = state.releases.filter((r) => r.claim_id !== release.claim_id);
|
|
2563
|
+
save();
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
function schedule() {
|
|
2567
|
+
if (stopped || timer) return;
|
|
2568
|
+
timer = scheduler.setTimeout(() => {
|
|
2569
|
+
timer = void 0;
|
|
2570
|
+
void ensure();
|
|
2571
|
+
}, input.heartbeatMs);
|
|
2572
|
+
timer.unref?.();
|
|
2573
|
+
}
|
|
2574
|
+
async function ensure() {
|
|
2575
|
+
if (running) return running;
|
|
2576
|
+
if (stopped || !state) return void 0;
|
|
2577
|
+
const claim = state;
|
|
2578
|
+
if (!save()) return void 0;
|
|
2579
|
+
running = (async () => {
|
|
2580
|
+
await releasePending();
|
|
2581
|
+
const registration = await input.registration();
|
|
2582
|
+
if (!registration?.process || state !== claim || stopped) return void 0;
|
|
2583
|
+
const answer = await client.presence({
|
|
2584
|
+
...registration,
|
|
2585
|
+
process: registration.process,
|
|
2586
|
+
session_id: claim.session,
|
|
2587
|
+
claim_id: claim.claim_id,
|
|
2588
|
+
...claim.registration_id ? { registration_id: claim.registration_id } : {},
|
|
2589
|
+
...claim.subject_id ? { subject_id: claim.subject_id } : {}
|
|
2590
|
+
});
|
|
2591
|
+
if (state !== claim) return void 0;
|
|
2592
|
+
if (!answer.ok) {
|
|
2593
|
+
if (!["absent", "timeout", "transport"].includes(answer.code)) diagnostic = answer.message;
|
|
2594
|
+
return void 0;
|
|
2595
|
+
}
|
|
2596
|
+
claim.registration_id = answer.value.registration_id;
|
|
2597
|
+
claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
|
|
2598
|
+
standing = answer.value.standing === "granted" ? { state: "managed", subject_id: claim.subject_id } : answer.value.standing === "duplicate" ? { state: "duplicate" } : answer.value.standing === "pending" ? { state: "pending" } : { state: "unmanaged" };
|
|
2599
|
+
diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
|
|
2600
|
+
save();
|
|
2601
|
+
return claim.registration_id;
|
|
2602
|
+
})();
|
|
2603
|
+
try {
|
|
2604
|
+
return await running;
|
|
2605
|
+
} catch (error) {
|
|
2606
|
+
diagnostic = `presence unavailable: ${String(error)}`;
|
|
2607
|
+
return void 0;
|
|
2608
|
+
} finally {
|
|
2609
|
+
running = void 0;
|
|
2610
|
+
if (!stopped && state !== claim) void ensure();
|
|
2611
|
+
else schedule();
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
return {
|
|
2615
|
+
report(session) {
|
|
2616
|
+
const releases = [...state?.releases ?? []];
|
|
2617
|
+
if (state?.registration_id && !releases.some((release) => release.claim_id === state.claim_id)) {
|
|
2618
|
+
releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
|
|
2619
|
+
}
|
|
2620
|
+
standing = void 0;
|
|
2621
|
+
state = { version: 2, session, claim_id: randomUUID2(), releases };
|
|
2622
|
+
save();
|
|
2623
|
+
stopped = false;
|
|
2624
|
+
void ensure();
|
|
2625
|
+
},
|
|
2626
|
+
ensure,
|
|
2627
|
+
async release() {
|
|
2628
|
+
const releasing = state;
|
|
2629
|
+
standing = void 0;
|
|
2630
|
+
stopped = true;
|
|
2631
|
+
if (timer) scheduler.clearTimeout(timer);
|
|
2632
|
+
timer = void 0;
|
|
2633
|
+
if (running) await running;
|
|
2634
|
+
if (state === releasing) standing = void 0;
|
|
2635
|
+
if (releasing?.registration_id && !releasing.releases.some((r) => r.claim_id === releasing.claim_id)) {
|
|
2636
|
+
releasing.releases.push({ registration_id: releasing.registration_id, claim_id: releasing.claim_id });
|
|
2637
|
+
if (!save()) return;
|
|
2638
|
+
}
|
|
2639
|
+
await releasePending();
|
|
2640
|
+
},
|
|
2641
|
+
stop() {
|
|
2642
|
+
stopped = true;
|
|
2643
|
+
if (timer) scheduler.clearTimeout(timer);
|
|
2644
|
+
timer = void 0;
|
|
2645
|
+
},
|
|
2646
|
+
reopen() {
|
|
2647
|
+
stopped = false;
|
|
2648
|
+
void ensure();
|
|
2649
|
+
},
|
|
2650
|
+
management: () => standing,
|
|
2651
|
+
diagnostic: () => diagnostic
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
var STORE_IDENTITY = /* @__PURE__ */ new WeakMap();
|
|
2655
|
+
function storeIdentity(store) {
|
|
2656
|
+
const known = STORE_IDENTITY.get(store);
|
|
2657
|
+
if (known !== void 0) return known;
|
|
2658
|
+
const created = randomUUID2();
|
|
2659
|
+
STORE_IDENTITY.set(store, created);
|
|
2660
|
+
return created;
|
|
2661
|
+
}
|
|
2662
|
+
function createRadarPublication(options) {
|
|
2721
2663
|
try {
|
|
2722
|
-
|
|
2723
|
-
} catch {
|
|
2724
|
-
return
|
|
2725
|
-
state: "pending",
|
|
2726
|
-
reason: "the daemon's topology could not be read for this process's pane"
|
|
2727
|
-
};
|
|
2664
|
+
return createRadarPublicationInner(options);
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
return unavailablePublication(error);
|
|
2728
2667
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2668
|
+
}
|
|
2669
|
+
function unavailablePublication(error) {
|
|
2670
|
+
const diagnostic = `Radar publication unavailable${error ? `: ${String(error).slice(0, 180)}` : ""}`;
|
|
2671
|
+
const writer = {
|
|
2672
|
+
update: () => void 0,
|
|
2673
|
+
retire: () => void 0,
|
|
2674
|
+
stop: () => void 0,
|
|
2675
|
+
reopen: () => void 0,
|
|
2676
|
+
diagnostic: () => diagnostic
|
|
2677
|
+
};
|
|
2678
|
+
return {
|
|
2679
|
+
execution: writer,
|
|
2680
|
+
presence: {
|
|
2681
|
+
report: () => void 0,
|
|
2682
|
+
ensure: async () => void 0,
|
|
2683
|
+
release: async () => void 0,
|
|
2684
|
+
stop: () => void 0,
|
|
2685
|
+
reopen: () => void 0,
|
|
2686
|
+
management: () => void 0,
|
|
2687
|
+
diagnostic: () => diagnostic
|
|
2688
|
+
},
|
|
2689
|
+
facts: { ...writer, update: () => void 0 },
|
|
2690
|
+
assignment: () => writer,
|
|
2691
|
+
binding: () => void 0,
|
|
2692
|
+
subject: async () => void 0,
|
|
2693
|
+
retireAssignment: () => void 0,
|
|
2694
|
+
diagnostic: () => diagnostic,
|
|
2695
|
+
stop: () => void 0,
|
|
2696
|
+
reopen: () => void 0
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
function createRadarPublicationInner(options) {
|
|
2700
|
+
const dataRoot = options.dataRoot;
|
|
2701
|
+
const store = options.store ?? createPublicationStore(dataRoot);
|
|
2702
|
+
const scheduler = options.scheduler ?? systemScheduler;
|
|
2703
|
+
const client = options.client;
|
|
2704
|
+
const slot = options.slot ?? radarProcessSlot();
|
|
2705
|
+
const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
|
|
2706
|
+
const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
|
|
2707
|
+
const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
|
|
2708
|
+
const cached = slot.publications.get(cacheKey);
|
|
2709
|
+
if (cached !== void 0) {
|
|
2710
|
+
cached.reopen();
|
|
2711
|
+
return cached;
|
|
2712
|
+
}
|
|
2713
|
+
const registrationName = `registrations/${createHash("sha256").update(`${options.registration.source}\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
2714
|
+
const channelRecord = (channel, target) => `channels/${createHash("sha256").update(`${channel}\0${incarnation}\0${target ?? ""}`).digest("hex").slice(0, 32)}.json`;
|
|
2715
|
+
const presenceRecord = `presence/${createHash("sha256").update(`presence\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
2716
|
+
function readRegistration() {
|
|
2717
|
+
const value = store.read(registrationName);
|
|
2718
|
+
const registration = recordAt2(value, "registration");
|
|
2719
|
+
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || textAt(registration, "source") !== options.registration.source || textAt(registration, "incarnation") !== incarnation)
|
|
2720
|
+
return void 0;
|
|
2721
|
+
const storedAgentId = textAt(value, "registration_id");
|
|
2736
2722
|
return {
|
|
2737
|
-
|
|
2738
|
-
|
|
2723
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2724
|
+
registration,
|
|
2725
|
+
...storedAgentId !== void 0 && isUuid(storedAgentId) ? { registration_id: storedAgentId } : {}
|
|
2739
2726
|
};
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2727
|
+
}
|
|
2728
|
+
const stored = readRegistration();
|
|
2729
|
+
let processRecord = stored;
|
|
2730
|
+
let registrationDiagnostic;
|
|
2731
|
+
let registrationRecord;
|
|
2732
|
+
async function ensureRegistration() {
|
|
2733
|
+
if (processRecord !== void 0) return processRecord;
|
|
2734
|
+
registrationRecord ??= (async () => {
|
|
2735
|
+
const resolution = options.location ? await options.location() : { state: "absent", reason: "this process publishes no location" };
|
|
2736
|
+
const built = {
|
|
2737
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2738
|
+
registration: {
|
|
2739
|
+
...options.registration,
|
|
2740
|
+
...resolution.state === "resolved" ? { location: resolution.location } : {},
|
|
2741
|
+
incarnation,
|
|
2742
|
+
...options.birth !== void 0 ? { process: options.birth } : {}
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
try {
|
|
2746
|
+
store.write(registrationName, built);
|
|
2747
|
+
} catch (error) {
|
|
2748
|
+
registrationDiagnostic ??= `private storage unavailable: ${String(error)}`.slice(0, 256);
|
|
2749
|
+
return void 0;
|
|
2750
|
+
}
|
|
2751
|
+
registrationDiagnostic = void 0;
|
|
2752
|
+
processRecord = built;
|
|
2753
|
+
return built;
|
|
2754
|
+
})();
|
|
2755
|
+
try {
|
|
2756
|
+
return await registrationRecord;
|
|
2757
|
+
} finally {
|
|
2758
|
+
registrationRecord = void 0;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
let agentId = stored?.registration_id;
|
|
2762
|
+
let subjectReady;
|
|
2763
|
+
async function ensureSubject(recheck) {
|
|
2764
|
+
if (!recheck && agentId !== void 0) return agentId;
|
|
2765
|
+
subjectReady ??= (async () => {
|
|
2766
|
+
const ready = await client.ping();
|
|
2767
|
+
if (ready.ok === false) {
|
|
2768
|
+
if (ready.code !== "absent" && ready.code !== "timeout" && ready.code !== "transport")
|
|
2769
|
+
registrationDiagnostic ??= `daemon negotiation failed: ${ready.message}`.slice(0, 256);
|
|
2770
|
+
return void 0;
|
|
2771
|
+
}
|
|
2772
|
+
const record = await ensureRegistration();
|
|
2773
|
+
if (record === void 0) return void 0;
|
|
2774
|
+
const registered = await presence.ensure();
|
|
2775
|
+
if (registered === void 0) return void 0;
|
|
2776
|
+
agentId = registered;
|
|
2777
|
+
processRecord = { ...record, registration_id: agentId };
|
|
2778
|
+
store.write(registrationName, processRecord);
|
|
2779
|
+
if (options.binding !== void 0) writeBinding(options.binding);
|
|
2780
|
+
return agentId;
|
|
2781
|
+
})();
|
|
2782
|
+
try {
|
|
2783
|
+
return await subjectReady;
|
|
2784
|
+
} finally {
|
|
2785
|
+
subjectReady = void 0;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
function writeBinding(binding) {
|
|
2789
|
+
if (agentId === void 0) return;
|
|
2790
|
+
store.write(`bindings/${binding.key}.json`, {
|
|
2791
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2792
|
+
registration_id: agentId,
|
|
2793
|
+
incarnation,
|
|
2794
|
+
run: binding.run,
|
|
2795
|
+
owner: binding.owner,
|
|
2796
|
+
label: binding.label,
|
|
2797
|
+
observed_at: new Date(scheduler.now()).toISOString()
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
const publisher = (source, reportingOwner) => ({
|
|
2801
|
+
source,
|
|
2802
|
+
incarnation,
|
|
2803
|
+
...reportingOwner !== void 0 ? { reporting_owner: reportingOwner } : {}
|
|
2804
|
+
});
|
|
2805
|
+
const execution = createChannelWriter({
|
|
2806
|
+
store,
|
|
2807
|
+
client,
|
|
2808
|
+
scheduler,
|
|
2809
|
+
recordName: channelRecord("execution"),
|
|
2810
|
+
channel: "execution",
|
|
2811
|
+
publisher: publisher("herdsman-pi"),
|
|
2812
|
+
subject: ensureSubject,
|
|
2813
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2814
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2815
|
+
});
|
|
2816
|
+
const presence = createPresenceReporter({
|
|
2817
|
+
store,
|
|
2818
|
+
client,
|
|
2819
|
+
scheduler,
|
|
2820
|
+
recordName: presenceRecord,
|
|
2821
|
+
registration: async () => {
|
|
2822
|
+
const record = await ensureRegistration();
|
|
2823
|
+
if (!record) return void 0;
|
|
2824
|
+
const location = await options.location?.();
|
|
2825
|
+
const { location: _initialLocation, ...identity } = record.registration;
|
|
2826
|
+
return {
|
|
2827
|
+
...identity,
|
|
2828
|
+
...location?.state === "resolved" ? { location: location.location } : {},
|
|
2829
|
+
...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
|
|
2830
|
+
};
|
|
2831
|
+
},
|
|
2832
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2833
|
+
});
|
|
2834
|
+
const facts = createChannelWriter({
|
|
2835
|
+
store,
|
|
2836
|
+
client,
|
|
2837
|
+
scheduler,
|
|
2838
|
+
recordName: channelRecord("facts"),
|
|
2839
|
+
channel: "facts",
|
|
2840
|
+
publisher: publisher("pi"),
|
|
2841
|
+
codec: factsCodec,
|
|
2842
|
+
subject: ensureSubject,
|
|
2843
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2844
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2845
|
+
});
|
|
2846
|
+
const assignmentWriters = /* @__PURE__ */ new Map();
|
|
2847
|
+
const publication = {
|
|
2848
|
+
execution,
|
|
2849
|
+
presence,
|
|
2850
|
+
facts,
|
|
2851
|
+
assignment(target, reportingOwner) {
|
|
2852
|
+
const existing = assignmentWriters.get(target);
|
|
2853
|
+
if (existing !== void 0) return existing;
|
|
2854
|
+
try {
|
|
2855
|
+
const writer = createChannelWriter({
|
|
2856
|
+
store,
|
|
2857
|
+
client,
|
|
2858
|
+
scheduler,
|
|
2859
|
+
recordName: channelRecord("assignment", target),
|
|
2860
|
+
channel: "assignment",
|
|
2861
|
+
publisher: publisher("herdsman-owner", reportingOwner),
|
|
2862
|
+
codec: assignmentCodec,
|
|
2863
|
+
// The owner publishes about a subject it did not register: the exact
|
|
2864
|
+
// registration_id from the child's sidecar is the whole correlation.
|
|
2865
|
+
subject: async () => target,
|
|
2866
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2867
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2868
|
+
});
|
|
2869
|
+
assignmentWriters.set(target, writer);
|
|
2870
|
+
return writer;
|
|
2871
|
+
} catch (error) {
|
|
2872
|
+
registrationDiagnostic ??= `Radar assignment unavailable: ${String(error).slice(0, 180)}`;
|
|
2873
|
+
return unavailablePublication(error).execution;
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
binding(key) {
|
|
2877
|
+
let value;
|
|
2878
|
+
try {
|
|
2879
|
+
value = store.read(`bindings/${key}.json`);
|
|
2880
|
+
} catch (error) {
|
|
2881
|
+
registrationDiagnostic ??= `Radar binding unavailable: ${String(error).slice(0, 180)}`;
|
|
2882
|
+
return void 0;
|
|
2883
|
+
}
|
|
2884
|
+
const bound = textAt(value, "registration_id");
|
|
2885
|
+
const boundIncarnation = textAt(value, "incarnation");
|
|
2886
|
+
const run = textAt(value, "run");
|
|
2887
|
+
const owner = textAt(value, "owner");
|
|
2888
|
+
const label = textAt(value, "label");
|
|
2889
|
+
const observedAt = textAt(value, "observed_at");
|
|
2890
|
+
if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
|
|
2891
|
+
return void 0;
|
|
2892
|
+
return {
|
|
2893
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2894
|
+
registration_id: bound,
|
|
2895
|
+
incarnation: boundIncarnation,
|
|
2896
|
+
run,
|
|
2897
|
+
owner,
|
|
2898
|
+
label,
|
|
2899
|
+
observed_at: observedAt
|
|
2900
|
+
};
|
|
2901
|
+
},
|
|
2902
|
+
retireAssignment(targetAgentId) {
|
|
2903
|
+
assignmentWriters.get(targetAgentId)?.retire();
|
|
2904
|
+
},
|
|
2905
|
+
async subject() {
|
|
2906
|
+
const agentId2 = await ensureSubject(false);
|
|
2907
|
+
if (agentId2 === void 0) return void 0;
|
|
2908
|
+
const record = await ensureRegistration();
|
|
2909
|
+
if (record === void 0) return void 0;
|
|
2910
|
+
const currentLocation = await options.location?.();
|
|
2911
|
+
const location = currentLocation?.state === "resolved" ? currentLocation.location : void 0;
|
|
2912
|
+
const standing = presence.management();
|
|
2913
|
+
const subjectId = standing?.state === "managed" ? standing.subject_id : void 0;
|
|
2914
|
+
return {
|
|
2915
|
+
registration_id: agentId2,
|
|
2916
|
+
...subjectId !== void 0 ? { subject_id: subjectId } : {},
|
|
2917
|
+
...location !== void 0 ? { location } : {}
|
|
2918
|
+
};
|
|
2919
|
+
},
|
|
2920
|
+
diagnostic: () => registrationDiagnostic ?? execution.diagnostic() ?? presence.diagnostic() ?? facts.diagnostic(),
|
|
2921
|
+
stop() {
|
|
2922
|
+
for (const writer of assignmentWriters.values()) writer.stop();
|
|
2923
|
+
execution.stop();
|
|
2924
|
+
presence.stop();
|
|
2925
|
+
facts.stop();
|
|
2926
|
+
},
|
|
2927
|
+
reopen() {
|
|
2928
|
+
for (const writer of assignmentWriters.values()) writer.reopen();
|
|
2929
|
+
execution.reopen();
|
|
2930
|
+
presence.reopen();
|
|
2931
|
+
facts.reopen();
|
|
2751
2932
|
}
|
|
2752
2933
|
};
|
|
2934
|
+
slot.publications.set(cacheKey, publication);
|
|
2935
|
+
return publication;
|
|
2753
2936
|
}
|
|
2754
2937
|
|
|
2755
|
-
// extension/radar-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
}
|
|
2759
|
-
function trajectory(reason) {
|
|
2760
|
-
return `the daemon's current record of this launch's child is ${reason}`;
|
|
2761
|
-
}
|
|
2762
|
-
async function observeRadarChild(client, provenance) {
|
|
2763
|
-
if (provenance.registration_id === void 0)
|
|
2764
|
-
return unknownChild("not bound to a child by this launch");
|
|
2765
|
-
if (provenance.pane_id === void 0)
|
|
2766
|
-
return unknownChild("without a pane this launch recorded for it");
|
|
2767
|
-
const read = await client.spawnGet(provenance.request_id);
|
|
2768
|
-
if (read.ok === false)
|
|
2769
|
-
return unknownChild(
|
|
2770
|
-
`not readable (${read.code}: ${read.message})`
|
|
2771
|
-
);
|
|
2772
|
-
const topology = read.value;
|
|
2773
|
-
const edge = topology.edge;
|
|
2774
|
-
if (edge.bound === void 0 || edge.bound.registration_id === void 0)
|
|
2775
|
-
return unknownChild("not bound to a child by the daemon");
|
|
2776
|
-
if (edge.bound.registration_id !== provenance.registration_id)
|
|
2777
|
-
return unknownChild(
|
|
2778
|
-
`bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
|
|
2779
|
-
);
|
|
2780
|
-
if (edge.location?.pane === void 0)
|
|
2781
|
-
return unknownChild("recorded without a pane");
|
|
2782
|
-
if (edge.location.pane !== provenance.pane_id)
|
|
2783
|
-
return unknownChild(
|
|
2784
|
-
`recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
|
|
2785
|
-
);
|
|
2786
|
-
if (edge.exit_observation?.state !== "unknown")
|
|
2787
|
-
return unknownChild("recorded with an observed process exit");
|
|
2788
|
-
if (topology.freshness !== "fresh")
|
|
2789
|
-
return unknownChild(
|
|
2790
|
-
"on a pane the daemon's own inventory does not currently report"
|
|
2791
|
-
);
|
|
2792
|
-
if (topology.state !== "bound")
|
|
2793
|
-
return unknownChild(
|
|
2794
|
-
`not currently placed (state ${topology.state ?? "unreported"})`
|
|
2795
|
-
);
|
|
2796
|
-
const reply = await client.agentGet(edge.bound.registration_id);
|
|
2797
|
-
if (reply.ok === false)
|
|
2798
|
-
return unknownChild(
|
|
2799
|
-
`process verdict not readable (${reply.code}: ${reply.message})`
|
|
2800
|
-
);
|
|
2801
|
-
const { verification, location, contextSession } = reply.value;
|
|
2802
|
-
if (verification === "absent" || verification === "mismatched")
|
|
2803
|
-
return {
|
|
2804
|
-
kind: "gone",
|
|
2805
|
-
verification,
|
|
2806
|
-
diagnostic: trajectory(`the registered process is ${verification}`)
|
|
2807
|
-
};
|
|
2808
|
-
if (verification !== "verified")
|
|
2809
|
-
return unknownChild(`process verdict is ${verification}`);
|
|
2810
|
-
if (location?.pane !== provenance.pane_id)
|
|
2811
|
-
return unknownChild(
|
|
2812
|
-
`registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
|
|
2813
|
-
);
|
|
2814
|
-
if (provenance.session_id === void 0)
|
|
2815
|
-
return unknownChild("without a session this launch recorded for it");
|
|
2816
|
-
if (contextSession !== provenance.session_id)
|
|
2817
|
-
return unknownChild(
|
|
2818
|
-
`publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
|
|
2819
|
-
);
|
|
2938
|
+
// extension/radar-execution.ts
|
|
2939
|
+
var RADAR_SUBJECT_SOURCE = "herdsman-pi";
|
|
2940
|
+
function radarLocalRegistration() {
|
|
2820
2941
|
return {
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
request_id: provenance.request_id,
|
|
2824
|
-
registration_id: edge.bound.registration_id,
|
|
2825
|
-
source: edge.bound.source,
|
|
2826
|
-
incarnation: edge.bound.incarnation,
|
|
2827
|
-
pane_id: edge.location.pane
|
|
2828
|
-
}
|
|
2942
|
+
source: RADAR_SUBJECT_SOURCE,
|
|
2943
|
+
incarnation: radarProcessIncarnation()
|
|
2829
2944
|
};
|
|
2830
2945
|
}
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
return verification === "absent" || verification === "mismatched" ? verification : void 0;
|
|
2836
|
-
}
|
|
2837
|
-
async function relaunchRefusalForChild(client, agentId) {
|
|
2838
|
-
const read = await client.agentGet(agentId);
|
|
2839
|
-
if (read.ok === false)
|
|
2840
|
-
return `the child's process cannot be read (${read.code}: ${read.message})`;
|
|
2841
|
-
const { verification } = read.value;
|
|
2842
|
-
if (verification === "absent" || verification === "mismatched") return void 0;
|
|
2843
|
-
return `the child's registered process is ${verification === "verified" ? "still running" : "not proved gone"}`;
|
|
2946
|
+
function dialogLabel(event) {
|
|
2947
|
+
if (typeof event !== "object" || event === null) return "";
|
|
2948
|
+
const label = event.label;
|
|
2949
|
+
return typeof label === "string" && label.trim() ? label.trim().slice(0, 256) : "";
|
|
2844
2950
|
}
|
|
2845
|
-
function
|
|
2846
|
-
|
|
2951
|
+
function assistantStop(event) {
|
|
2952
|
+
const message = typeof event === "object" && event !== null && "message" in event ? event.message : event;
|
|
2953
|
+
if (typeof message !== "object" || message === null) return {};
|
|
2954
|
+
const value = message;
|
|
2955
|
+
return value.role === "assistant" ? { stopReason: value.stopReason } : {};
|
|
2847
2956
|
}
|
|
2848
|
-
function
|
|
2957
|
+
function unavailablePublication2() {
|
|
2958
|
+
const writer = {
|
|
2959
|
+
update: () => void 0,
|
|
2960
|
+
retire: () => void 0,
|
|
2961
|
+
stop: () => void 0,
|
|
2962
|
+
reopen: () => void 0,
|
|
2963
|
+
diagnostic: () => "Radar private storage is unavailable"
|
|
2964
|
+
};
|
|
2849
2965
|
return {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2966
|
+
execution: writer,
|
|
2967
|
+
facts: { ...writer, update: () => void 0 },
|
|
2968
|
+
presence: {
|
|
2969
|
+
report: () => void 0,
|
|
2970
|
+
stop: () => void 0,
|
|
2971
|
+
reopen: () => void 0,
|
|
2972
|
+
management: () => void 0,
|
|
2973
|
+
ensure: async () => void 0,
|
|
2974
|
+
release: async () => void 0,
|
|
2975
|
+
diagnostic: () => "Radar private storage is unavailable"
|
|
2976
|
+
},
|
|
2977
|
+
assignment: () => writer,
|
|
2978
|
+
binding: () => void 0,
|
|
2979
|
+
subject: async () => void 0,
|
|
2980
|
+
retireAssignment: () => void 0,
|
|
2981
|
+
diagnostic: writer.diagnostic,
|
|
2982
|
+
stop: () => void 0,
|
|
2983
|
+
reopen: () => void 0
|
|
2853
2984
|
};
|
|
2854
2985
|
}
|
|
2855
|
-
function
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
"
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
"unknown_method",
|
|
2870
|
-
"refused",
|
|
2871
|
-
"not_found",
|
|
2872
|
-
"busy",
|
|
2873
|
-
"backend_unavailable"
|
|
2874
|
-
];
|
|
2875
|
-
function spawnFailure(result, requestId) {
|
|
2876
|
-
return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
|
|
2986
|
+
function factsFrom(context) {
|
|
2987
|
+
const facts = {};
|
|
2988
|
+
const model = context?.model;
|
|
2989
|
+
if (typeof model?.id === "string" && model.id.length > 0)
|
|
2990
|
+
facts.model = model.id;
|
|
2991
|
+
if (typeof model?.provider === "string" && model.provider.length > 0)
|
|
2992
|
+
facts.provider = model.provider;
|
|
2993
|
+
const thinking = context?.thinkingLevel;
|
|
2994
|
+
if (typeof thinking === "string" && thinking.length > 0)
|
|
2995
|
+
facts.thinking = thinking;
|
|
2996
|
+
const name = context?.sessionManager?.getSessionName?.();
|
|
2997
|
+
if (typeof name === "string" && name.trim().length > 0)
|
|
2998
|
+
facts.session_name = name.trim();
|
|
2999
|
+
return facts;
|
|
2877
3000
|
}
|
|
2878
|
-
|
|
2879
|
-
const
|
|
2880
|
-
const
|
|
2881
|
-
const
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3001
|
+
function createRadarExecutionAdapter(pi, options) {
|
|
3002
|
+
const slot = options.slot ?? radarProcessSlot();
|
|
3003
|
+
const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
|
|
3004
|
+
const managed = options.managedBinding;
|
|
3005
|
+
const birth = "birth" in options ? options.birth : readProcessBirth();
|
|
3006
|
+
let publication;
|
|
3007
|
+
try {
|
|
3008
|
+
publication = options.publication ?? createRadarPublication({
|
|
3009
|
+
dataRoot: options.dataRoot,
|
|
3010
|
+
store: options.store,
|
|
3011
|
+
client: options.client,
|
|
3012
|
+
scheduler: options.scheduler,
|
|
3013
|
+
slot,
|
|
3014
|
+
incarnation,
|
|
3015
|
+
birth,
|
|
3016
|
+
registration: {
|
|
3017
|
+
source: RADAR_SUBJECT_SOURCE,
|
|
3018
|
+
...managed !== void 0 ? { owner: managed.owner, run: managed.run, label: managed.label } : {}
|
|
3019
|
+
},
|
|
3020
|
+
...options.location !== void 0 ? { location: options.location } : {},
|
|
3021
|
+
...managed !== void 0 ? { binding: managed } : {},
|
|
3022
|
+
...options.launchRegistrationId !== void 0 ? { launchRegistrationId: options.launchRegistrationId } : {}
|
|
3023
|
+
});
|
|
3024
|
+
} catch {
|
|
3025
|
+
publication = unavailablePublication2();
|
|
3026
|
+
}
|
|
3027
|
+
const disposers = [];
|
|
3028
|
+
const keep = (disposer) => {
|
|
3029
|
+
if (typeof disposer === "function")
|
|
3030
|
+
disposers.push(disposer);
|
|
3031
|
+
};
|
|
3032
|
+
const on = (name, handler) => {
|
|
3033
|
+
keep(
|
|
3034
|
+
pi.on(name, (event, context) => {
|
|
3035
|
+
if (!stopped) handler(event, context);
|
|
3036
|
+
})
|
|
2895
3037
|
);
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
if (spawned.ok === false) {
|
|
2907
|
-
const refusal = spawnFailure(spawned, launch.request_id);
|
|
2908
|
-
if (refusal.kind === "refused") return refusal;
|
|
3038
|
+
};
|
|
3039
|
+
let stopped = false;
|
|
3040
|
+
let rootSession = false;
|
|
3041
|
+
let active = false;
|
|
3042
|
+
let questionnaire;
|
|
3043
|
+
const permissionPrompts = /* @__PURE__ */ new Set();
|
|
3044
|
+
let lastOutcome;
|
|
3045
|
+
let lastPublished;
|
|
3046
|
+
function blocked() {
|
|
3047
|
+
return questionnaire !== void 0 || permissionPrompts.size > 0;
|
|
2909
3048
|
}
|
|
2910
|
-
|
|
2911
|
-
const
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3049
|
+
function currentSnapshot() {
|
|
3050
|
+
const reason = questionnaire ?? (permissionPrompts.size > 0 ? "permission" : void 0);
|
|
3051
|
+
return {
|
|
3052
|
+
activity: blocked() ? "blocked" : active ? "working" : "idle",
|
|
3053
|
+
...reason !== void 0 ? { waiting_reason: reason } : {},
|
|
3054
|
+
...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
|
|
3055
|
+
};
|
|
2916
3056
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
...signal !== void 0 ? { signal } : {}
|
|
2921
|
-
});
|
|
2922
|
-
if (bound.kind !== "bound") return bound;
|
|
2923
|
-
const agentId = bound.registration_id;
|
|
2924
|
-
if (launch.onBound !== void 0) {
|
|
3057
|
+
function reportSession(context) {
|
|
3058
|
+
const session = context?.sessionManager?.getSessionId?.();
|
|
3059
|
+
if (!isUuid(session)) return;
|
|
2925
3060
|
try {
|
|
2926
|
-
|
|
2927
|
-
} catch
|
|
2928
|
-
return unresolvedLaunch(
|
|
2929
|
-
launch.request_id,
|
|
2930
|
-
`the daemon's binding of the child ${agentId} could not be recorded (${String(error)})`
|
|
2931
|
-
);
|
|
3061
|
+
publication.presence.report(session);
|
|
3062
|
+
} catch {
|
|
2932
3063
|
}
|
|
2933
3064
|
}
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
paneId: radarPaneHandle(bound.pane_id)
|
|
2947
|
-
}) && (launch.observedChildAgent === void 0 || launch.observedChildAgent() === agentId),
|
|
2948
|
-
{ timeoutMs: remaining(), ...signal !== void 0 ? { signal } : {} }
|
|
2949
|
-
);
|
|
2950
|
-
return {
|
|
2951
|
-
kind: "ready",
|
|
2952
|
-
value: {
|
|
2953
|
-
request_id: launch.request_id,
|
|
2954
|
-
registration_id: agentId,
|
|
2955
|
-
pane_id: bound.pane_id,
|
|
2956
|
-
bound_process: {
|
|
2957
|
-
source: bound.source,
|
|
2958
|
-
incarnation: bound.incarnation
|
|
2959
|
-
},
|
|
2960
|
-
state
|
|
2961
|
-
}
|
|
2962
|
-
};
|
|
2963
|
-
} catch (error) {
|
|
2964
|
-
const claimed = options.readers.readState(launch.mailbox)?.paneId;
|
|
2965
|
-
return unresolvedLaunch(
|
|
2966
|
-
launch.request_id,
|
|
2967
|
-
claimed !== void 0 && claimed !== radarPaneHandle(bound.pane_id) ? `the bound child ${agentId} claims the pane ${claimed}, not the pane ${bound.pane_id} this launch created for it` : `the bound child ${agentId} has not acknowledged this launch's startup (${String(error)})`
|
|
2968
|
-
);
|
|
3065
|
+
let lastFacts;
|
|
3066
|
+
function publishFacts(context) {
|
|
3067
|
+
if (stopped || !rootSession) return;
|
|
3068
|
+
const facts = factsFrom(context);
|
|
3069
|
+
const encoded = JSON.stringify(facts);
|
|
3070
|
+
if (encoded === lastFacts) return;
|
|
3071
|
+
if (lastFacts === void 0 && encoded === "{}") return;
|
|
3072
|
+
lastFacts = encoded;
|
|
3073
|
+
try {
|
|
3074
|
+
publication.facts.update(facts);
|
|
3075
|
+
} catch {
|
|
3076
|
+
}
|
|
2969
3077
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3078
|
+
for (const name of [
|
|
3079
|
+
"model_select",
|
|
3080
|
+
"thinking_level_select",
|
|
3081
|
+
"session_info_changed",
|
|
3082
|
+
"turn_end"
|
|
3083
|
+
]) {
|
|
3084
|
+
on(name, (_event, context) => publishFacts(context));
|
|
3085
|
+
}
|
|
3086
|
+
function emit() {
|
|
3087
|
+
if (stopped || !rootSession) return;
|
|
3088
|
+
const snapshot = boundedRadarSnapshot(currentSnapshot());
|
|
3089
|
+
const encoded = JSON.stringify(snapshot);
|
|
3090
|
+
if (encoded === lastPublished) return;
|
|
3091
|
+
lastPublished = encoded;
|
|
3092
|
+
try {
|
|
3093
|
+
publication.execution.update(snapshot);
|
|
3094
|
+
} catch {
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
keep(
|
|
3098
|
+
pi.events?.on("permissions:ui_prompt", (event) => {
|
|
3099
|
+
const requestId = event?.requestId;
|
|
3100
|
+
if (!rootSession || typeof requestId !== "string") return;
|
|
3101
|
+
permissionPrompts.add(requestId);
|
|
3102
|
+
emit();
|
|
3103
|
+
})
|
|
3104
|
+
);
|
|
3105
|
+
keep(
|
|
3106
|
+
pi.events?.on("permissions:decision", (event) => {
|
|
3107
|
+
const requestId = event?.requestId;
|
|
3108
|
+
if (typeof requestId !== "string" || !permissionPrompts.delete(requestId))
|
|
3109
|
+
return;
|
|
3110
|
+
emit();
|
|
3111
|
+
})
|
|
3112
|
+
);
|
|
3113
|
+
if (options.questionnaireEvent !== void 0)
|
|
3114
|
+
keep(
|
|
3115
|
+
pi.events?.on(options.questionnaireEvent, (event) => {
|
|
3116
|
+
if (!rootSession || typeof event !== "object" || event === null)
|
|
3117
|
+
return;
|
|
3118
|
+
const value = event;
|
|
3119
|
+
if (typeof value.active !== "boolean") return;
|
|
3120
|
+
questionnaire = value.active ? dialogLabel(event) || "questionnaire" : void 0;
|
|
3121
|
+
emit();
|
|
3122
|
+
})
|
|
3123
|
+
);
|
|
3124
|
+
on("session_start", (_event, context) => {
|
|
3125
|
+
rootSession = true;
|
|
3126
|
+
reportSession(context);
|
|
3127
|
+
publishFacts(context);
|
|
3128
|
+
publication.reopen();
|
|
3129
|
+
active = context?.isIdle?.() === false;
|
|
3130
|
+
emit();
|
|
3131
|
+
});
|
|
3132
|
+
on("agent_start", () => {
|
|
3133
|
+
if (!rootSession) return;
|
|
3134
|
+
active = true;
|
|
3135
|
+
emit();
|
|
3136
|
+
});
|
|
3137
|
+
on("agent_settled", (event, context) => {
|
|
3138
|
+
if (!rootSession) return;
|
|
3139
|
+
if (event?.aborted === true) lastOutcome = { result: "aborted" };
|
|
3140
|
+
if (context?.isIdle?.() === true) active = false;
|
|
3141
|
+
emit();
|
|
3142
|
+
});
|
|
3143
|
+
on("message_end", (event) => {
|
|
3144
|
+
if (!rootSession) return;
|
|
3145
|
+
const { stopReason } = assistantStop(event);
|
|
3146
|
+
if (stopReason === "error") {
|
|
3147
|
+
lastOutcome = { result: "error" };
|
|
3148
|
+
} else if (stopReason === "aborted") {
|
|
3149
|
+
lastOutcome = { result: "aborted" };
|
|
3150
|
+
} else if (stopReason === "stop") {
|
|
3151
|
+
lastOutcome = { result: "finished" };
|
|
3018
3152
|
} else {
|
|
3019
|
-
|
|
3153
|
+
return;
|
|
3020
3154
|
}
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3155
|
+
emit();
|
|
3156
|
+
});
|
|
3157
|
+
on("session_shutdown", async () => {
|
|
3158
|
+
suspend();
|
|
3159
|
+
await publication.presence.release();
|
|
3160
|
+
});
|
|
3161
|
+
function suspend() {
|
|
3162
|
+
if (stopped) return;
|
|
3163
|
+
rootSession = false;
|
|
3164
|
+
active = false;
|
|
3165
|
+
questionnaire = void 0;
|
|
3166
|
+
permissionPrompts.clear();
|
|
3167
|
+
lastOutcome = void 0;
|
|
3168
|
+
lastPublished = void 0;
|
|
3169
|
+
publication.stop();
|
|
3170
|
+
}
|
|
3171
|
+
function stop() {
|
|
3172
|
+
if (stopped) return;
|
|
3173
|
+
suspend();
|
|
3174
|
+
stopped = true;
|
|
3175
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
3026
3176
|
}
|
|
3177
|
+
return {
|
|
3178
|
+
publication,
|
|
3179
|
+
binding: () => managed !== void 0 ? publication.binding(managed.key) : void 0,
|
|
3180
|
+
blocked,
|
|
3181
|
+
stop
|
|
3182
|
+
};
|
|
3027
3183
|
}
|
|
3028
3184
|
|
|
3029
3185
|
// extension/radar-runtime.ts
|
|
@@ -3036,7 +3192,7 @@ __export(radar_runtime_exports, {
|
|
|
3036
3192
|
requestRadarRuntime: () => requestRadarRuntime
|
|
3037
3193
|
});
|
|
3038
3194
|
var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
|
|
3039
|
-
var RADAR_RUNTIME_VERSION =
|
|
3195
|
+
var RADAR_RUNTIME_VERSION = 3;
|
|
3040
3196
|
function lazyRadarRuntime(start) {
|
|
3041
3197
|
let pending;
|
|
3042
3198
|
return {
|
|
@@ -3070,6 +3226,7 @@ function isRadarRuntimeHandle(value) {
|
|
|
3070
3226
|
return handle.version === RADAR_RUNTIME_VERSION && typeof handle.start === "function";
|
|
3071
3227
|
}
|
|
3072
3228
|
export {
|
|
3229
|
+
radar_children_exports as children,
|
|
3073
3230
|
radar_client_exports as client,
|
|
3074
3231
|
radar_communication_exports as communication,
|
|
3075
3232
|
radar_execution_exports as execution,
|