pi-radar 0.2.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 (42) hide show
  1. package/README.md +10 -4
  2. package/dist/extension/baseline.js +333 -394
  3. package/dist/extension/radar-client.js +126 -11
  4. package/dist/extension/radar-communication.js +39 -0
  5. package/dist/extension/radar-execution.js +169 -382
  6. package/dist/extension/radar-launch.js +18 -13
  7. package/dist/extension/radar-publication.js +161 -298
  8. package/dist/extension/radar-runtime.js +1 -1
  9. package/dist/index.js +356 -407
  10. package/extension/baseline.ts +2 -0
  11. package/extension/generated-contracts.ts +1310 -10
  12. package/extension/radar-client.ts +191 -23
  13. package/extension/radar-communication.ts +66 -0
  14. package/extension/radar-execution.ts +11 -58
  15. package/extension/radar-launch.ts +29 -22
  16. package/extension/radar-publication.ts +211 -410
  17. package/extension/radar-runtime.ts +4 -1
  18. package/index.ts +1 -0
  19. package/package.json +9 -5
  20. package/schema/public/v1/handoff-inbox.deserialize.schema.json +18 -0
  21. package/schema/public/v1/handoff-inbox.serialize.schema.json +18 -0
  22. package/schema/public/v1/handoff-publish-params.deserialize.schema.json +33 -0
  23. package/schema/public/v1/handoff-publish-params.serialize.schema.json +33 -0
  24. package/schema/public/v1/handoff-receipt.deserialize.schema.json +66 -0
  25. package/schema/public/v1/handoff-receipt.serialize.schema.json +66 -0
  26. package/schema/public/v1/handoff.deserialize.schema.json +84 -0
  27. package/schema/public/v1/handoff.serialize.schema.json +84 -0
  28. package/schema/public/v1/observation-get-params.deserialize.schema.json +46 -0
  29. package/schema/public/v1/observation-get-params.serialize.schema.json +52 -0
  30. package/schema/public/v1/observation-get-response.deserialize.schema.json +2731 -0
  31. package/schema/public/v1/observation-get-response.serialize.schema.json +2862 -0
  32. package/schema/public/v1/observation-snapshot.deserialize.schema.json +2616 -0
  33. package/schema/public/v1/observation-snapshot.serialize.schema.json +2741 -0
  34. package/schema/public/v1/presence-answer.deserialize.schema.json +40 -0
  35. package/schema/public/v1/presence-answer.serialize.schema.json +40 -0
  36. package/schema/public/v1/presence-release.deserialize.schema.json +51 -0
  37. package/schema/public/v1/presence-release.serialize.schema.json +52 -0
  38. package/schema/public/v1/presence-report.deserialize.schema.json +149 -0
  39. package/schema/public/v1/presence-report.serialize.schema.json +157 -0
  40. package/extension/radar-session-marker.ts +0 -146
  41. package/schema/public/v1/launch-identity-marker.deserialize.schema.json +0 -32
  42. package/schema/public/v1/launch-identity-marker.serialize.schema.json +0 -32
@@ -15,6 +15,7 @@ var RADAR_SPAWN_CONTRACT = "spawn.v1";
15
15
  var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
16
16
  var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
17
17
  var RADAR_EVENTS_CONTRACT = "events.v1";
18
+ var RADAR_COMMUNICATION_CONTRACT = "communication.handoff.v1";
18
19
  var RADAR_REQUESTER = "pi-radar";
19
20
  var RADAR_MAX_LINE_BYTES = 1024 * 1024;
20
21
  var RADAR_CALL_TIMEOUT_MS = 2e3;
@@ -112,6 +113,25 @@ function recordAt(value, field) {
112
113
  function isUuid(value) {
113
114
  return typeof value === "string" && UUID.test(value);
114
115
  }
116
+ function decodePresenceAnswer(value) {
117
+ const registrationId = recordAt(value, "registration_id");
118
+ const claimId = recordAt(value, "claim_id");
119
+ const standing = recordAt(value, "standing");
120
+ const subjectId = recordAt(value, "subject_id");
121
+ if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
122
+ return protocol("presence reply carries no valid claim standing");
123
+ return { ok: true, value };
124
+ }
125
+ function managementAt(value) {
126
+ const field = recordAt(value, "management");
127
+ const state = stringAt(field, "state");
128
+ if (state === "managed") {
129
+ const subjectId = stringAt(field, "subject_id");
130
+ return subjectId === void 0 ? void 0 : { state, subject_id: subjectId };
131
+ }
132
+ if (state === "duplicate" || state === "pending" || state === "unmanaged") return { state };
133
+ return void 0;
134
+ }
115
135
  function stringAt(value, field) {
116
136
  if (typeof value !== "object" || value === null) return void 0;
117
137
  const found = value[field];
@@ -122,6 +142,28 @@ function numberAt(value, field) {
122
142
  const found = value[field];
123
143
  return typeof found === "number" && Number.isFinite(found) ? found : void 0;
124
144
  }
145
+ function handoffAt(value) {
146
+ const version = numberAt(value, "version");
147
+ const id = stringAt(value, "id");
148
+ const sender = recordAt(value, "sender");
149
+ const senderGeneration = numberAt(sender, "execution_generation");
150
+ const target = stringAt(value, "recipient_subject_id");
151
+ const summary = stringAt(value, "summary");
152
+ const content = stringAt(value, "content");
153
+ const published = numberAt(value, "published_at_unix_ms");
154
+ const correlation = typeof value === "object" && value !== null ? value.correlation_id : void 0;
155
+ if (version !== 1 || !isUuid(id) || !isUuid(stringAt(sender, "registration_id")) || !isUuid(stringAt(sender, "session_id")) || !isUuid(stringAt(sender, "subject_id")) || !isUuid(stringAt(sender, "execution_id")) || !Number.isSafeInteger(senderGeneration) || (senderGeneration ?? 0) < 1 || !isUuid(target) || summary === void 0 || content === void 0 || published === void 0 || !Number.isSafeInteger(published) || published < 0 || correlation !== void 0 && correlation !== null && (typeof correlation !== "string" || correlation.length === 0 || Buffer.byteLength(correlation, "utf8") > 256 || new RegExp("\\p{Cc}", "u").test(correlation))) return void 0;
156
+ return value;
157
+ }
158
+ function handoffReceiptAt(value) {
159
+ const version = numberAt(value, "version");
160
+ const handoffId = stringAt(value, "handoff_id");
161
+ const recipient = recordAt(value, "recipient");
162
+ const generation = numberAt(recipient, "execution_generation");
163
+ const received = numberAt(value, "received_at_unix_ms");
164
+ if (version !== 1 || !isUuid(handoffId) || !isUuid(stringAt(recipient, "registration_id")) || !isUuid(stringAt(recipient, "session_id")) || !isUuid(stringAt(recipient, "subject_id")) || !isUuid(stringAt(recipient, "execution_id")) || !Number.isSafeInteger(generation) || (generation ?? 0) < 1 || received === void 0 || !Number.isSafeInteger(received) || received < 0) return void 0;
165
+ return value;
166
+ }
125
167
  function writerBinding(value) {
126
168
  const handle = stringAt(value, "handle");
127
169
  const source = stringAt(value, "source");
@@ -227,7 +269,8 @@ var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
227
269
  var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
228
270
  "PI_RADAR_MANAGED_SUBJECT_ID",
229
271
  "PI_RADAR_MANAGED_LAUNCH_ID",
230
- "PI_RADAR_MANAGED_EXECUTION_GENERATION"
272
+ "PI_RADAR_MANAGED_EXECUTION_GENERATION",
273
+ "PI_RADAR_MANAGED_PARENT_SUBJECT_ID"
231
274
  ]);
232
275
  function unresolvedSpawn(requestId, detail) {
233
276
  return {
@@ -272,7 +315,7 @@ function spawnEdge(value) {
272
315
  const bound = recordAt(value, "bound");
273
316
  const source = stringAt(bound, "source");
274
317
  const incarnation = stringAt(bound, "incarnation");
275
- const agentId = stringAt(bound, "agent_id");
318
+ const agentId = stringAt(bound, "registration_id");
276
319
  const exit = exitAt(recordAt(value, "exit_observation"));
277
320
  return {
278
321
  ok: true,
@@ -286,7 +329,7 @@ function spawnEdge(value) {
286
329
  bound: {
287
330
  source,
288
331
  incarnation,
289
- ...isUuid(agentId) ? { agent_id: agentId } : {}
332
+ ...isUuid(agentId) ? { registration_id: agentId } : {}
290
333
  }
291
334
  } : {},
292
335
  ...exit !== void 0 ? { exit_observation: exit } : {}
@@ -626,6 +669,22 @@ function createRadarClient(options = {}) {
626
669
  return {
627
670
  socketPath,
628
671
  ping,
672
+ async presence(request) {
673
+ return withRegistry(async () => {
674
+ const answer = await call("agent.presence", { ...request });
675
+ if (!answer.ok) return answer;
676
+ return decodePresenceAnswer(answer.value);
677
+ });
678
+ },
679
+ async releasePresence(request) {
680
+ return withRegistry(async () => {
681
+ const answer = await call("agent.release", { ...request });
682
+ if (!answer.ok) return answer;
683
+ const decoded = decodePresenceAnswer(answer.value);
684
+ if (!decoded.ok) return decoded;
685
+ return decoded.value.standing === "released" ? decoded : protocol("release reply did not confirm the claim release");
686
+ });
687
+ },
629
688
  async register(registration) {
630
689
  return withRegistry(async () => {
631
690
  const answer = await call("agent.register", {
@@ -641,15 +700,15 @@ function createRadarClient(options = {}) {
641
700
  });
642
701
  if (answer.ok === false) return answer;
643
702
  const record = answer.value.registration;
644
- const agentId = stringAt(record, "agent_id");
645
- if (!isUuid(agentId)) return protocol("registration reply carries no agent_id");
646
- return { ok: true, value: { agent_id: agentId } };
703
+ const agentId = stringAt(record, "registration_id");
704
+ if (!isUuid(agentId)) return protocol("registration reply carries no registration_id");
705
+ return { ok: true, value: { registration_id: agentId } };
647
706
  });
648
707
  },
649
708
  async acquire(request) {
650
709
  return withRegistryChannel(request.channel, async () => {
651
710
  const answer = await call("agent.acquire", {
652
- agent_id: request.agent_id,
711
+ registration_id: request.registration_id,
653
712
  channel: request.channel,
654
713
  publisher: request.publisher,
655
714
  ...request.replace !== void 0 ? { replace: request.replace } : {}
@@ -678,7 +737,7 @@ function createRadarClient(options = {}) {
678
737
  }
679
738
  return withRegistryChannel(request.channel, async () => {
680
739
  const answer = await call("agent.publish", {
681
- agent_id: request.agent_id,
740
+ registration_id: request.registration_id,
682
741
  channel: request.channel,
683
742
  writer_handle: request.writer_handle,
684
743
  sequence: request.sequence,
@@ -697,7 +756,7 @@ function createRadarClient(options = {}) {
697
756
  async context(request) {
698
757
  return withRegistry(async () => {
699
758
  const answer = await call("agent.context", {
700
- agent_id: request.agent_id,
759
+ registration_id: request.registration_id,
701
760
  publisher: request.publisher,
702
761
  ...request.writer_handle !== void 0 ? { writer_handle: request.writer_handle } : {},
703
762
  ...request.replace !== void 0 ? { replace: request.replace } : {},
@@ -713,9 +772,14 @@ function createRadarClient(options = {}) {
713
772
  if (writer === void 0)
714
773
  return protocol("context reply carries no writer binding");
715
774
  const warning = stringAt(answer.value, "warning");
775
+ const management = managementAt(answer.value);
716
776
  return {
717
777
  ok: true,
718
- value: { writer, ...warning !== void 0 ? { warning } : {} }
778
+ value: {
779
+ writer,
780
+ ...warning !== void 0 ? { warning } : {},
781
+ ...management !== void 0 ? { management } : {}
782
+ }
719
783
  };
720
784
  });
721
785
  },
@@ -792,7 +856,7 @@ function createRadarClient(options = {}) {
792
856
  message: "agent.get requires a canonical agent UUID"
793
857
  };
794
858
  return withRegistry(async () => {
795
- const answer = await call("agent.get", { agent_id: agentId });
859
+ const answer = await call("agent.get", { registration_id: agentId });
796
860
  if (answer.ok === false) return answer;
797
861
  return agentProcessAt(recordAt(answer.value, "agent"));
798
862
  });
@@ -843,6 +907,56 @@ function createRadarClient(options = {}) {
843
907
  return protocol("spawn.close reply carries no close outcome");
844
908
  });
845
909
  },
910
+ async communicationPublish(request) {
911
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
912
+ const answer = await call("communication.publish", {
913
+ sender_registration_id: request.sender_registration_id,
914
+ recipient_subject_id: request.recipient_subject_id,
915
+ ...request.correlation_id !== void 0 ? { correlation_id: request.correlation_id } : {},
916
+ summary: request.summary,
917
+ content: request.content
918
+ }, request.request_id);
919
+ if (answer.ok === false) return answer;
920
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
921
+ const created = recordAt(answer.value, "created");
922
+ if (handoff === void 0 || typeof created !== "boolean")
923
+ return protocol("communication.publish reply carries no handoff");
924
+ return { ok: true, value: { handoff, created } };
925
+ });
926
+ },
927
+ async communicationInbox(request) {
928
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
929
+ const answer = await call("communication.inbox", request);
930
+ if (answer.ok === false) return answer;
931
+ const handoffIds = recordAt(answer.value, "handoff_ids");
932
+ if (!Array.isArray(handoffIds) || handoffIds.some((id) => !isUuid(id)))
933
+ return protocol("communication.inbox reply carries malformed handoff IDs");
934
+ return { ok: true, value: answer.value };
935
+ });
936
+ },
937
+ async communicationGet(request) {
938
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
939
+ const answer = await call("communication.get", request);
940
+ if (answer.ok === false) return answer;
941
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
942
+ const receiptValue = recordAt(answer.value, "receipt");
943
+ const receipt = receiptValue === null ? null : handoffReceiptAt(receiptValue);
944
+ if (handoff === void 0 || receiptValue !== null && receipt === void 0)
945
+ return protocol("communication.get reply carries malformed content or receipt");
946
+ return { ok: true, value: { handoff, receipt: receipt ?? null } };
947
+ });
948
+ },
949
+ async communicationAck(request) {
950
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
951
+ const answer = await call("communication.ack", request);
952
+ if (answer.ok === false) return answer;
953
+ const receipt = handoffReceiptAt(recordAt(answer.value, "receipt"));
954
+ const created = recordAt(answer.value, "created");
955
+ if (receipt === void 0 || typeof created !== "boolean")
956
+ return protocol("communication.ack reply carries no receipt");
957
+ return { ok: true, value: { receipt, created } };
958
+ });
959
+ },
846
960
  async events(request) {
847
961
  if (!Number.isSafeInteger(request.after) || request.after < 0 || !Number.isSafeInteger(request.wait_ms) || request.wait_ms < 0 || request.wait_ms > RADAR_EVENT_WAIT_MS)
848
962
  return {
@@ -1063,6 +1177,43 @@ function boundedRadarSnapshot(snapshot) {
1063
1177
  };
1064
1178
  }
1065
1179
 
1180
+ // extension/radar-communication.ts
1181
+ function createRadarHandoffPort(client, publication, parentSubjectId = () => process.env.PI_RADAR_MANAGED_PARENT_SUBJECT_ID) {
1182
+ async function agentId() {
1183
+ const subject = await publication.subject();
1184
+ return subject === void 0 ? { ok: false, code: "absent", message: "this process has no current Radar registration" } : { ok: true, value: subject.registration_id };
1185
+ }
1186
+ return {
1187
+ async publish(request) {
1188
+ const identity = await agentId();
1189
+ if (identity.ok === false) return identity;
1190
+ const parent = parentSubjectId();
1191
+ if (parent === void 0)
1192
+ return { ok: false, code: "absent", message: "this managed execution has no Radar parent subject" };
1193
+ return client.communicationPublish({
1194
+ ...request,
1195
+ sender_registration_id: identity.value,
1196
+ recipient_subject_id: parent
1197
+ });
1198
+ },
1199
+ async inbox() {
1200
+ const identity = await agentId();
1201
+ if (identity.ok === false) return identity;
1202
+ return client.communicationInbox({ registration_id: identity.value });
1203
+ },
1204
+ async get(handoff_id) {
1205
+ const identity = await agentId();
1206
+ if (identity.ok === false) return identity;
1207
+ return client.communicationGet({ registration_id: identity.value, handoff_id });
1208
+ },
1209
+ async ack(handoff_id) {
1210
+ const identity = await agentId();
1211
+ if (identity.ok === false) return identity;
1212
+ return client.communicationAck({ registration_id: identity.value, handoff_id });
1213
+ }
1214
+ };
1215
+ }
1216
+
1066
1217
  // extension/radar-publication.ts
1067
1218
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
1068
1219
  import {
@@ -1079,77 +1230,7 @@ import {
1079
1230
  writeSync
1080
1231
  } from "node:fs";
1081
1232
  import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
1082
-
1083
- // extension/radar-session-marker.ts
1084
- var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
1085
- var RADAR_LAUNCH_MARKER_VERSION = 1;
1086
- var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
1087
- var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
1088
- var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
1089
- var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
1090
- var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
1091
- function decodeRadarLaunchMarker(value) {
1092
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1093
- const record = value;
1094
- if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
1095
- return void 0;
1096
- }
1097
- if (record.version !== RADAR_LAUNCH_MARKER_VERSION || typeof record.subject_id !== "string" || !UUID2.test(record.subject_id) || typeof record.launch_id !== "string" || !UUID2.test(record.launch_id) || !Number.isSafeInteger(record.execution_generation) || record.execution_generation < 1 || record.execution_generation > RADAR_MAX_EXECUTION_GENERATION) {
1098
- return void 0;
1099
- }
1100
- return {
1101
- version: RADAR_LAUNCH_MARKER_VERSION,
1102
- subject_id: record.subject_id,
1103
- launch_id: record.launch_id,
1104
- execution_generation: record.execution_generation
1105
- };
1106
- }
1107
- function appendRadarLaunchMarker(appendEntry, marker) {
1108
- const validated = decodeRadarLaunchMarker(marker);
1109
- if (!validated) throw new Error("invalid Radar launch identity marker");
1110
- appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
1111
- }
1112
- function latestRadarLaunchMarker(branch) {
1113
- let latest;
1114
- for (const entry of branch) {
1115
- if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
1116
- latest = entry;
1117
- }
1118
- }
1119
- if (!latest) return { status: "absent" };
1120
- const marker = decodeRadarLaunchMarker(latest.data);
1121
- return marker ? { status: "valid", marker } : { status: "invalid" };
1122
- }
1123
- function radarLaunchMarkerFromEnvironment(env = process.env) {
1124
- const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
1125
- if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
1126
- return void 0;
1127
- }
1128
- return decodeRadarLaunchMarker({
1129
- version: RADAR_LAUNCH_MARKER_VERSION,
1130
- subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
1131
- launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
1132
- execution_generation: Number(rawGeneration)
1133
- });
1134
- }
1135
- function activeRadarLaunchMarker(host) {
1136
- if (!host.sessionManager) return { status: "absent" };
1137
- return latestRadarLaunchMarker(host.sessionManager.getBranch());
1138
- }
1139
- function persistRadarLaunchMarker(host, env = process.env) {
1140
- const marker = radarLaunchMarkerFromEnvironment(env);
1141
- if (!marker) return "absent";
1142
- if (!host.appendEntry || !host.sessionManager) return "unsupported";
1143
- const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
1144
- if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
1145
- appendRadarLaunchMarker(host.appendEntry, marker);
1146
- return "written";
1147
- }
1148
- function sameMarker(a, b) {
1149
- return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
1150
- }
1151
-
1152
- // extension/radar-publication.ts
1233
+ var DUPLICATE_SESSION_DIAGNOSTIC = "Radar rejected this session claim as a duplicate; reload or resume the session before retrying";
1153
1234
  var RADAR_PUBLICATION_VERSION = 1;
1154
1235
  var RADAR_LEASE_MS = 3e4;
1155
1236
  var RADAR_HEARTBEAT_MS = 15e3;
@@ -1174,7 +1255,7 @@ function readProcessBirth() {
1174
1255
  return void 0;
1175
1256
  }
1176
1257
  }
1177
- var RECORD_NAME = /^(?:subjects|channels|context|bindings)\/[a-z0-9-]{1,64}\.json$/;
1258
+ var RECORD_NAME = /^(?:registrations|presence|subjects|channels|context|bindings)\/[a-z0-9-]{1,64}\.json$/;
1178
1259
  var MAX_RECORD_BYTES = 64 * 1024;
1179
1260
  var PRIVATE_MODE = 448;
1180
1261
  var RECORD_MODE = 384;
@@ -1456,39 +1537,6 @@ function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
1456
1537
  return void 0;
1457
1538
  return { sequence, snapshot, lease_ms: lease, observed_at: observed };
1458
1539
  }
1459
- var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
1460
- var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
1461
- var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
1462
- var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
1463
- function sessionAt(value) {
1464
- if (typeof value !== "object" || value === null) return void 0;
1465
- const found = value.session;
1466
- if (found === null) return null;
1467
- return typeof found === "string" ? found : void 0;
1468
- }
1469
- function sameLaunchIdentity(a, b) {
1470
- return a === b || a !== void 0 && b !== void 0 && a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
1471
- }
1472
- function contextReportAt(value, keys, markedKeys) {
1473
- const marked = exactKeys(value, markedKeys);
1474
- if (!exactKeys(value, keys) && !marked) return void 0;
1475
- const sequence = numberAt2(value, "sequence");
1476
- const lease = numberAt2(value, "lease_ms");
1477
- const observed = textAt(value, "observed_at");
1478
- const session = sessionAt(value);
1479
- if (sequence === void 0 || sequence < 1) return void 0;
1480
- if (lease === void 0 || observed === void 0 || session === void 0)
1481
- return void 0;
1482
- const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
1483
- if (marked && marker === void 0) return void 0;
1484
- return {
1485
- sequence,
1486
- session,
1487
- lease_ms: lease,
1488
- observed_at: observed,
1489
- ...marker !== void 0 ? { launch_identity: marker } : {}
1490
- };
1491
- }
1492
1540
  function createChannelWriter(input) {
1493
1541
  const { store, client, scheduler, recordName, channel, publisher } = input;
1494
1542
  const codec = input.codec ?? executionCodec;
@@ -1504,7 +1552,7 @@ function createChannelWriter(input) {
1504
1552
  function blank(agentId) {
1505
1553
  return {
1506
1554
  version: RADAR_PUBLICATION_VERSION,
1507
- agent_id: agentId,
1555
+ registration_id: agentId,
1508
1556
  channel,
1509
1557
  publisher,
1510
1558
  sequence: 0
@@ -1512,7 +1560,7 @@ function createChannelWriter(input) {
1512
1560
  }
1513
1561
  function loadRecord() {
1514
1562
  const value = store.read(recordName);
1515
- const agentId = textAt(value, "agent_id");
1563
+ const agentId = textAt(value, "registration_id");
1516
1564
  const storedPublisher = recordAt2(value, "publisher");
1517
1565
  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)
1518
1566
  return blank("");
@@ -1527,7 +1575,7 @@ function createChannelWriter(input) {
1527
1575
  const sentAt = numberAt2(acceptedValue, "sent_at");
1528
1576
  return {
1529
1577
  version: RADAR_PUBLICATION_VERSION,
1530
- agent_id: agentId,
1578
+ registration_id: agentId,
1531
1579
  channel,
1532
1580
  publisher,
1533
1581
  ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
@@ -1568,12 +1616,12 @@ function createChannelWriter(input) {
1568
1616
  if (record.writer !== void 0) return true;
1569
1617
  const agentId = await input.subject(recovering);
1570
1618
  if (agentId === void 0) return false;
1571
- if (record.agent_id !== agentId) {
1619
+ if (record.registration_id !== agentId) {
1572
1620
  record = blank(agentId);
1573
1621
  save();
1574
1622
  }
1575
1623
  const answer = await client.acquire({
1576
- agent_id: agentId,
1624
+ registration_id: agentId,
1577
1625
  channel,
1578
1626
  publisher
1579
1627
  });
@@ -1603,7 +1651,7 @@ function createChannelWriter(input) {
1603
1651
  const writer = record.writer;
1604
1652
  if (writer === void 0) return;
1605
1653
  const answer = await client.publish({
1606
- agent_id: record.agent_id,
1654
+ registration_id: record.registration_id,
1607
1655
  channel,
1608
1656
  writer_handle: writer.handle,
1609
1657
  sequence: pending.sequence,
@@ -1697,9 +1745,9 @@ function createChannelWriter(input) {
1697
1745
  stopPump();
1698
1746
  const writer = record.writer;
1699
1747
  if (writer === void 0) return;
1700
- const agentId = record.agent_id;
1748
+ const agentId = record.registration_id;
1701
1749
  try {
1702
- void client.retire({ agent_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
1750
+ void client.retire({ registration_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
1703
1751
  if (answer.ok === true) {
1704
1752
  try {
1705
1753
  record.retired_at = new Date(scheduler.now()).toISOString();
@@ -1722,214 +1770,127 @@ function createChannelWriter(input) {
1722
1770
  diagnostic: () => record.diagnostic
1723
1771
  };
1724
1772
  }
1725
- function createContextWriter(input) {
1726
- const { store, client, scheduler, recordName, publisher } = input;
1727
- let record = loadRecord();
1728
- let desired;
1729
- let desiredMarker;
1730
- let running = false;
1731
- let again = false;
1732
- let recovering = false;
1733
- let stopped = false;
1734
- let fenced = false;
1735
- let pump;
1736
- function blank(agentId) {
1737
- return {
1738
- version: RADAR_PUBLICATION_VERSION,
1739
- agent_id: agentId,
1740
- publisher,
1741
- sequence: 0
1742
- };
1743
- }
1744
- function loadRecord() {
1745
- const value = store.read(recordName);
1746
- const agentId = textAt(value, "agent_id");
1747
- const storedPublisher = recordAt2(value, "publisher");
1748
- if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) && agentId !== "" || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
1749
- return blank("");
1750
- const writer = recordAt2(value, "writer");
1751
- const handle = textAt(writer, "handle");
1752
- const generation = numberAt2(writer, "generation");
1753
- if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
1754
- return blank(agentId);
1755
- const pending = contextReportAt(
1756
- recordAt2(value, "pending"),
1757
- CONTEXT_PENDING_KEYS,
1758
- CONTEXT_PENDING_MARKED_KEYS
1759
- );
1760
- const acceptedValue = recordAt2(value, "accepted");
1761
- const accepted = contextReportAt(
1762
- acceptedValue,
1763
- CONTEXT_ACCEPTED_KEYS,
1764
- CONTEXT_ACCEPTED_MARKED_KEYS
1765
- );
1766
- const sentAt = numberAt2(acceptedValue, "sent_at");
1767
- return {
1768
- version: RADAR_PUBLICATION_VERSION,
1769
- agent_id: agentId,
1770
- publisher,
1771
- ...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
1772
- sequence: numberAt2(value, "sequence") ?? 0,
1773
- ...pending !== void 0 ? { pending } : {},
1774
- ...accepted !== void 0 && sentAt !== void 0 ? { accepted: { ...accepted, sent_at: sentAt } } : {},
1775
- ...textAt(value, "diagnostic") !== void 0 ? { diagnostic: textAt(value, "diagnostic") } : {}
1776
- };
1777
- }
1778
- function note(message) {
1779
- if (record.diagnostic !== void 0) return;
1780
- record.diagnostic = message.slice(0, 256);
1781
- save();
1773
+ function createPresenceReporter(input) {
1774
+ const { store, client, scheduler, recordName } = input;
1775
+ let state;
1776
+ let standing;
1777
+ let diagnostic;
1778
+ try {
1779
+ const saved = store.read(recordName);
1780
+ if (saved?.version === 2 && isUuid(saved.claim_id) && isUuid(saved.session) && Array.isArray(saved.releases))
1781
+ state = saved;
1782
+ } catch (error) {
1783
+ diagnostic = `presence storage unavailable: ${String(error)}`;
1782
1784
  }
1785
+ let timer;
1786
+ let stopped = false;
1787
+ let running;
1783
1788
  function save() {
1784
- store.write(recordName, record);
1785
- }
1786
- function fence(message) {
1787
- fenced = true;
1788
- stopPump();
1789
- note(`context stopped: ${message}`);
1790
- }
1791
- function startPump() {
1792
- if (pump !== void 0 || stopped || fenced) return;
1793
- pump = scheduler.setInterval(() => kick(), input.heartbeatMs);
1794
- pump.unref?.();
1795
- }
1796
- function stopPump() {
1797
- if (pump === void 0) return;
1798
- scheduler.clearInterval(pump);
1799
- pump = void 0;
1789
+ try {
1790
+ if (state) store.write(recordName, state);
1791
+ return true;
1792
+ } catch (error) {
1793
+ diagnostic = `presence storage unavailable: ${String(error)}`;
1794
+ return false;
1795
+ }
1800
1796
  }
1801
- async function ensureSubject() {
1802
- if (record.writer !== void 0) return true;
1803
- const agentId = await input.subject(recovering);
1804
- if (agentId === void 0) return false;
1805
- if (record.agent_id !== agentId) {
1806
- record = blank(agentId);
1797
+ async function releasePending() {
1798
+ if (!state) return;
1799
+ const registration = await input.registration();
1800
+ if (!registration?.process) return;
1801
+ for (const release of [...state.releases]) {
1802
+ const answer = await client.releasePresence({ ...release, process: registration.process });
1803
+ if (!answer.ok) return;
1804
+ state.releases = state.releases.filter((r) => r.claim_id !== release.claim_id);
1807
1805
  save();
1808
1806
  }
1809
- return true;
1810
1807
  }
1811
- function heartbeatDue() {
1812
- return record.accepted !== void 0 && scheduler.now() - record.accepted.sent_at >= input.heartbeatMs;
1808
+ function schedule() {
1809
+ if (stopped || timer) return;
1810
+ timer = scheduler.setTimeout(() => {
1811
+ timer = void 0;
1812
+ void ensure();
1813
+ }, input.heartbeatMs);
1814
+ timer.unref?.();
1813
1815
  }
1814
- async function send(pending) {
1815
- const writer = record.writer;
1816
- const answer = await client.context({
1817
- agent_id: record.agent_id,
1818
- publisher,
1819
- // A first publish presents no handle: the daemon binds the writer the
1820
- // publisher already is, which is the binding this writer then keeps.
1821
- ...writer !== void 0 ? { writer_handle: writer.handle } : {},
1822
- sequence: pending.sequence,
1823
- lease_ms: pending.lease_ms,
1824
- observed_at: pending.observed_at,
1825
- context: {
1826
- session: pending.session,
1827
- ...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
1816
+ async function ensure() {
1817
+ if (running) return running;
1818
+ if (stopped || !state) return void 0;
1819
+ const claim = state;
1820
+ if (!save()) return void 0;
1821
+ running = (async () => {
1822
+ await releasePending();
1823
+ const registration = await input.registration();
1824
+ if (!registration?.process || state !== claim || stopped) return void 0;
1825
+ const answer = await client.presence({
1826
+ ...registration,
1827
+ process: registration.process,
1828
+ session_id: claim.session,
1829
+ claim_id: claim.claim_id,
1830
+ ...claim.registration_id ? { registration_id: claim.registration_id } : {},
1831
+ ...claim.subject_id ? { subject_id: claim.subject_id } : {}
1832
+ });
1833
+ if (state !== claim) return void 0;
1834
+ if (!answer.ok) {
1835
+ if (!["absent", "timeout", "transport"].includes(answer.code)) diagnostic = answer.message;
1836
+ return void 0;
1828
1837
  }
1829
- });
1830
- if (answer.ok === true) {
1831
- recovering = false;
1832
- record.writer = {
1833
- handle: answer.value.writer.handle,
1834
- generation: answer.value.writer.generation
1835
- };
1836
- record.sequence = Math.max(record.sequence, answer.value.writer.sequence);
1837
- record.accepted = {
1838
- ...pending,
1839
- sent_at: answer.value.warning === void 0 ? scheduler.now() : record.accepted?.sent_at ?? scheduler.now()
1840
- };
1841
- record.pending = void 0;
1838
+ claim.registration_id = answer.value.registration_id;
1839
+ claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
1840
+ 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" };
1841
+ diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
1842
1842
  save();
1843
- return;
1844
- }
1845
- if (answer.code === "refused" || answer.code === "bad_params" || // A daemon that speaks the registry but does not serve this record would
1846
- // refuse every retry the same way; one diagnostic beats a silent retry
1847
- // loop once per heartbeat.
1848
- answer.code === "unknown_method") {
1849
- fence(answer.message);
1850
- return;
1851
- }
1852
- if (answer.code === "not_found") {
1853
- record = blank("");
1854
- recovering = true;
1855
- save();
1856
- return;
1857
- }
1858
- }
1859
- async function stage(session, launchIdentity) {
1860
- const pending = {
1861
- sequence: record.sequence + 1,
1862
- session,
1863
- lease_ms: input.leaseMs,
1864
- observed_at: new Date(scheduler.now()).toISOString(),
1865
- ...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
1866
- };
1867
- record.pending = pending;
1868
- save();
1869
- await send(pending);
1870
- }
1871
- async function drainOnce() {
1872
- if (fenced) return;
1873
- if (record.pending !== void 0) {
1874
- await send(record.pending);
1875
- return;
1876
- }
1877
- if (desired === void 0) return;
1878
- if (!await ensureSubject()) return;
1879
- if (fenced) return;
1880
- if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
1881
- await stage(desired, desiredMarker);
1882
- return;
1883
- }
1884
- if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
1885
- }
1886
- async function drain() {
1887
- if (running || stopped || fenced) return;
1888
- running = true;
1843
+ return claim.registration_id;
1844
+ })();
1889
1845
  try {
1890
- await drainOnce();
1846
+ return await running;
1891
1847
  } catch (error) {
1892
- fenced = true;
1893
- stopPump();
1894
- try {
1895
- note(`context storage failure: ${String(error).slice(0, 180)}`);
1896
- } catch {
1897
- }
1848
+ diagnostic = `presence unavailable: ${String(error)}`;
1849
+ return void 0;
1898
1850
  } finally {
1899
- running = false;
1900
- if (again && !stopped && !fenced) {
1901
- again = false;
1902
- kick();
1903
- }
1851
+ running = void 0;
1852
+ if (!stopped && state !== claim) void ensure();
1853
+ else schedule();
1904
1854
  }
1905
1855
  }
1906
- function kick() {
1907
- if (stopped || fenced) return;
1908
- startPump();
1909
- if (running) {
1910
- again = true;
1911
- return;
1912
- }
1913
- void drain().catch(() => void 0);
1914
- }
1915
1856
  return {
1916
- update(session, launchIdentity) {
1917
- if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
1918
- return;
1919
- desired = session;
1920
- desiredMarker = launchIdentity;
1921
- if (stopped) return;
1922
- kick();
1857
+ report(session) {
1858
+ const releases = [...state?.releases ?? []];
1859
+ if (state?.registration_id && !releases.some((release) => release.claim_id === state.claim_id)) {
1860
+ releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
1861
+ }
1862
+ standing = void 0;
1863
+ state = { version: 2, session, claim_id: randomUUID2(), releases };
1864
+ save();
1865
+ stopped = false;
1866
+ void ensure();
1867
+ },
1868
+ ensure,
1869
+ async release() {
1870
+ const releasing = state;
1871
+ standing = void 0;
1872
+ stopped = true;
1873
+ if (timer) scheduler.clearTimeout(timer);
1874
+ timer = void 0;
1875
+ if (running) await running;
1876
+ if (state === releasing) standing = void 0;
1877
+ if (releasing?.registration_id && !releasing.releases.some((r) => r.claim_id === releasing.claim_id)) {
1878
+ releasing.releases.push({ registration_id: releasing.registration_id, claim_id: releasing.claim_id });
1879
+ if (!save()) return;
1880
+ }
1881
+ await releasePending();
1923
1882
  },
1924
1883
  stop() {
1925
1884
  stopped = true;
1926
- stopPump();
1885
+ if (timer) scheduler.clearTimeout(timer);
1886
+ timer = void 0;
1927
1887
  },
1928
1888
  reopen() {
1929
1889
  stopped = false;
1930
- if (desired !== void 0 || record.pending !== void 0) kick();
1890
+ void ensure();
1931
1891
  },
1932
- diagnostic: () => record.diagnostic
1892
+ management: () => standing,
1893
+ diagnostic: () => diagnostic
1933
1894
  };
1934
1895
  }
1935
1896
  var STORE_IDENTITY = /* @__PURE__ */ new WeakMap();
@@ -1958,10 +1919,13 @@ function unavailablePublication(error) {
1958
1919
  };
1959
1920
  return {
1960
1921
  execution: writer,
1961
- context: {
1962
- update: () => void 0,
1922
+ presence: {
1923
+ report: () => void 0,
1924
+ ensure: async () => void 0,
1925
+ release: async () => void 0,
1963
1926
  stop: () => void 0,
1964
1927
  reopen: () => void 0,
1928
+ management: () => void 0,
1965
1929
  diagnostic: () => diagnostic
1966
1930
  },
1967
1931
  facts: { ...writer, update: () => void 0 },
@@ -1988,33 +1952,29 @@ function createRadarPublicationInner(options) {
1988
1952
  cached.reopen();
1989
1953
  return cached;
1990
1954
  }
1991
- const subjectName = `subjects/${createHash("sha256").update(`${options.registration.source}\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
1955
+ const registrationName = `registrations/${createHash("sha256").update(`${options.registration.source}\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
1992
1956
  const channelRecord = (channel, target) => `channels/${createHash("sha256").update(`${channel}\0${incarnation}\0${target ?? ""}`).digest("hex").slice(0, 32)}.json`;
1993
- const contextRecord = `context/${createHash("sha256").update(`context\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
1994
- function readSubject() {
1995
- const value = store.read(subjectName);
1957
+ const presenceRecord = `presence/${createHash("sha256").update(`presence\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
1958
+ function readRegistration() {
1959
+ const value = store.read(registrationName);
1996
1960
  const registration = recordAt2(value, "registration");
1997
1961
  if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || textAt(registration, "source") !== options.registration.source || textAt(registration, "incarnation") !== incarnation)
1998
1962
  return void 0;
1999
- const storedAgentId = textAt(value, "agent_id");
1963
+ const storedAgentId = textAt(value, "registration_id");
2000
1964
  return {
2001
1965
  version: RADAR_PUBLICATION_VERSION,
2002
1966
  registration,
2003
- ...storedAgentId !== void 0 && isUuid(storedAgentId) ? { agent_id: storedAgentId } : {}
1967
+ ...storedAgentId !== void 0 && isUuid(storedAgentId) ? { registration_id: storedAgentId } : {}
2004
1968
  };
2005
1969
  }
2006
- const stored = readSubject();
2007
- let subject = stored;
1970
+ const stored = readRegistration();
1971
+ let processRecord = stored;
2008
1972
  let registrationDiagnostic;
2009
1973
  let registrationRecord;
2010
1974
  async function ensureRegistration() {
2011
- if (subject !== void 0) return subject;
1975
+ if (processRecord !== void 0) return processRecord;
2012
1976
  registrationRecord ??= (async () => {
2013
1977
  const resolution = options.location ? await options.location() : { state: "absent", reason: "this process publishes no location" };
2014
- if (resolution.state === "pending") {
2015
- registrationDiagnostic = `location bootstrap pending: ${resolution.reason}`.slice(0, 256);
2016
- return void 0;
2017
- }
2018
1978
  const built = {
2019
1979
  version: RADAR_PUBLICATION_VERSION,
2020
1980
  registration: {
@@ -2025,13 +1985,13 @@ function createRadarPublicationInner(options) {
2025
1985
  }
2026
1986
  };
2027
1987
  try {
2028
- store.write(subjectName, built);
1988
+ store.write(registrationName, built);
2029
1989
  } catch (error) {
2030
1990
  registrationDiagnostic ??= `private storage unavailable: ${String(error)}`.slice(0, 256);
2031
1991
  return void 0;
2032
1992
  }
2033
1993
  registrationDiagnostic = void 0;
2034
- subject = built;
1994
+ processRecord = built;
2035
1995
  return built;
2036
1996
  })();
2037
1997
  try {
@@ -2040,11 +2000,10 @@ function createRadarPublicationInner(options) {
2040
2000
  registrationRecord = void 0;
2041
2001
  }
2042
2002
  }
2043
- let agentId = stored?.agent_id;
2003
+ let agentId = stored?.registration_id;
2044
2004
  let subjectReady;
2045
2005
  async function ensureSubject(recheck) {
2046
- if (recheck) agentId = void 0;
2047
- if (agentId !== void 0) return agentId;
2006
+ if (!recheck && agentId !== void 0) return agentId;
2048
2007
  subjectReady ??= (async () => {
2049
2008
  const ready = await client.ping();
2050
2009
  if (ready.ok === false) {
@@ -2054,18 +2013,11 @@ function createRadarPublicationInner(options) {
2054
2013
  }
2055
2014
  const record = await ensureRegistration();
2056
2015
  if (record === void 0) return void 0;
2057
- const answer = await client.register({
2058
- ...record.registration,
2059
- ...spawnToken !== void 0 ? { spawn_token: spawnToken } : {}
2060
- });
2061
- if (answer.ok === false) {
2062
- if (answer.code !== "absent" && answer.code !== "timeout" && answer.code !== "transport")
2063
- registrationDiagnostic ??= `subject registration refused: ${answer.message}`.slice(0, 256);
2064
- return void 0;
2065
- }
2066
- agentId = answer.value.agent_id;
2067
- subject = { ...record, agent_id: agentId };
2068
- store.write(subjectName, subject);
2016
+ const registered = await presence.ensure();
2017
+ if (registered === void 0) return void 0;
2018
+ agentId = registered;
2019
+ processRecord = { ...record, registration_id: agentId };
2020
+ store.write(registrationName, processRecord);
2069
2021
  if (options.binding !== void 0) writeBinding(options.binding);
2070
2022
  return agentId;
2071
2023
  })();
@@ -2079,7 +2031,7 @@ function createRadarPublicationInner(options) {
2079
2031
  if (agentId === void 0) return;
2080
2032
  store.write(`bindings/${binding.key}.json`, {
2081
2033
  version: RADAR_PUBLICATION_VERSION,
2082
- agent_id: agentId,
2034
+ registration_id: agentId,
2083
2035
  incarnation,
2084
2036
  run: binding.run,
2085
2037
  owner: binding.owner,
@@ -2103,14 +2055,22 @@ function createRadarPublicationInner(options) {
2103
2055
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
2104
2056
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
2105
2057
  });
2106
- const context = createContextWriter({
2058
+ const presence = createPresenceReporter({
2107
2059
  store,
2108
2060
  client,
2109
2061
  scheduler,
2110
- recordName: contextRecord,
2111
- publisher: publisher("herdsman-pi"),
2112
- subject: ensureSubject,
2113
- leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
2062
+ recordName: presenceRecord,
2063
+ registration: async () => {
2064
+ const record = await ensureRegistration();
2065
+ if (!record) return void 0;
2066
+ const location = await options.location?.();
2067
+ const { location: _initialLocation, ...identity } = record.registration;
2068
+ return {
2069
+ ...identity,
2070
+ ...location?.state === "resolved" ? { location: location.location } : {},
2071
+ ...spawnToken ? { spawn_token: spawnToken } : {}
2072
+ };
2073
+ },
2114
2074
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
2115
2075
  });
2116
2076
  const facts = createChannelWriter({
@@ -2128,7 +2088,7 @@ function createRadarPublicationInner(options) {
2128
2088
  const assignmentWriters = /* @__PURE__ */ new Map();
2129
2089
  const publication = {
2130
2090
  execution,
2131
- context,
2091
+ presence,
2132
2092
  facts,
2133
2093
  assignment(target, reportingOwner) {
2134
2094
  const existing = assignmentWriters.get(target);
@@ -2143,7 +2103,7 @@ function createRadarPublicationInner(options) {
2143
2103
  publisher: publisher("herdsman-owner", reportingOwner),
2144
2104
  codec: assignmentCodec,
2145
2105
  // The owner publishes about a subject it did not register: the exact
2146
- // agent_id from the child's sidecar is the whole correlation.
2106
+ // registration_id from the child's sidecar is the whole correlation.
2147
2107
  subject: async () => target,
2148
2108
  leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
2149
2109
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -2163,7 +2123,7 @@ function createRadarPublicationInner(options) {
2163
2123
  registrationDiagnostic ??= `Radar binding unavailable: ${String(error).slice(0, 180)}`;
2164
2124
  return void 0;
2165
2125
  }
2166
- const bound = textAt(value, "agent_id");
2126
+ const bound = textAt(value, "registration_id");
2167
2127
  const boundIncarnation = textAt(value, "incarnation");
2168
2128
  const run = textAt(value, "run");
2169
2129
  const owner = textAt(value, "owner");
@@ -2173,7 +2133,7 @@ function createRadarPublicationInner(options) {
2173
2133
  return void 0;
2174
2134
  return {
2175
2135
  version: RADAR_PUBLICATION_VERSION,
2176
- agent_id: bound,
2136
+ registration_id: bound,
2177
2137
  incarnation: boundIncarnation,
2178
2138
  run,
2179
2139
  owner,
@@ -2189,23 +2149,27 @@ function createRadarPublicationInner(options) {
2189
2149
  if (agentId2 === void 0) return void 0;
2190
2150
  const record = await ensureRegistration();
2191
2151
  if (record === void 0) return void 0;
2192
- const location = record.registration.location;
2152
+ const currentLocation = await options.location?.();
2153
+ const location = currentLocation?.state === "resolved" ? currentLocation.location : void 0;
2154
+ const standing = presence.management();
2155
+ const subjectId = standing?.state === "managed" ? standing.subject_id : void 0;
2193
2156
  return {
2194
- agent_id: agentId2,
2157
+ registration_id: agentId2,
2158
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2195
2159
  ...location !== void 0 ? { location } : {}
2196
2160
  };
2197
2161
  },
2198
- diagnostic: () => registrationDiagnostic ?? execution.diagnostic() ?? context.diagnostic() ?? facts.diagnostic(),
2162
+ diagnostic: () => registrationDiagnostic ?? execution.diagnostic() ?? presence.diagnostic() ?? facts.diagnostic(),
2199
2163
  stop() {
2200
2164
  for (const writer of assignmentWriters.values()) writer.stop();
2201
2165
  execution.stop();
2202
- context.stop();
2166
+ presence.stop();
2203
2167
  facts.stop();
2204
2168
  },
2205
2169
  reopen() {
2206
2170
  for (const writer of assignmentWriters.values()) writer.reopen();
2207
2171
  execution.reopen();
2208
- context.reopen();
2172
+ presence.reopen();
2209
2173
  facts.reopen();
2210
2174
  }
2211
2175
  };
@@ -2237,10 +2201,13 @@ function unavailablePublication2() {
2237
2201
  return {
2238
2202
  execution: writer,
2239
2203
  facts: { ...writer, update: () => void 0 },
2240
- context: {
2241
- update: () => void 0,
2204
+ presence: {
2205
+ report: () => void 0,
2242
2206
  stop: () => void 0,
2243
2207
  reopen: () => void 0,
2208
+ management: () => void 0,
2209
+ ensure: async () => void 0,
2210
+ release: async () => void 0,
2244
2211
  diagnostic: () => "Radar private storage is unavailable"
2245
2212
  },
2246
2213
  assignment: () => writer,
@@ -2323,11 +2290,11 @@ function createRadarExecutionAdapter(pi, options) {
2323
2290
  ...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
2324
2291
  };
2325
2292
  }
2326
- function reportSession(context, launchIdentity) {
2293
+ function reportSession(context) {
2327
2294
  const session = context?.sessionManager?.getSessionId?.();
2328
2295
  if (!isUuid(session)) return;
2329
2296
  try {
2330
- publication.context.update(session, launchIdentity);
2297
+ publication.presence.report(session);
2331
2298
  } catch {
2332
2299
  }
2333
2300
  }
@@ -2391,38 +2358,8 @@ function createRadarExecutionAdapter(pi, options) {
2391
2358
  })
2392
2359
  );
2393
2360
  on("session_start", (_event, context) => {
2394
- const sessionHost = {
2395
- appendEntry: pi.appendEntry ? (customType, data) => pi.appendEntry?.(customType, data) : void 0,
2396
- sessionManager: context?.sessionManager
2397
- };
2398
- let launchIdentity;
2399
- try {
2400
- const result = persistRadarLaunchMarker(
2401
- sessionHost,
2402
- options.markerEnvironment
2403
- );
2404
- if (result === "written" || result === "unchanged") {
2405
- const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
2406
- const activeMarker = activeRadarLaunchMarker(sessionHost);
2407
- if (expected && activeMarker.status === "valid" && JSON.stringify(activeMarker.marker) === JSON.stringify(expected)) {
2408
- launchIdentity = activeMarker.marker;
2409
- } else {
2410
- options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
2411
- }
2412
- } else if (result === "unsupported") {
2413
- options.diagnostic?.("Pi host does not support Radar launch identity session entries");
2414
- } else if (options.markerEnvironment && [
2415
- "PI_RADAR_MANAGED_SUBJECT_ID",
2416
- "PI_RADAR_MANAGED_LAUNCH_ID",
2417
- "PI_RADAR_MANAGED_EXECUTION_GENERATION"
2418
- ].some((key) => options.markerEnvironment?.[key] !== void 0)) {
2419
- options.diagnostic?.("incomplete or invalid Radar launch identity environment");
2420
- }
2421
- } catch (error) {
2422
- options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
2423
- }
2424
2361
  rootSession = true;
2425
- reportSession(context, launchIdentity);
2362
+ reportSession(context);
2426
2363
  publishFacts(context);
2427
2364
  publication.reopen();
2428
2365
  active = context?.isIdle?.() === false;
@@ -2453,8 +2390,9 @@ function createRadarExecutionAdapter(pi, options) {
2453
2390
  }
2454
2391
  emit();
2455
2392
  });
2456
- on("session_shutdown", () => {
2393
+ on("session_shutdown", async () => {
2457
2394
  suspend();
2395
+ await publication.presence.release();
2458
2396
  });
2459
2397
  function suspend() {
2460
2398
  if (stopped) return;
@@ -2589,7 +2527,7 @@ async function resolveRadarProcessLocation(client, env = process.env) {
2589
2527
 
2590
2528
  // extension/radar-runtime.ts
2591
2529
  var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
2592
- var RADAR_RUNTIME_VERSION = 1;
2530
+ var RADAR_RUNTIME_VERSION = 2;
2593
2531
  function lazyRadarRuntime(start) {
2594
2532
  let pending;
2595
2533
  return {
@@ -2617,6 +2555,7 @@ function registerPiRadar(pi) {
2617
2555
  });
2618
2556
  const runtime = lazyRadarRuntime(async () => ({
2619
2557
  publication: execution.publication,
2558
+ communication: createRadarHandoffPort(client, execution.publication),
2620
2559
  execution
2621
2560
  }));
2622
2561
  provideRadarRuntime(pi.events, runtime);