pi-radar 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +8 -10
  2. package/dist/extension/baseline.js +343 -361
  3. package/dist/extension/radar-children.js +108 -218
  4. package/dist/extension/radar-client.js +103 -15
  5. package/dist/extension/radar-execution.js +26 -17
  6. package/dist/extension/radar-inspection.js +0 -45
  7. package/dist/extension/radar-launch.js +24 -185
  8. package/dist/extension/radar-publication.js +26 -17
  9. package/dist/extension/radar-runtime.js +1 -1
  10. package/dist/index.js +317 -488
  11. package/extension/generated-contracts.ts +362 -13
  12. package/extension/radar-children.ts +60 -155
  13. package/extension/radar-client.ts +130 -19
  14. package/extension/radar-inspection.ts +2 -77
  15. package/extension/radar-launch.ts +52 -391
  16. package/extension/radar-publication.ts +36 -53
  17. package/extension/radar-runtime.ts +1 -1
  18. package/package.json +1 -1
  19. package/schema/public/v1/observation-get-response.deserialize.schema.json +5 -25
  20. package/schema/public/v1/observation-get-response.serialize.schema.json +7 -29
  21. package/schema/public/v1/observation-snapshot.deserialize.schema.json +5 -25
  22. package/schema/public/v1/observation-snapshot.serialize.schema.json +7 -29
  23. package/schema/public/v1/spawn-list-page.deserialize.schema.json +40 -17
  24. package/schema/public/v1/spawn-list-page.serialize.schema.json +40 -17
  25. package/schema/public/v1/subject-assignment-acquire.deserialize.schema.json +78 -0
  26. package/schema/public/v1/subject-assignment-acquire.serialize.schema.json +79 -0
  27. package/schema/public/v1/subject-assignment-publish.deserialize.schema.json +310 -0
  28. package/schema/public/v1/subject-assignment-publish.serialize.schema.json +310 -0
  29. package/schema/public/v1/subject-assignment-retire.deserialize.schema.json +22 -0
  30. package/schema/public/v1/subject-assignment-retire.serialize.schema.json +22 -0
  31. package/schema/public/v1/subject-child-get-params.deserialize.schema.json +18 -0
  32. package/schema/public/v1/subject-child-get-params.serialize.schema.json +18 -0
  33. package/schema/public/v1/subject-children-params.deserialize.schema.json +51 -0
  34. package/schema/public/v1/subject-children-params.serialize.schema.json +54 -0
  35. package/schema/public/v1/subject-get-params.deserialize.schema.json +14 -0
  36. package/schema/public/v1/subject-get-params.serialize.schema.json +14 -0
  37. package/schema/public/v1/subject-get-result.deserialize.schema.json +566 -0
  38. package/schema/public/v1/subject-get-result.serialize.schema.json +573 -0
  39. package/schema/public/v1/subject-list-params.deserialize.schema.json +38 -0
  40. package/schema/public/v1/subject-list-params.serialize.schema.json +43 -0
  41. package/schema/public/v1/subject-list-result.deserialize.schema.json +575 -0
  42. package/schema/public/v1/subject-list-result.serialize.schema.json +583 -0
package/dist/index.js CHANGED
@@ -9,61 +9,14 @@ var radar_children_exports = {};
9
9
  __export(radar_children_exports, {
10
10
  createRadarChildren: () => createRadarChildren
11
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
- }
12
+ import { randomUUID as randomUUID2 } from "node:crypto";
59
13
 
60
14
  // extension/radar-launch.ts
61
15
  var radar_launch_exports = {};
62
16
  __export(radar_launch_exports, {
17
+ launchFailure: () => launchFailure,
63
18
  launchRadarWorker: () => launchRadarWorker,
64
- observeRadarChild: () => observeRadarChild,
65
- readProcessGone: () => readProcessGone,
66
- relaunchRefusalForChild: () => relaunchRefusalForChild
19
+ waitForChild: () => waitForChild
67
20
  });
68
21
  import { setTimeout as delay } from "node:timers/promises";
69
22
 
@@ -87,7 +40,6 @@ __export(radar_client_exports, {
87
40
  RADAR_SPAWN_CLOSE_CONTRACT: () => RADAR_SPAWN_CLOSE_CONTRACT,
88
41
  RADAR_SPAWN_CONTRACT: () => RADAR_SPAWN_CONTRACT,
89
42
  RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT: () => RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT,
90
- RADAR_SPAWN_LIST_CONTRACT: () => RADAR_SPAWN_LIST_CONTRACT,
91
43
  boundedRadarAssignment: () => boundedRadarAssignment,
92
44
  boundedRadarFacts: () => boundedRadarFacts,
93
45
  boundedRadarSnapshot: () => boundedRadarSnapshot,
@@ -104,11 +56,10 @@ import { lstatSync } from "node:fs";
104
56
  import { createConnection } from "node:net";
105
57
  import { dirname, join } from "node:path";
106
58
  var RADAR_PROTOCOL_VERSION = 2;
107
- var RADAR_MINIMUM_REVISION = 7;
59
+ var RADAR_MINIMUM_REVISION = 9;
108
60
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
109
61
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
110
62
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
111
- var RADAR_SPAWN_LIST_CONTRACT = "spawn.list.v1";
112
63
  var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
113
64
  var RADAR_LIFECYCLE_RESTART_CONTRACT = "lifecycle.restart.v1";
114
65
  var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
@@ -418,6 +369,9 @@ function spawnEdge(value) {
418
369
  const agentId = stringAt(bound, "registration_id");
419
370
  const subjectId = stringAt(bound, "subject_id");
420
371
  const exit = exitAt(recordAt(value, "exit_observation"));
372
+ const withdrawnRecord = recordAt(value, "withdrawn");
373
+ const withdrawnAt = stringAt(withdrawnRecord, "at");
374
+ const withdrawnReason = stringAt(withdrawnRecord, "reason");
421
375
  return {
422
376
  ok: true,
423
377
  value: {
@@ -434,7 +388,9 @@ function spawnEdge(value) {
434
388
  ...isUuid(subjectId) ? { subject_id: subjectId } : {}
435
389
  }
436
390
  } : {},
437
- ...exit !== void 0 ? { exit_observation: exit } : {}
391
+ ...exit !== void 0 ? { exit_observation: exit } : {},
392
+ ...stringAt(value, "granted_at") !== void 0 ? { granted_at: stringAt(value, "granted_at") } : {},
393
+ ...withdrawnAt !== void 0 && withdrawnReason !== void 0 ? { withdrawn: { at: withdrawnAt, reason: withdrawnReason } } : {}
438
394
  }
439
395
  };
440
396
  }
@@ -444,6 +400,14 @@ var PROCESS_VERDICTS = [
444
400
  "mismatched",
445
401
  "unavailable"
446
402
  ];
403
+ function subjectAt(value) {
404
+ const subjectId = recordAt(value, "subject_id");
405
+ const session = recordAt(value, "session_id");
406
+ const state = recordAt(value, "state");
407
+ if (!isUuid(subjectId) || session != null && !isUuid(session) || !["starting", "live", "detached", "unknown"].includes(String(state)))
408
+ return protocol("subject read carries no valid identity or attachment state");
409
+ return { ok: true, value };
410
+ }
447
411
  function agentProcessAt(agent) {
448
412
  const process2 = recordAt(agent, "process");
449
413
  const claimed = recordAt(process2, "claimed");
@@ -836,7 +800,9 @@ function createRadarClient(options = {}) {
836
800
  return withRegistry(async () => {
837
801
  const answer = await call("agent.presence", { ...request });
838
802
  if (!answer.ok) return answer;
839
- return decodePresenceAnswer(answer.value);
803
+ const decoded = decodePresenceAnswer(answer.value);
804
+ if (decoded.ok) processRegistrationId = decoded.value.registration_id;
805
+ return decoded;
840
806
  });
841
807
  },
842
808
  async releasePresence(request) {
@@ -871,9 +837,13 @@ function createRadarClient(options = {}) {
871
837
  },
872
838
  async acquire(request) {
873
839
  return withRegistryChannel(request.channel, async () => {
874
- const answer = await call("agent.acquire", {
875
- registration_id: request.registration_id,
876
- channel: request.channel,
840
+ const assignment = request.channel === "assignment";
841
+ if (assignment && processRegistrationId === void 0)
842
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
843
+ if (assignment && request.subject_id === void 0)
844
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
845
+ const answer = await call(assignment ? "subject.assignment.acquire" : "agent.acquire", {
846
+ ...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
877
847
  publisher: request.publisher,
878
848
  ...request.replace !== void 0 ? { replace: request.replace } : {}
879
849
  });
@@ -900,15 +870,19 @@ function createRadarClient(options = {}) {
900
870
  }
901
871
  }
902
872
  return withRegistryChannel(request.channel, async () => {
903
- const answer = await call("agent.publish", {
904
- registration_id: request.registration_id,
905
- channel: request.channel,
873
+ const update = {
906
874
  writer_handle: request.writer_handle,
907
875
  sequence: request.sequence,
908
876
  ...request.lease_ms !== void 0 ? { lease_ms: request.lease_ms } : {},
909
877
  ...request.observed_at !== void 0 ? { observed_at: request.observed_at } : {},
910
878
  snapshot
911
- });
879
+ };
880
+ const assignment = request.channel === "assignment";
881
+ if (assignment && processRegistrationId === void 0)
882
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
883
+ if (assignment && request.subject_id === void 0)
884
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
885
+ const answer = await call(assignment ? "subject.assignment.publish" : "agent.publish", assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id, update } : { registration_id: request.registration_id, channel: request.channel, ...update });
912
886
  if (answer.ok === false) return answer;
913
887
  const channel = answer.value.channel;
914
888
  const writer = channel?.writer;
@@ -949,7 +923,15 @@ function createRadarClient(options = {}) {
949
923
  },
950
924
  async retire(request) {
951
925
  return withRegistryChannel(request.channel, async () => {
952
- const answer = await call("agent.retire", request);
926
+ const assignment = request.channel === "assignment";
927
+ if (assignment && processRegistrationId === void 0)
928
+ return { ok: false, code: "refused", message: "assignment requires a granted parent" };
929
+ if (assignment && request.subject_id === void 0)
930
+ return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
931
+ const answer = await call(assignment ? "subject.assignment.retire" : "agent.retire", {
932
+ ...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
933
+ writer_handle: request.writer_handle
934
+ });
953
935
  return answer.ok === true ? { ok: true, value: void 0 } : answer;
954
936
  });
955
937
  },
@@ -1024,14 +1006,73 @@ function createRadarClient(options = {}) {
1024
1006
  request.request_id
1025
1007
  );
1026
1008
  if (answer.ok === false) return answer;
1027
- return focusOutcome(recordAt(answer.value, "request"), request.request_id);
1009
+ const settled = focusOutcome(recordAt(answer.value, "request"), request.request_id);
1010
+ if (settled.ok === false) return settled;
1011
+ const launch = recordAt(recordAt(answer.value, "request"), "launch_request_id");
1012
+ if (!isUuid(launch)) return protocol("lifecycle.restart reply names no launch");
1013
+ return { ok: true, value: { ...settled.value, launch_request_id: launch } };
1014
+ });
1015
+ },
1016
+ async subjectChildren(request = {}) {
1017
+ const registrationId = processRegistrationId;
1018
+ if (registrationId === void 0)
1019
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
1020
+ return withRegistry(async () => {
1021
+ const params = { ...request, registration_id: registrationId };
1022
+ const answer = await call("subject.children", { ...params });
1023
+ if (!answer.ok) return answer;
1024
+ const subjects = recordAt(answer.value, "subjects");
1025
+ const next = recordAt(answer.value, "next");
1026
+ if (!Array.isArray(subjects) || next != null && !isUuid(next))
1027
+ return protocol("subject.children reply carries no valid page");
1028
+ for (const subject of subjects) {
1029
+ const decoded = subjectAt(subject);
1030
+ if (!decoded.ok) return decoded;
1031
+ }
1032
+ return { ok: true, value: answer.value };
1033
+ });
1034
+ },
1035
+ async childGet(subject_id) {
1036
+ const registrationId = processRegistrationId;
1037
+ if (registrationId === void 0)
1038
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
1039
+ if (!isUuid(subject_id))
1040
+ return { ok: false, code: "bad_params", message: "child requires a subject UUID" };
1041
+ return withRegistry(async () => {
1042
+ const params = { registration_id: registrationId, subject_id };
1043
+ const answer = await call("subject.child.get", { ...params });
1044
+ return answer.ok ? subjectAt(recordAt(answer.value, "subject")) : answer;
1045
+ });
1046
+ },
1047
+ async childInspect(subject_id) {
1048
+ const registration_id = processRegistrationId;
1049
+ if (registration_id === void 0)
1050
+ return { ok: false, code: "refused", message: "this process has no Radar registration" };
1051
+ return withRegistry(async () => {
1052
+ const answer = await call("subject.child.inspect", { registration_id, subject_id });
1053
+ if (!answer.ok) return answer;
1054
+ const subject = subjectAt(recordAt(answer.value, "subject"));
1055
+ if (!subject.ok) return subject;
1056
+ const output = recordAt(answer.value, "output");
1057
+ const text = stringAt(output, "text");
1058
+ if (text === void 0 || typeof recordAt(output, "truncated") !== "boolean")
1059
+ return protocol("child inspection has no output evidence");
1060
+ const foreground = recordAt(answer.value, "foreground");
1061
+ const activity = subject.value.execution?.snapshot?.snapshot;
1062
+ return { ok: true, value: {
1063
+ capturedAt: Date.now(),
1064
+ recentOutputTruncated: recordAt(output, "truncated"),
1065
+ ...text ? { recentOutput: text } : {},
1066
+ ...foreground ? { foreground } : {},
1067
+ ...activity && "activity" in activity ? { activity: activity.activity } : {}
1068
+ } };
1028
1069
  });
1029
1070
  },
1030
1071
  async spawnList(request) {
1031
1072
  const registrationId = processRegistrationId;
1032
1073
  if (registrationId === void 0)
1033
1074
  return { ok: false, code: "refused", message: "spawn.list requires this client's process registration" };
1034
- return withContract(RADAR_SPAWN_LIST_CONTRACT, async () => {
1075
+ return withRegistry(async () => {
1035
1076
  const params = {
1036
1077
  registration_id: registrationId,
1037
1078
  ...request.after !== void 0 ? { after: request.after } : {},
@@ -1394,215 +1435,9 @@ function boundedRadarSnapshot(snapshot) {
1394
1435
  };
1395
1436
  }
1396
1437
 
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
1404
- });
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
- };
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;
1502
- return {
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
1510
- }
1511
- };
1512
- }
1513
-
1514
1438
  // extension/radar-launch.ts
1515
- function unknownChild(reason) {
1516
- return { kind: "unknown", diagnostic: trajectory(reason) };
1517
- }
1518
- function trajectory(reason) {
1519
- return `the daemon's current record of this launch's child is ${reason}`;
1520
- }
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
- );
1579
- return {
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
1587
- }
1588
- };
1589
- }
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;
1595
- }
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"}`;
1603
- }
1604
1439
  function refusedLaunch(message) {
1605
- return { kind: "refused", message };
1440
+ return { kind: "failed", message };
1606
1441
  }
1607
1442
  function unresolvedLaunch(requestId, message) {
1608
1443
  return {
@@ -1611,14 +1446,6 @@ function unresolvedLaunch(requestId, message) {
1611
1446
  message: `spawn ${requestId} is unresolved (${message})`
1612
1447
  };
1613
1448
  }
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";
1618
- }
1619
- function edgeMessage(edge) {
1620
- return `the daemon's spawn record is created ${edge.created} and launched ${edge.launched}`;
1621
- }
1622
1449
  var PRE_EFFECT_CODES = [
1623
1450
  "absent",
1624
1451
  "unsupported",
@@ -1631,14 +1458,12 @@ var PRE_EFFECT_CODES = [
1631
1458
  "busy",
1632
1459
  "backend_unavailable"
1633
1460
  ];
1634
- function spawnFailure(result, requestId) {
1461
+ function launchFailure(result, requestId) {
1635
1462
  return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
1636
1463
  }
1637
1464
  async function launchRadarWorker(options) {
1638
1465
  const { client, launch, signal } = options;
1639
1466
  const pollMs = options.pollMs ?? 250;
1640
- const deadline = Date.now() + options.timeoutMs;
1641
- const remaining = () => Math.max(0, deadline - Date.now());
1642
1467
  const parent = await options.publication.subject();
1643
1468
  if (parent === void 0 || !isUuid(parent.registration_id))
1644
1469
  return refusedLaunch(
@@ -1669,55 +1494,22 @@ async function launchRadarWorker(options) {
1669
1494
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
1670
1495
  });
1671
1496
  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));
1497
+ const refusal = launchFailure(spawned, launch.request_id);
1498
+ if (refusal.kind === "failed") return refusal;
1681
1499
  }
1682
1500
  const bound = await waitForChild(client, launch.request_id, {
1683
- remaining,
1684
1501
  pollMs,
1685
1502
  ...signal !== void 0 ? { signal } : {}
1686
1503
  });
1687
1504
  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
- }
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
- );
1720
- }
1505
+ return {
1506
+ kind: "ready",
1507
+ value: {
1508
+ request_id: launch.request_id,
1509
+ subject_id: bound.subject_id,
1510
+ pane_id: bound.pane_id
1511
+ }
1512
+ };
1721
1513
  }
1722
1514
  async function waitForChild(client, requestId, options) {
1723
1515
  let last = "the daemon has not answered";
@@ -1726,37 +1518,21 @@ async function waitForChild(client, requestId, options) {
1726
1518
  return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
1727
1519
  const answer = await client.spawnGet(requestId);
1728
1520
  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));
1521
+ const { edge } = answer.value;
1522
+ if (edge.withdrawn !== void 0)
1523
+ return refusedLaunch(`the daemon withdrew this launch: ${edge.withdrawn.reason}`);
1524
+ if (edge.granted_at === void 0) {
1525
+ await delay(options.pollMs);
1526
+ continue;
1527
+ }
1735
1528
  const pane = edge.location?.pane;
1736
1529
  if (pane === void 0 || pane === "")
1737
1530
  return unresolvedLaunch(
1738
1531
  requestId,
1739
1532
  "the daemon's launch recorded no pane to verify the child against"
1740
1533
  );
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
1534
  const agentId = edge.bound?.registration_id;
1752
1535
  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
1536
  const subjectId = edge.bound?.subject_id;
1761
1537
  if (subjectId === void 0 || !isUuid(subjectId))
1762
1538
  return unresolvedLaunch(
@@ -1766,136 +1542,60 @@ async function waitForChild(client, requestId, options) {
1766
1542
  return {
1767
1543
  kind: "bound",
1768
1544
  subject_id: subjectId,
1769
- registration_id: agentId,
1770
- pane_id: pane,
1771
- source,
1772
- incarnation
1545
+ pane_id: pane
1773
1546
  };
1774
1547
  }
1775
1548
  last = "no child has registered with this launch's token yet";
1776
1549
  } else {
1777
- last = answer.message;
1550
+ return unresolvedLaunch(requestId, answer.message);
1778
1551
  }
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
- });
1552
+ await delay(options.pollMs);
1784
1553
  }
1785
1554
  }
1786
1555
 
1787
1556
  // extension/radar-children.ts
1788
- var PAGE = 100;
1789
- var MAX_PAGES = 20;
1790
1557
  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
- });
1816
- }
1817
- if (!read.value.next) return { ok: true, value: newest };
1818
- after = read.value.next;
1819
- }
1820
- return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
1821
- }
1822
- async function resolve2(subject) {
1823
- const all = await resolveAll();
1824
- return all.ok ? all.value.get(subject) : void 0;
1825
- }
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);
1831
- }
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);
1837
- }
1838
- }
1839
1558
  return {
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) };
1559
+ async list(selection) {
1560
+ const children = [];
1561
+ let after;
1562
+ do {
1563
+ const page = await client.subjectChildren({ selection, after, limit: 100 });
1564
+ if (!page.ok) return page;
1565
+ children.push(...page.value.subjects);
1566
+ if (page.value.next === after)
1567
+ return { ok: false, code: "protocol", message: "child page did not advance" };
1568
+ after = page.value.next ?? void 0;
1569
+ } while (after !== void 0);
1570
+ return { ok: true, value: children };
1845
1571
  },
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;
1874
- },
1875
- retire(subject) {
1876
- held.get(subject)?.writer.retire();
1877
- held.delete(subject);
1878
- },
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
1572
+ async restart(subject, program, options) {
1573
+ const parent = await publication.subject();
1574
+ if (parent?.subject_id === void 0)
1575
+ return { kind: "failed", message: "this process has no granted subject" };
1576
+ const request_id = randomUUID2();
1577
+ const requested = await client.restart({ request_id, parent: parent.registration_id, subject_id: subject, ...program });
1578
+ if (!requested.ok) return launchFailure(requested, request_id);
1579
+ const bound = await waitForChild(client, requested.value.launch_request_id, {
1580
+ pollMs: options?.pollMs ?? 250,
1581
+ ...options?.signal !== void 0 ? { signal: options.signal } : {}
1888
1582
  });
1583
+ if (bound.kind !== "bound") return bound;
1584
+ return { kind: "ready", value: { request_id: requested.value.launch_request_id, subject_id: bound.subject_id, pane_id: bound.pane_id } };
1889
1585
  },
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
- });
1586
+ assignment: (subject, owner) => publication.assignment(subject, owner),
1587
+ retire: (subject) => publication.retireAssignment(subject),
1588
+ async observe(subject) {
1589
+ const read = await client.childGet(subject);
1590
+ if (!read.ok) return { kind: "unknown", diagnostic: `${read.code}: ${read.message}` };
1591
+ if (read.value.state === "live") return { kind: "live", subject: read.value };
1592
+ if (read.value.state === "detached") return { kind: "gone", subject: read.value };
1593
+ return { kind: "unknown", diagnostic: `child attachment is ${read.value.state}` };
1594
+ },
1595
+ async inspect(subject) {
1596
+ const read = await client.childInspect(subject);
1597
+ if (!read.ok) throw new Error(`Child inspection unavailable (${read.code}: ${read.message})`);
1598
+ return read.value;
1899
1599
  }
1900
1600
  };
1901
1601
  }
@@ -1964,7 +1664,7 @@ __export(radar_publication_exports, {
1964
1664
  radarProcessSlot: () => radarProcessSlot,
1965
1665
  readProcessBirth: () => readProcessBirth
1966
1666
  });
1967
- import { createHash, randomUUID as randomUUID2 } from "node:crypto";
1667
+ import { createHash, randomUUID as randomUUID3 } from "node:crypto";
1968
1668
  import {
1969
1669
  closeSync,
1970
1670
  constants,
@@ -2094,7 +1794,7 @@ function createPublicationStore(root) {
2094
1794
  ensureDirectory(directory);
2095
1795
  const temporary = join2(
2096
1796
  directory,
2097
- `.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID2()}.tmp`
1797
+ `.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID3()}.tmp`
2098
1798
  );
2099
1799
  const bytes = Buffer.from(JSON.stringify(value), "utf8");
2100
1800
  if (bytes.length > MAX_RECORD_BYTES)
@@ -2150,7 +1850,7 @@ function radarProcessSlot() {
2150
1850
  return host[PROCESS_SLOT];
2151
1851
  }
2152
1852
  function radarProcessIncarnation(slot = radarProcessSlot()) {
2153
- return slot.incarnation ??= randomUUID2();
1853
+ return slot.incarnation ??= randomUUID3();
2154
1854
  }
2155
1855
  function radarBindingKey(run, owner, label) {
2156
1856
  return createHash("sha256").update(`${run}\0${owner}\0${label}`).digest("hex").slice(0, 32);
@@ -2310,15 +2010,18 @@ function createChannelWriter(input) {
2310
2010
  function blank(agentId) {
2311
2011
  return {
2312
2012
  version: RADAR_PUBLICATION_VERSION,
2313
- registration_id: agentId,
2013
+ target_id: agentId,
2314
2014
  channel,
2315
2015
  publisher,
2316
2016
  sequence: 0
2317
2017
  };
2318
2018
  }
2019
+ function addressed(target) {
2020
+ return channel === "assignment" ? { subject_id: target } : { registration_id: target };
2021
+ }
2319
2022
  function loadRecord() {
2320
2023
  const value = store.read(recordName);
2321
- const agentId = textAt(value, "registration_id");
2024
+ const agentId = textAt(value, "target_id");
2322
2025
  const storedPublisher = recordAt2(value, "publisher");
2323
2026
  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
2027
  return blank("");
@@ -2333,7 +2036,7 @@ function createChannelWriter(input) {
2333
2036
  const sentAt = numberAt2(acceptedValue, "sent_at");
2334
2037
  return {
2335
2038
  version: RADAR_PUBLICATION_VERSION,
2336
- registration_id: agentId,
2039
+ target_id: agentId,
2337
2040
  channel,
2338
2041
  publisher,
2339
2042
  ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
@@ -2374,15 +2077,11 @@ function createChannelWriter(input) {
2374
2077
  if (record.writer !== void 0) return true;
2375
2078
  const agentId = await input.subject(recovering);
2376
2079
  if (agentId === void 0) return false;
2377
- if (record.registration_id !== agentId) {
2080
+ if (record.target_id !== agentId) {
2378
2081
  record = blank(agentId);
2379
2082
  save();
2380
2083
  }
2381
- const answer = await client.acquire({
2382
- registration_id: agentId,
2383
- channel,
2384
- publisher
2385
- });
2084
+ const answer = await client.acquire({ ...addressed(agentId), channel, publisher });
2386
2085
  if (answer.ok === false) {
2387
2086
  if (answer.code === "not_found") {
2388
2087
  recovering = true;
@@ -2409,7 +2108,7 @@ function createChannelWriter(input) {
2409
2108
  const writer = record.writer;
2410
2109
  if (writer === void 0) return;
2411
2110
  const answer = await client.publish({
2412
- registration_id: record.registration_id,
2111
+ ...addressed(record.target_id),
2413
2112
  channel,
2414
2113
  writer_handle: writer.handle,
2415
2114
  sequence: pending.sequence,
@@ -2503,9 +2202,9 @@ function createChannelWriter(input) {
2503
2202
  stopPump();
2504
2203
  const writer = record.writer;
2505
2204
  if (writer === void 0) return;
2506
- const agentId = record.registration_id;
2205
+ const agentId = record.target_id;
2507
2206
  try {
2508
- void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
2207
+ void client.retire({ ...addressed(agentId), channel, writer_handle: writer.handle }).then((answer) => {
2509
2208
  if (answer.ok === true) {
2510
2209
  try {
2511
2210
  record.retired_at = new Date(scheduler.now()).toISOString();
@@ -2595,6 +2294,7 @@ function createPresenceReporter(input) {
2595
2294
  }
2596
2295
  claim.registration_id = answer.value.registration_id;
2597
2296
  claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
2297
+ if (claim.subject_id !== void 0) input.granted?.(claim.subject_id);
2598
2298
  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
2299
  diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
2600
2300
  save();
@@ -2618,7 +2318,7 @@ function createPresenceReporter(input) {
2618
2318
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
2619
2319
  }
2620
2320
  standing = void 0;
2621
- state = { version: 2, session, claim_id: randomUUID2(), releases };
2321
+ state = { version: 2, session, claim_id: randomUUID3(), releases };
2622
2322
  save();
2623
2323
  stopped = false;
2624
2324
  void ensure();
@@ -2655,7 +2355,7 @@ var STORE_IDENTITY = /* @__PURE__ */ new WeakMap();
2655
2355
  function storeIdentity(store) {
2656
2356
  const known = STORE_IDENTITY.get(store);
2657
2357
  if (known !== void 0) return known;
2658
- const created = randomUUID2();
2358
+ const created = randomUUID3();
2659
2359
  STORE_IDENTITY.set(store, created);
2660
2360
  return created;
2661
2361
  }
@@ -2776,7 +2476,7 @@ function createRadarPublicationInner(options) {
2776
2476
  agentId = registered;
2777
2477
  processRecord = { ...record, registration_id: agentId };
2778
2478
  store.write(registrationName, processRecord);
2779
- if (options.binding !== void 0) writeBinding(options.binding);
2479
+ if (options.binding !== void 0) writeBinding(options.binding, grantedSubject());
2780
2480
  return agentId;
2781
2481
  })();
2782
2482
  try {
@@ -2785,11 +2485,16 @@ function createRadarPublicationInner(options) {
2785
2485
  subjectReady = void 0;
2786
2486
  }
2787
2487
  }
2788
- function writeBinding(binding) {
2488
+ function grantedSubject() {
2489
+ const standing = presence.management();
2490
+ return standing?.state === "managed" ? standing.subject_id : void 0;
2491
+ }
2492
+ function writeBinding(binding, subjectId) {
2789
2493
  if (agentId === void 0) return;
2790
2494
  store.write(`bindings/${binding.key}.json`, {
2791
2495
  version: RADAR_PUBLICATION_VERSION,
2792
2496
  registration_id: agentId,
2497
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2793
2498
  incarnation,
2794
2499
  run: binding.run,
2795
2500
  owner: binding.owner,
@@ -2829,6 +2534,9 @@ function createRadarPublicationInner(options) {
2829
2534
  ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
2830
2535
  };
2831
2536
  },
2537
+ granted: (subjectId) => {
2538
+ if (options.binding !== void 0) writeBinding(options.binding, subjectId);
2539
+ },
2832
2540
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
2833
2541
  });
2834
2542
  const facts = createChannelWriter({
@@ -2860,8 +2568,7 @@ function createRadarPublicationInner(options) {
2860
2568
  channel: "assignment",
2861
2569
  publisher: publisher("herdsman-owner", reportingOwner),
2862
2570
  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.
2571
+ // Radar owns this assignment across the child's process replacements.
2865
2572
  subject: async () => target,
2866
2573
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
2867
2574
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -2882,16 +2589,18 @@ function createRadarPublicationInner(options) {
2882
2589
  return void 0;
2883
2590
  }
2884
2591
  const bound = textAt(value, "registration_id");
2592
+ const subjectId = textAt(value, "subject_id");
2885
2593
  const boundIncarnation = textAt(value, "incarnation");
2886
2594
  const run = textAt(value, "run");
2887
2595
  const owner = textAt(value, "owner");
2888
2596
  const label = textAt(value, "label");
2889
2597
  const observedAt = textAt(value, "observed_at");
2890
- if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
2598
+ if (!isUuid(bound) || subjectId !== void 0 && !isUuid(subjectId) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
2891
2599
  return void 0;
2892
2600
  return {
2893
2601
  version: RADAR_PUBLICATION_VERSION,
2894
2602
  registration_id: bound,
2603
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2895
2604
  incarnation: boundIncarnation,
2896
2605
  run,
2897
2606
  owner,
@@ -3182,6 +2891,126 @@ function createRadarExecutionAdapter(pi, options) {
3182
2891
  };
3183
2892
  }
3184
2893
 
2894
+ // extension/radar-inspection.ts
2895
+ var radar_inspection_exports = {};
2896
+
2897
+ // extension/radar-location.ts
2898
+ var radar_location_exports = {};
2899
+ __export(radar_location_exports, {
2900
+ radarLocalPane: () => radarLocalPane,
2901
+ radarPaneHandle: () => radarPaneHandle,
2902
+ resolveRadarProcessLocation: () => resolveRadarProcessLocation,
2903
+ tmuxServerName: () => tmuxServerName
2904
+ });
2905
+ import { basename } from "node:path";
2906
+ function radarLocalPane(env) {
2907
+ const pane = env.TMUX_PANE?.trim();
2908
+ return pane && /^%\d+$/.test(pane) ? pane : void 0;
2909
+ }
2910
+ function radarPaneHandle(qualified) {
2911
+ return qualified.slice(qualified.lastIndexOf(":") + 1);
2912
+ }
2913
+ function tmuxServerName(env) {
2914
+ const socket = env.TMUX?.trim().split(",")[0];
2915
+ return socket ? basename(socket) || void 0 : void 0;
2916
+ }
2917
+ function qualifiedOwnPane(env, backend, instance) {
2918
+ if (backend !== "tmux")
2919
+ return {
2920
+ absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
2921
+ };
2922
+ if (instance === void 0 || instance === "")
2923
+ return {
2924
+ pending: "the daemon did not name the tmux server it addresses"
2925
+ };
2926
+ const server = tmuxServerName(env);
2927
+ if (server === void 0)
2928
+ return {
2929
+ absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
2930
+ };
2931
+ if (server !== instance)
2932
+ return {
2933
+ absent: `this process is attached to tmux server ${server}, not to ${instance}`
2934
+ };
2935
+ const pane = radarLocalPane(env);
2936
+ if (pane === void 0)
2937
+ return {
2938
+ absent: "this process's own environment names no tmux pane of that server"
2939
+ };
2940
+ return { pane: `tmux:${instance}:${pane}` };
2941
+ }
2942
+ function observedContainment(inventory, paneId) {
2943
+ const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
2944
+ if (rows.length === 0) return void 0;
2945
+ if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
2946
+ const row = rows[0];
2947
+ const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
2948
+ if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
2949
+ return {
2950
+ ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
2951
+ };
2952
+ return { pane: row };
2953
+ }
2954
+ async function resolveRadarProcessLocation(client, env = process.env) {
2955
+ let ready;
2956
+ try {
2957
+ ready = await client.ping();
2958
+ } catch {
2959
+ return { state: "pending", reason: "the daemon did not answer this process" };
2960
+ }
2961
+ if (ready.ok === false)
2962
+ return {
2963
+ state: "pending",
2964
+ reason: `the daemon did not answer this process (${ready.code})`
2965
+ };
2966
+ const backend = ready.value.backend;
2967
+ if (backend === void 0 || backend === "none")
2968
+ return { state: "absent", reason: "the daemon serves no physical backend" };
2969
+ const instance = ready.value.instance ?? void 0;
2970
+ const own = qualifiedOwnPane(env, backend, instance);
2971
+ if ("absent" in own) return { state: "absent", reason: own.absent };
2972
+ if ("pending" in own) return { state: "pending", reason: own.pending };
2973
+ const paneId = own.pane;
2974
+ if (paneId === env.PI_HERDSMAN_PARENT_PANE_ID?.trim())
2975
+ return {
2976
+ state: "absent",
2977
+ reason: "this process is in the pane its launch was created beside, not in a pane of its own"
2978
+ };
2979
+ let inventory;
2980
+ try {
2981
+ inventory = await client.observe();
2982
+ } catch {
2983
+ return {
2984
+ state: "pending",
2985
+ reason: "the daemon's topology could not be read for this process's pane"
2986
+ };
2987
+ }
2988
+ if (inventory.ok === false)
2989
+ return {
2990
+ state: "pending",
2991
+ reason: `the daemon's topology could not be read (${inventory.code})`
2992
+ };
2993
+ const containment = observedContainment(inventory.value, paneId);
2994
+ if (containment === void 0)
2995
+ return {
2996
+ state: "pending",
2997
+ reason: `the daemon's topology does not report pane ${paneId} yet`
2998
+ };
2999
+ if ("ambiguous" in containment)
3000
+ return { state: "pending", reason: containment.ambiguous };
3001
+ const row = containment.pane;
3002
+ return {
3003
+ state: "resolved",
3004
+ location: {
3005
+ backend,
3006
+ ...instance !== void 0 ? { instance } : {},
3007
+ workspace: row.workspace_id,
3008
+ tab: row.tab_id,
3009
+ pane: paneId
3010
+ }
3011
+ };
3012
+ }
3013
+
3185
3014
  // extension/radar-runtime.ts
3186
3015
  var radar_runtime_exports = {};
3187
3016
  __export(radar_runtime_exports, {
@@ -3192,7 +3021,7 @@ __export(radar_runtime_exports, {
3192
3021
  requestRadarRuntime: () => requestRadarRuntime
3193
3022
  });
3194
3023
  var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
3195
- var RADAR_RUNTIME_VERSION = 3;
3024
+ var RADAR_RUNTIME_VERSION = 5;
3196
3025
  function lazyRadarRuntime(start) {
3197
3026
  let pending;
3198
3027
  return {