pi-radar 0.3.0 → 0.4.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 (36) hide show
  1. package/README.md +4 -2
  2. package/dist/extension/baseline.js +227 -402
  3. package/dist/extension/radar-client.js +53 -13
  4. package/dist/extension/radar-communication.js +5 -5
  5. package/dist/extension/radar-execution.js +168 -383
  6. package/dist/extension/radar-launch.js +13 -13
  7. package/dist/extension/radar-publication.js +160 -299
  8. package/dist/index.js +239 -414
  9. package/extension/generated-contracts.ts +1249 -13
  10. package/extension/radar-client.ts +92 -29
  11. package/extension/radar-communication.ts +5 -5
  12. package/extension/radar-execution.ts +11 -58
  13. package/extension/radar-launch.ts +21 -21
  14. package/extension/radar-publication.ts +209 -412
  15. package/package.json +3 -4
  16. package/schema/public/v1/handoff-publish-params.deserialize.schema.json +2 -2
  17. package/schema/public/v1/handoff-publish-params.serialize.schema.json +2 -2
  18. package/schema/public/v1/handoff-receipt.deserialize.schema.json +4 -4
  19. package/schema/public/v1/handoff-receipt.serialize.schema.json +4 -4
  20. package/schema/public/v1/handoff.deserialize.schema.json +4 -4
  21. package/schema/public/v1/handoff.serialize.schema.json +4 -4
  22. package/schema/public/v1/observation-get-params.deserialize.schema.json +46 -0
  23. package/schema/public/v1/observation-get-params.serialize.schema.json +52 -0
  24. package/schema/public/v1/observation-get-response.deserialize.schema.json +2731 -0
  25. package/schema/public/v1/observation-get-response.serialize.schema.json +2862 -0
  26. package/schema/public/v1/observation-snapshot.deserialize.schema.json +2616 -0
  27. package/schema/public/v1/observation-snapshot.serialize.schema.json +2741 -0
  28. package/schema/public/v1/presence-answer.deserialize.schema.json +40 -0
  29. package/schema/public/v1/presence-answer.serialize.schema.json +40 -0
  30. package/schema/public/v1/presence-release.deserialize.schema.json +51 -0
  31. package/schema/public/v1/presence-release.serialize.schema.json +52 -0
  32. package/schema/public/v1/presence-report.deserialize.schema.json +149 -0
  33. package/schema/public/v1/presence-report.serialize.schema.json +157 -0
  34. package/extension/radar-session-marker.ts +0 -146
  35. package/schema/public/v1/launch-identity-marker.deserialize.schema.json +0 -32
  36. package/schema/public/v1/launch-identity-marker.serialize.schema.json +0 -32
@@ -4,7 +4,6 @@ import { createConnection } from "node:net";
4
4
  import { dirname, join } from "node:path";
5
5
  import type {
6
6
  DaemonPingSerialize,
7
- LaunchIdentityMarkerSerialize,
8
7
  FactsSnapshotDeserialize,
9
8
  HandoffInboxSerialize,
10
9
  HandoffReceiptSerialize,
@@ -114,6 +113,10 @@ export type RadarLocation = {
114
113
  pane?: string;
115
114
  };
116
115
 
116
+ export type RadarPresenceReport = import("./generated-contracts.ts").PresenceReportDeserialize;
117
+ export type RadarPresenceAnswer = import("./generated-contracts.ts").PresenceAnswerSerialize;
118
+ export type RadarPresenceRelease = import("./generated-contracts.ts").PresenceReleaseDeserialize;
119
+
117
120
  export type RadarProcessClaim = {
118
121
  boot_id: string;
119
122
  pid: number;
@@ -204,7 +207,6 @@ export type RadarWriterBinding = {
204
207
  */
205
208
  export type RadarContextValue = {
206
209
  session: string | null;
207
- launch_identity?: LaunchIdentityMarkerSerialize;
208
210
  };
209
211
 
210
212
  /**
@@ -286,7 +288,7 @@ export type RadarEffectOutcome = "completed" | "refused" | "unknown";
286
288
  /**
287
289
  * One durable spawn edge as the daemon publishes it (`spawn.get`).
288
290
  *
289
- * `bound.agent_id` is the exact registry identity the child obtained with the
291
+ * `bound.registration_id` is the exact registry identity the child obtained with the
290
292
  * launch's private token, which is the only thing that says *this* child belongs
291
293
  * to *this* spawn: a label, a pane or a session UUID would each be a guess.
292
294
  */
@@ -296,7 +298,7 @@ export type RadarSpawnEdge = {
296
298
  created: RadarEffectOutcome;
297
299
  launched: RadarEffectOutcome;
298
300
  location?: RadarLocation;
299
- bound?: { source: string; incarnation: string; agent_id?: string };
301
+ bound?: { source: string; incarnation: string; registration_id?: string };
300
302
  exit_observation?: RadarSpawnExitObservation;
301
303
  };
302
304
 
@@ -307,7 +309,6 @@ export type RadarSpawnEdge = {
307
309
  * no exit was observed.
308
310
  */
309
311
  export type RadarSpawnExitObservation = SpawnExitObservationSerialize;
310
- export type RadarLaunchIdentityMarker = LaunchIdentityMarkerSerialize;
311
312
 
312
313
  /** Durable Radar-owned metadata accepted with a managed launch intent. */
313
314
  export type RadarManagedLaunchMetadata = ManagedLaunchMetadataDeserialize;
@@ -502,6 +503,43 @@ export function isUuid(value: unknown): value is string {
502
503
  return typeof value === "string" && UUID.test(value);
503
504
  }
504
505
 
506
+ function decodePresenceAnswer(value: unknown): RadarResult<RadarPresenceAnswer> {
507
+ const registrationId = recordAt(value, "registration_id");
508
+ const claimId = recordAt(value, "claim_id");
509
+ const standing = recordAt(value, "standing");
510
+ const subjectId = recordAt(value, "subject_id");
511
+ if (
512
+ !isUuid(registrationId) ||
513
+ !isUuid(claimId) ||
514
+ !["pending", "granted", "duplicate", "released"].includes(String(standing)) ||
515
+ (subjectId !== undefined && !isUuid(subjectId)) ||
516
+ (standing === "granted" && !isUuid(subjectId))
517
+ )
518
+ return protocol("presence reply carries no valid claim standing");
519
+ return { ok: true, value: value as RadarPresenceAnswer };
520
+ }
521
+
522
+ /**
523
+ * How Radar stands on a publisher's current session: adopted as a managed Pi
524
+ * session, rejected as a duplicate, still pending evidence/recovery, or not managed (yet).
525
+ */
526
+ export type RadarManagement =
527
+ | { state: "managed"; subject_id: string }
528
+ | { state: "duplicate" }
529
+ | { state: "pending" }
530
+ | { state: "unmanaged" };
531
+
532
+ function managementAt(value: unknown): RadarManagement | undefined {
533
+ const field = recordAt(value, "management");
534
+ const state = stringAt(field, "state");
535
+ if (state === "managed") {
536
+ const subjectId = stringAt(field, "subject_id");
537
+ return subjectId === undefined ? undefined : { state, subject_id: subjectId };
538
+ }
539
+ if (state === "duplicate" || state === "pending" || state === "unmanaged") return { state };
540
+ return undefined;
541
+ }
542
+
505
543
  function stringAt(value: unknown, field: string): string | undefined {
506
544
  if (typeof value !== "object" || value === null) return undefined;
507
545
  const found = (value as Record<string, unknown>)[field];
@@ -529,7 +567,7 @@ function handoffAt(value: unknown): HandoffSerialize | undefined {
529
567
  ? (value as Record<string, unknown>).correlation_id
530
568
  : undefined;
531
569
  if (
532
- version !== 1 || !isUuid(id) || !isUuid(stringAt(sender, "agent_id")) ||
570
+ version !== 1 || !isUuid(id) || !isUuid(stringAt(sender, "registration_id")) ||
533
571
  !isUuid(stringAt(sender, "session_id")) || !isUuid(stringAt(sender, "subject_id")) ||
534
572
  !isUuid(stringAt(sender, "execution_id")) || !Number.isSafeInteger(senderGeneration) ||
535
573
  (senderGeneration ?? 0) < 1 || !isUuid(target) || summary === undefined || content === undefined ||
@@ -548,7 +586,7 @@ function handoffReceiptAt(value: unknown): HandoffReceiptSerialize | undefined {
548
586
  const generation = numberAt(recipient, "execution_generation");
549
587
  const received = numberAt(value, "received_at_unix_ms");
550
588
  if (
551
- version !== 1 || !isUuid(handoffId) || !isUuid(stringAt(recipient, "agent_id")) ||
589
+ version !== 1 || !isUuid(handoffId) || !isUuid(stringAt(recipient, "registration_id")) ||
552
590
  !isUuid(stringAt(recipient, "session_id")) || !isUuid(stringAt(recipient, "subject_id")) ||
553
591
  !isUuid(stringAt(recipient, "execution_id")) || !Number.isSafeInteger(generation) ||
554
592
  (generation ?? 0) < 1 || received === undefined || !Number.isSafeInteger(received) || received < 0
@@ -764,7 +802,7 @@ function locationAt(value: unknown): RadarLocation | undefined {
764
802
  }
765
803
 
766
804
  /**
767
- * One durable edge, decoded strictly. The bound child's `agent_id` is kept only
805
+ * One durable edge, decoded strictly. The bound child's `registration_id` is kept only
768
806
  * when it is a canonical UUID: a legacy binding has none, and an unreadable one
769
807
  * is not an identity to correlate with.
770
808
  */
@@ -784,7 +822,7 @@ function spawnEdge(value: unknown): RadarResult<RadarSpawnEdge> {
784
822
  const bound = recordAt(value, "bound");
785
823
  const source = stringAt(bound, "source");
786
824
  const incarnation = stringAt(bound, "incarnation");
787
- const agentId = stringAt(bound, "agent_id");
825
+ const agentId = stringAt(bound, "registration_id");
788
826
  const exit = exitAt(recordAt(value, "exit_observation"));
789
827
  return {
790
828
  ok: true,
@@ -799,7 +837,7 @@ function spawnEdge(value: unknown): RadarResult<RadarSpawnEdge> {
799
837
  bound: {
800
838
  source,
801
839
  incarnation,
802
- ...(isUuid(agentId) ? { agent_id: agentId } : {}),
840
+ ...(isUuid(agentId) ? { registration_id: agentId } : {}),
803
841
  },
804
842
  }
805
843
  : {}),
@@ -1001,15 +1039,15 @@ export type RadarClient = {
1001
1039
  ping(): Promise<RadarResult<RadarHandshake>>;
1002
1040
  register(
1003
1041
  registration: RadarRegistration,
1004
- ): Promise<RadarResult<{ agent_id: string }>>;
1042
+ ): Promise<RadarResult<{ registration_id: string }>>;
1005
1043
  acquire(request: {
1006
- agent_id: string;
1044
+ registration_id: string;
1007
1045
  channel: RadarChannel;
1008
1046
  publisher: RadarPublisherIdentity;
1009
1047
  replace?: { generation: number; handle: string };
1010
1048
  }): Promise<RadarResult<RadarWriterBinding>>;
1011
1049
  publish(request: {
1012
- agent_id: string;
1050
+ registration_id: string;
1013
1051
  channel: RadarChannel;
1014
1052
  writer_handle: string;
1015
1053
  sequence: number;
@@ -1017,8 +1055,10 @@ export type RadarClient = {
1017
1055
  observed_at?: string;
1018
1056
  snapshot: RadarChannelSnapshot;
1019
1057
  }): Promise<RadarResult<{ sequence: number }>>;
1058
+ presence(request: RadarPresenceReport): Promise<RadarResult<RadarPresenceAnswer>>;
1059
+ releasePresence(request: RadarPresenceRelease): Promise<RadarResult<RadarPresenceAnswer>>;
1020
1060
  context(request: {
1021
- agent_id: string;
1061
+ registration_id: string;
1022
1062
  publisher: RadarPublisherIdentity;
1023
1063
  writer_handle?: string;
1024
1064
  replace?: { generation: number; handle: string };
@@ -1026,9 +1066,9 @@ export type RadarClient = {
1026
1066
  lease_ms?: number;
1027
1067
  observed_at?: string;
1028
1068
  context: RadarContextValue;
1029
- }): Promise<RadarResult<{ writer: RadarWriterBinding; warning?: string }>>;
1069
+ }): Promise<RadarResult<{ writer: RadarWriterBinding; warning?: string; management?: RadarManagement }>>;
1030
1070
  retire(request: {
1031
- agent_id: string;
1071
+ registration_id: string;
1032
1072
  channel: RadarChannel;
1033
1073
  writer_handle: string;
1034
1074
  }): Promise<RadarResult<void>>;
@@ -1061,15 +1101,15 @@ export type RadarClient = {
1061
1101
  }): Promise<RadarResult<RadarSpawnEdge>>;
1062
1102
  spawnGet(request_id: string): Promise<RadarResult<RadarSpawnTopology>>;
1063
1103
  /** The registered child's process verdict, read from its immutable claim. */
1064
- agentGet(agent_id: string): Promise<RadarResult<RadarAgentProcess>>;
1104
+ agentGet(registration_id: string): Promise<RadarResult<RadarAgentProcess>>;
1065
1105
  observe(): Promise<RadarResult<RadarInventory>>;
1066
1106
  /** Read the durable event journal after `after`, waiting at most `wait_ms`. */
1067
1107
  communicationPublish(request: HandoffPublishParamsDeserialize & {
1068
1108
  request_id: string;
1069
1109
  }): Promise<RadarResult<{ handoff: HandoffSerialize; created: boolean }>>;
1070
- communicationInbox(request: { agent_id: string }): Promise<RadarResult<HandoffInboxSerialize>>;
1071
- communicationGet(request: { agent_id: string; handoff_id: string }): Promise<RadarResult<{ handoff: HandoffSerialize; receipt: HandoffReceiptSerialize | null }>>;
1072
- communicationAck(request: { agent_id: string; handoff_id: string }): Promise<RadarResult<{ receipt: HandoffReceiptSerialize; created: boolean }>>;
1110
+ communicationInbox(request: { registration_id: string }): Promise<RadarResult<HandoffInboxSerialize>>;
1111
+ communicationGet(request: { registration_id: string; handoff_id: string }): Promise<RadarResult<{ handoff: HandoffSerialize; receipt: HandoffReceiptSerialize | null }>>;
1112
+ communicationAck(request: { registration_id: string; handoff_id: string }): Promise<RadarResult<{ receipt: HandoffReceiptSerialize; created: boolean }>>;
1073
1113
  events(request: {
1074
1114
  after: number;
1075
1115
  epoch?: string;
@@ -1468,6 +1508,24 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1468
1508
  return {
1469
1509
  socketPath,
1470
1510
  ping,
1511
+ async presence(request) {
1512
+ return withRegistry(async () => {
1513
+ const answer = await call("agent.presence", { ...request });
1514
+ if (!answer.ok) return answer;
1515
+ return decodePresenceAnswer(answer.value);
1516
+ });
1517
+ },
1518
+ async releasePresence(request) {
1519
+ return withRegistry(async () => {
1520
+ const answer = await call("agent.release", { ...request });
1521
+ if (!answer.ok) return answer;
1522
+ const decoded = decodePresenceAnswer(answer.value);
1523
+ if (!decoded.ok) return decoded;
1524
+ return decoded.value.standing === "released"
1525
+ ? decoded
1526
+ : protocol("release reply did not confirm the claim release");
1527
+ });
1528
+ },
1471
1529
  async register(registration) {
1472
1530
  return withRegistry(async () => {
1473
1531
  const answer = await call("agent.register", {
@@ -1487,15 +1545,15 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1487
1545
  });
1488
1546
  if (answer.ok === false) return answer;
1489
1547
  const record = (answer.value as { registration?: unknown }).registration;
1490
- const agentId = stringAt(record, "agent_id");
1491
- if (!isUuid(agentId)) return protocol("registration reply carries no agent_id");
1492
- return { ok: true, value: { agent_id: agentId } };
1548
+ const agentId = stringAt(record, "registration_id");
1549
+ if (!isUuid(agentId)) return protocol("registration reply carries no registration_id");
1550
+ return { ok: true, value: { registration_id: agentId } };
1493
1551
  });
1494
1552
  },
1495
1553
  async acquire(request) {
1496
1554
  return withRegistryChannel(request.channel, async () => {
1497
1555
  const answer = await call("agent.acquire", {
1498
- agent_id: request.agent_id,
1556
+ registration_id: request.registration_id,
1499
1557
  channel: request.channel,
1500
1558
  publisher: request.publisher,
1501
1559
  ...(request.replace !== undefined ? { replace: request.replace } : {}),
@@ -1524,7 +1582,7 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1524
1582
  }
1525
1583
  return withRegistryChannel(request.channel, async () => {
1526
1584
  const answer = await call("agent.publish", {
1527
- agent_id: request.agent_id,
1585
+ registration_id: request.registration_id,
1528
1586
  channel: request.channel,
1529
1587
  writer_handle: request.writer_handle,
1530
1588
  sequence: request.sequence,
@@ -1543,7 +1601,7 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1543
1601
  async context(request) {
1544
1602
  return withRegistry(async () => {
1545
1603
  const answer = await call("agent.context", {
1546
- agent_id: request.agent_id,
1604
+ registration_id: request.registration_id,
1547
1605
  publisher: request.publisher,
1548
1606
  ...(request.writer_handle !== undefined
1549
1607
  ? { writer_handle: request.writer_handle }
@@ -1563,9 +1621,14 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1563
1621
  if (writer === undefined)
1564
1622
  return protocol("context reply carries no writer binding");
1565
1623
  const warning = stringAt(answer.value, "warning");
1624
+ const management = managementAt(answer.value);
1566
1625
  return {
1567
1626
  ok: true,
1568
- value: { writer, ...(warning !== undefined ? { warning } : {}) },
1627
+ value: {
1628
+ writer,
1629
+ ...(warning !== undefined ? { warning } : {}),
1630
+ ...(management !== undefined ? { management } : {}),
1631
+ },
1569
1632
  };
1570
1633
  });
1571
1634
  },
@@ -1658,7 +1721,7 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1658
1721
  message: "agent.get requires a canonical agent UUID",
1659
1722
  };
1660
1723
  return withRegistry(async () => {
1661
- const answer = await call("agent.get", { agent_id: agentId });
1724
+ const answer = await call("agent.get", { registration_id: agentId });
1662
1725
  if (answer.ok === false) return answer;
1663
1726
  return agentProcessAt(recordAt(answer.value, "agent"));
1664
1727
  });
@@ -1724,7 +1787,7 @@ export function createRadarClient(options: RadarClientOptions = {}): RadarClient
1724
1787
  async communicationPublish(request) {
1725
1788
  return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1726
1789
  const answer = await call("communication.publish", {
1727
- sender_agent_id: request.sender_agent_id,
1790
+ sender_registration_id: request.sender_registration_id,
1728
1791
  recipient_subject_id: request.recipient_subject_id,
1729
1792
  ...(request.correlation_id !== undefined ? { correlation_id: request.correlation_id } : {}),
1730
1793
  summary: request.summary,
@@ -31,7 +31,7 @@ export function createRadarHandoffPort(
31
31
  const subject = await publication.subject();
32
32
  return subject === undefined
33
33
  ? { ok: false, code: "absent", message: "this process has no current Radar registration" }
34
- : { ok: true, value: subject.agent_id };
34
+ : { ok: true, value: subject.registration_id };
35
35
  }
36
36
 
37
37
  return {
@@ -43,24 +43,24 @@ export function createRadarHandoffPort(
43
43
  return { ok: false, code: "absent", message: "this managed execution has no Radar parent subject" };
44
44
  return client.communicationPublish({
45
45
  ...request,
46
- sender_agent_id: identity.value,
46
+ sender_registration_id: identity.value,
47
47
  recipient_subject_id: parent,
48
48
  });
49
49
  },
50
50
  async inbox() {
51
51
  const identity = await agentId();
52
52
  if (identity.ok === false) return identity;
53
- return client.communicationInbox({ agent_id: identity.value });
53
+ return client.communicationInbox({ registration_id: identity.value });
54
54
  },
55
55
  async get(handoff_id) {
56
56
  const identity = await agentId();
57
57
  if (identity.ok === false) return identity;
58
- return client.communicationGet({ agent_id: identity.value, handoff_id });
58
+ return client.communicationGet({ registration_id: identity.value, handoff_id });
59
59
  },
60
60
  async ack(handoff_id) {
61
61
  const identity = await agentId();
62
62
  if (identity.ok === false) return identity;
63
- return client.communicationAck({ agent_id: identity.value, handoff_id });
63
+ return client.communicationAck({ registration_id: identity.value, handoff_id });
64
64
  },
65
65
  };
66
66
  }
@@ -3,7 +3,6 @@ import {
3
3
  isUuid,
4
4
  type RadarScheduler,
5
5
  type RadarSnapshot,
6
- type RadarLaunchIdentityMarker,
7
6
  } from "./radar-client.ts";
8
7
  import type { RadarLocationResolution } from "./radar-location.ts";
9
8
  import type { RadarFactsSnapshot } from "./radar-client.ts";
@@ -20,11 +19,6 @@ import {
20
19
  type RadarPublication,
21
20
  type RadarChannelWriter,
22
21
  } from "./radar-publication.ts";
23
- import {
24
- activeRadarLaunchMarker,
25
- persistRadarLaunchMarker,
26
- radarLaunchMarkerFromEnvironment,
27
- } from "./radar-session-marker.ts";
28
22
 
29
23
  /**
30
24
  * The minimal Pi surface this adapter uses. It is declared structurally so the
@@ -32,7 +26,6 @@ import {
32
26
  * be lifted into its own extension without touching this module.
33
27
  */
34
28
  export type RadarExecutionEventApi = {
35
- appendEntry?: (customType: string, data?: unknown) => void;
36
29
  on(name: string, handler: (event: any, context: any) => unknown): unknown;
37
30
  events?: {
38
31
  on(name: string, handler: (event: any) => unknown): unknown;
@@ -70,10 +63,8 @@ export type RadarExecutionOptions = {
70
63
  * where no such event exists.
71
64
  */
72
65
  questionnaireEvent?: string;
73
- /** Best-effort diagnostic for durable session marker failures. */
66
+ /** Best-effort diagnostic for context publication failures. */
74
67
  diagnostic?: (message: string) => void;
75
- /** Injectable only for tests; production reads the daemon-issued process env. */
76
- markerEnvironment?: Record<string, string | undefined>;
77
68
  };
78
69
 
79
70
  export type RadarExecutionAdapter = {
@@ -138,10 +129,13 @@ function unavailablePublication(): RadarPublication {
138
129
  return {
139
130
  execution: writer,
140
131
  facts: { ...writer, update: () => undefined },
141
- context: {
142
- update: () => undefined,
132
+ presence: {
133
+ report: () => undefined,
143
134
  stop: () => undefined,
144
135
  reopen: () => undefined,
136
+ management: () => undefined,
137
+ ensure: async () => undefined,
138
+ release: async () => undefined,
145
139
  diagnostic: () => "Radar private storage is unavailable",
146
140
  },
147
141
  assignment: () => writer,
@@ -269,14 +263,11 @@ export function createRadarExecutionAdapter(
269
263
  * canonical UUID is left unreported: absent context is unknown, and publishing
270
264
  * the explicit null would claim there is no session at all.
271
265
  */
272
- function reportSession(
273
- context: any,
274
- launchIdentity?: RadarLaunchIdentityMarker,
275
- ): void {
266
+ function reportSession(context: any): void {
276
267
  const session = context?.sessionManager?.getSessionId?.();
277
268
  if (!isUuid(session)) return;
278
269
  try {
279
- publication.context.update(session, launchIdentity);
270
+ publication.presence.report(session);
280
271
  } catch {
281
272
  // Optional publication must not interfere with Pi's lifecycle handler.
282
273
  }
@@ -360,49 +351,10 @@ export function createRadarExecutionAdapter(
360
351
  );
361
352
 
362
353
  on("session_start", (_event, context) => {
363
- const sessionHost = {
364
- appendEntry: pi.appendEntry
365
- ? (customType: string, data?: unknown) => pi.appendEntry?.(customType, data)
366
- : undefined,
367
- sessionManager: context?.sessionManager,
368
- };
369
- let launchIdentity: RadarLaunchIdentityMarker | undefined;
370
- try {
371
- const result = persistRadarLaunchMarker(
372
- sessionHost,
373
- options.markerEnvironment,
374
- );
375
- if (result === "written" || result === "unchanged") {
376
- const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
377
- const activeMarker = activeRadarLaunchMarker(sessionHost);
378
- if (
379
- expected &&
380
- activeMarker.status === "valid" &&
381
- JSON.stringify(activeMarker.marker) === JSON.stringify(expected)
382
- ) {
383
- launchIdentity = activeMarker.marker;
384
- } else {
385
- options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
386
- }
387
- } else if (result === "unsupported") {
388
- options.diagnostic?.("Pi host does not support Radar launch identity session entries");
389
- } else if (
390
- options.markerEnvironment &&
391
- [
392
- "PI_RADAR_MANAGED_SUBJECT_ID",
393
- "PI_RADAR_MANAGED_LAUNCH_ID",
394
- "PI_RADAR_MANAGED_EXECUTION_GENERATION",
395
- ].some((key) => options.markerEnvironment?.[key] !== undefined)
396
- ) {
397
- options.diagnostic?.("incomplete or invalid Radar launch identity environment");
398
- }
399
- } catch (error) {
400
- options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
401
- }
402
354
  rootSession = true;
403
355
  // The session comes first: re-arming the publication must renew the session
404
356
  // this process is in now, never the one a previous session left behind.
405
- reportSession(context, launchIdentity);
357
+ reportSession(context);
406
358
  // Facts follow the root flag, so they are published only after it is set.
407
359
  publishFacts(context);
408
360
  // A process can replace or resume its session without a new process, so the
@@ -450,12 +402,13 @@ export function createRadarExecutionAdapter(
450
402
  emit();
451
403
  });
452
404
 
453
- on("session_shutdown", () => {
405
+ on("session_shutdown", async () => {
454
406
  // A session can end or be replaced without ending this process, and Pi keeps
455
407
  // the same extension instance across that: the listeners stay registered so
456
408
  // the next session_start re-arms the same process publication. Only the
457
409
  // timers and this session's facts are released.
458
410
  suspend();
411
+ await publication.presence.release();
459
412
  });
460
413
 
461
414
  function suspend(): void {
@@ -46,7 +46,7 @@ export type LaunchMailbox<State extends LaunchState> = {
46
46
  * the child's exact command into it, and still reports that pane in its own
47
47
  * inventory;
48
48
  * 2. a child registered with the launch's private token, so the edge names the
49
- * exact `agent_id` that child obtained — never a label, pane or session;
49
+ * exact `registration_id` that child obtained — never a label, pane or session;
50
50
  * 3. that child wrote a fresh startup acknowledgment for this launch generation
51
51
  * after Herdsman startup finished, naming the pane the daemon created as its
52
52
  * own location.
@@ -66,7 +66,7 @@ export type LaunchMailbox<State extends LaunchState> = {
66
66
 
67
67
  /** The exact managed parent this launch may use, and nothing inferred about it. */
68
68
  export type RadarLaunchParent = {
69
- agent_id: string;
69
+ registration_id: string;
70
70
  /** Stable subject from this process's current Radar registration. */
71
71
  subject_id: string;
72
72
  location?: RadarLocation;
@@ -109,12 +109,12 @@ export type RadarWorkerLaunch = {
109
109
  * because a binding that was not persisted is one no later reader can hold the
110
110
  * child by.
111
111
  */
112
- onBound?: (bound: { agent_id: string; pane_id: string }) => void;
112
+ onBound?: (bound: { registration_id: string; pane_id: string }) => void;
113
113
  };
114
114
 
115
115
  export type RadarWorkerLaunched<State = unknown> = {
116
116
  request_id: string;
117
- agent_id: string;
117
+ registration_id: string;
118
118
  pane_id: string;
119
119
  /**
120
120
  * The process the daemon bound this launch's edge to, taken from that same
@@ -143,7 +143,7 @@ export type RadarLaunchOutcome<State = unknown> =
143
143
  */
144
144
  export type RadarChildEvidence = {
145
145
  request_id: string;
146
- agent_id: string;
146
+ registration_id: string;
147
147
  source: string;
148
148
  incarnation: string;
149
149
  pane_id: string;
@@ -192,12 +192,12 @@ export async function observeRadarChild(
192
192
  provenance: {
193
193
  request_id: string;
194
194
  pane_id: string | undefined;
195
- agent_id: string | undefined;
195
+ registration_id: string | undefined;
196
196
  /** The session this launch's mailbox records for the child. */
197
197
  session_id: string | undefined;
198
198
  },
199
199
  ): Promise<RadarChildObservation> {
200
- if (provenance.agent_id === undefined)
200
+ if (provenance.registration_id === undefined)
201
201
  return unknownChild("not bound to a child by this launch");
202
202
  // A launch that recorded no pane has nothing to compare the daemon's read
203
203
  // against: the child's own claim about where it runs is not where the launch
@@ -211,11 +211,11 @@ export async function observeRadarChild(
211
211
  );
212
212
  const topology = read.value;
213
213
  const edge = topology.edge;
214
- if (edge.bound === undefined || edge.bound.agent_id === undefined)
214
+ if (edge.bound === undefined || edge.bound.registration_id === undefined)
215
215
  return unknownChild("not bound to a child by the daemon");
216
- if (edge.bound.agent_id !== provenance.agent_id)
216
+ if (edge.bound.registration_id !== provenance.registration_id)
217
217
  return unknownChild(
218
- `bound to another subject (${edge.bound.agent_id}), not to ${provenance.agent_id}`,
218
+ `bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`,
219
219
  );
220
220
  if (edge.location?.pane === undefined)
221
221
  return unknownChild("recorded without a pane");
@@ -241,7 +241,7 @@ export async function observeRadarChild(
241
241
  // The edge says where the child was bound, not that its process still runs.
242
242
  // The registry's verdict on the bound subject, and the location it registered
243
243
  // with, are what prove the child live.
244
- const reply = await client.agentGet(edge.bound.agent_id);
244
+ const reply = await client.agentGet(edge.bound.registration_id);
245
245
  if (reply.ok === false)
246
246
  return unknownChild(
247
247
  `process verdict not readable (${reply.code}: ${reply.message})`,
@@ -271,7 +271,7 @@ export async function observeRadarChild(
271
271
  kind: "live",
272
272
  evidence: {
273
273
  request_id: provenance.request_id,
274
- agent_id: edge.bound.agent_id,
274
+ registration_id: edge.bound.registration_id,
275
275
  source: edge.bound.source,
276
276
  incarnation: edge.bound.incarnation,
277
277
  pane_id: edge.location.pane,
@@ -393,7 +393,7 @@ export async function launchRadarWorker<State extends LaunchState>(
393
393
  const deadline = Date.now() + options.timeoutMs;
394
394
  const remaining = (): number => Math.max(0, deadline - Date.now());
395
395
  const pane = launch.parent.location?.pane;
396
- if (!isUuid(launch.parent.agent_id))
396
+ if (!isUuid(launch.parent.registration_id))
397
397
  return refusedLaunch(
398
398
  "the launch has no exact parent registration to spawn under",
399
399
  );
@@ -411,7 +411,7 @@ export async function launchRadarWorker<State extends LaunchState>(
411
411
 
412
412
  const spawned = await client.spawn({
413
413
  request_id: launch.request_id,
414
- parent: launch.parent.agent_id,
414
+ parent: launch.parent.registration_id,
415
415
  parent_session_id: launch.parent_session_id,
416
416
  executable: launch.executable,
417
417
  managed: { parent_subject_id: launch.parent.subject_id },
@@ -437,13 +437,13 @@ export async function launchRadarWorker<State extends LaunchState>(
437
437
  ...(signal !== undefined ? { signal } : {}),
438
438
  });
439
439
  if (bound.kind !== "bound") return bound;
440
- const agentId = bound.agent_id;
440
+ const agentId = bound.registration_id;
441
441
  // The binding is recorded before the readiness wait, not after it: the child
442
442
  // may never acknowledge, and the subject and pane the daemon named are still
443
443
  // what a reload has to prove this child by.
444
444
  if (launch.onBound !== undefined) {
445
445
  try {
446
- launch.onBound({ agent_id: agentId, pane_id: bound.pane_id });
446
+ launch.onBound({ registration_id: agentId, pane_id: bound.pane_id });
447
447
  } catch (error) {
448
448
  return unresolvedLaunch(
449
449
  launch.request_id,
@@ -475,7 +475,7 @@ export async function launchRadarWorker<State extends LaunchState>(
475
475
  kind: "ready",
476
476
  value: {
477
477
  request_id: launch.request_id,
478
- agent_id: agentId,
478
+ registration_id: agentId,
479
479
  pane_id: bound.pane_id,
480
480
  bound_process: {
481
481
  source: bound.source,
@@ -498,7 +498,7 @@ export async function launchRadarWorker<State extends LaunchState>(
498
498
  type BoundChild =
499
499
  | {
500
500
  kind: "bound";
501
- agent_id: string;
501
+ registration_id: string;
502
502
  pane_id: string;
503
503
  source: string;
504
504
  incarnation: string;
@@ -509,7 +509,7 @@ type BoundChild =
509
509
  * Poll the edge this request id authored until a child presents its token.
510
510
  *
511
511
  * The daemon binds the edge once, to the exact registration the token produced,
512
- * so `bound.agent_id` is the child identity. A refusal or an unknown launch
512
+ * so `bound.registration_id` is the child identity. A refusal or an unknown launch
513
513
  * effect ends the wait at once: neither becomes a child by waiting longer. An
514
514
  * edge whose recorded pane the daemon no longer reports ends it too: waiting
515
515
  * cannot create the pane a launch claims, and a location read afterwards would
@@ -559,7 +559,7 @@ async function waitForChild(
559
559
  requestId,
560
560
  "the daemon observed the launched child's process exit",
561
561
  );
562
- const agentId = edge.bound?.agent_id;
562
+ const agentId = edge.bound?.registration_id;
563
563
  if (agentId !== undefined && isUuid(agentId)) {
564
564
  // A child the daemon bound without the process it registered as is a
565
565
  // child no assignment can be addressed to, and an unaddressed Radar
@@ -579,7 +579,7 @@ async function waitForChild(
579
579
  );
580
580
  return {
581
581
  kind: "bound",
582
- agent_id: agentId,
582
+ registration_id: agentId,
583
583
  pane_id: pane,
584
584
  source,
585
585
  incarnation,