pi-radar 0.1.0 → 0.3.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 (35) hide show
  1. package/README.md +14 -6
  2. package/dist/extension/baseline.js +2740 -0
  3. package/dist/extension/radar-client.js +97 -2
  4. package/dist/extension/radar-communication.js +39 -0
  5. package/dist/extension/radar-execution.js +148 -16
  6. package/dist/extension/radar-launch.js +5 -0
  7. package/dist/extension/radar-location.js +2 -2
  8. package/dist/extension/radar-publication.js +67 -15
  9. package/dist/extension/radar-runtime.js +42 -0
  10. package/dist/extension/radar-session-marker.js +82 -0
  11. package/dist/index.js +340 -21
  12. package/extension/baseline.ts +39 -0
  13. package/extension/generated-contracts.ts +85 -0
  14. package/extension/radar-client.ts +140 -1
  15. package/extension/radar-communication.ts +66 -0
  16. package/extension/radar-execution.ts +56 -3
  17. package/extension/radar-launch.ts +8 -1
  18. package/extension/radar-location.ts +2 -2
  19. package/extension/radar-publication.ts +73 -12
  20. package/extension/radar-runtime.ts +97 -0
  21. package/extension/radar-session-marker.ts +146 -0
  22. package/index.ts +2 -0
  23. package/package.json +24 -5
  24. package/schema/public/v1/handoff-inbox.deserialize.schema.json +18 -0
  25. package/schema/public/v1/handoff-inbox.serialize.schema.json +18 -0
  26. package/schema/public/v1/handoff-publish-params.deserialize.schema.json +33 -0
  27. package/schema/public/v1/handoff-publish-params.serialize.schema.json +33 -0
  28. package/schema/public/v1/handoff-receipt.deserialize.schema.json +66 -0
  29. package/schema/public/v1/handoff-receipt.serialize.schema.json +66 -0
  30. package/schema/public/v1/handoff.deserialize.schema.json +84 -0
  31. package/schema/public/v1/handoff.serialize.schema.json +84 -0
  32. package/schema/public/v1/launch-identity-marker.deserialize.schema.json +32 -0
  33. package/schema/public/v1/launch-identity-marker.serialize.schema.json +32 -0
  34. package/schema/public/v1/managed-launch-metadata.deserialize.schema.json +39 -0
  35. package/schema/public/v1/managed-launch-metadata.serialize.schema.json +39 -0
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var __export = (target, all) => {
8
8
  var radar_client_exports = {};
9
9
  __export(radar_client_exports, {
10
10
  RADAR_CALL_TIMEOUT_MS: () => RADAR_CALL_TIMEOUT_MS,
11
+ RADAR_COMMUNICATION_CONTRACT: () => RADAR_COMMUNICATION_CONTRACT,
11
12
  RADAR_EVENTS_CONTRACT: () => RADAR_EVENTS_CONTRACT,
12
13
  RADAR_EVENT_RETRY_MS: () => RADAR_EVENT_RETRY_MS,
13
14
  RADAR_MAX_AWAITED: () => RADAR_MAX_AWAITED,
@@ -38,13 +39,14 @@ import { lstatSync } from "node:fs";
38
39
  import { createConnection } from "node:net";
39
40
  import { dirname, join } from "node:path";
40
41
  var RADAR_PROTOCOL_VERSION = 2;
41
- var RADAR_MINIMUM_REVISION = 1;
42
+ var RADAR_MINIMUM_REVISION = 3;
42
43
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
43
44
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
44
45
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
45
46
  var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
46
47
  var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
47
48
  var RADAR_EVENTS_CONTRACT = "events.v1";
49
+ var RADAR_COMMUNICATION_CONTRACT = "communication.handoff.v1";
48
50
  var RADAR_REQUESTER = "pi-radar";
49
51
  var RADAR_MAX_LINE_BYTES = 1024 * 1024;
50
52
  var RADAR_CALL_TIMEOUT_MS = 2e3;
@@ -152,6 +154,28 @@ function numberAt(value, field) {
152
154
  const found = value[field];
153
155
  return typeof found === "number" && Number.isFinite(found) ? found : void 0;
154
156
  }
157
+ function handoffAt(value) {
158
+ const version = numberAt(value, "version");
159
+ const id = stringAt(value, "id");
160
+ const sender = recordAt(value, "sender");
161
+ const senderGeneration = numberAt(sender, "execution_generation");
162
+ const target = stringAt(value, "recipient_subject_id");
163
+ const summary = stringAt(value, "summary");
164
+ const content = stringAt(value, "content");
165
+ const published = numberAt(value, "published_at_unix_ms");
166
+ const correlation = typeof value === "object" && value !== null ? value.correlation_id : void 0;
167
+ if (version !== 1 || !isUuid(id) || !isUuid(stringAt(sender, "agent_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;
168
+ return value;
169
+ }
170
+ function handoffReceiptAt(value) {
171
+ const version = numberAt(value, "version");
172
+ const handoffId = stringAt(value, "handoff_id");
173
+ const recipient = recordAt(value, "recipient");
174
+ const generation = numberAt(recipient, "execution_generation");
175
+ const received = numberAt(value, "received_at_unix_ms");
176
+ if (version !== 1 || !isUuid(handoffId) || !isUuid(stringAt(recipient, "agent_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;
177
+ return value;
178
+ }
155
179
  function writerBinding(value) {
156
180
  const handle = stringAt(value, "handle");
157
181
  const source = stringAt(value, "source");
@@ -258,6 +282,12 @@ function unresolvedOperation(requestId, detail) {
258
282
  };
259
283
  }
260
284
  var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
285
+ var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
286
+ "PI_RADAR_MANAGED_SUBJECT_ID",
287
+ "PI_RADAR_MANAGED_LAUNCH_ID",
288
+ "PI_RADAR_MANAGED_EXECUTION_GENERATION",
289
+ "PI_RADAR_MANAGED_PARENT_SUBJECT_ID"
290
+ ]);
261
291
  function unresolvedSpawn(requestId, detail) {
262
292
  return {
263
293
  ok: false,
@@ -402,9 +432,23 @@ function spawnRequestShape(request) {
402
432
  for (const [name, value] of env) {
403
433
  if (name === RADAR_SPAWN_TOKEN_ENV)
404
434
  return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
435
+ if (RADAR_MANAGED_IDENTITY_ENV.has(name))
436
+ return `spawn env cannot set Radar-issued identity variable ${name}`;
405
437
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
406
438
  return `spawn env contains an invalid name or value: ${name}`;
407
439
  }
440
+ if (request.managed !== void 0) {
441
+ if (typeof request.managed !== "object" || request.managed === null)
442
+ return "managed launch metadata must be an object";
443
+ for (const [name, value] of Object.entries(request.managed)) {
444
+ if (!["owner", "run", "label", "role", "parent_subject_id"].includes(name))
445
+ return `unknown managed launch metadata field: ${name}`;
446
+ if (value != null && (typeof value !== "string" || value.trim() === "" || new TextEncoder().encode(value).length > 256 || new RegExp("\\p{Cc}", "u").test(value)))
447
+ return `managed launch ${name} must be non-empty, control-free, and at most 256 UTF-8 bytes`;
448
+ }
449
+ if (request.managed.parent_subject_id != null && !isUuid(request.managed.parent_subject_id))
450
+ return "managed parent_subject_id must be a canonical UUID";
451
+ }
408
452
  return void 0;
409
453
  }
410
454
  function focusOutcome(record, requestId) {
@@ -815,7 +859,8 @@ function createRadarClient(options = {}) {
815
859
  executable: request.executable,
816
860
  argv: request.argv,
817
861
  ...request.cwd !== void 0 ? { cwd: request.cwd } : {},
818
- ...carriesContext && request.env !== void 0 ? { env: request.env } : {}
862
+ ...carriesContext && request.env !== void 0 ? { env: request.env } : {},
863
+ ...request.managed !== void 0 ? { managed: request.managed } : {}
819
864
  },
820
865
  requester
821
866
  },
@@ -915,6 +960,56 @@ function createRadarClient(options = {}) {
915
960
  return protocol("spawn.close reply carries no close outcome");
916
961
  });
917
962
  },
963
+ async communicationPublish(request) {
964
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
965
+ const answer = await call("communication.publish", {
966
+ sender_agent_id: request.sender_agent_id,
967
+ recipient_subject_id: request.recipient_subject_id,
968
+ ...request.correlation_id !== void 0 ? { correlation_id: request.correlation_id } : {},
969
+ summary: request.summary,
970
+ content: request.content
971
+ }, request.request_id);
972
+ if (answer.ok === false) return answer;
973
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
974
+ const created = recordAt(answer.value, "created");
975
+ if (handoff === void 0 || typeof created !== "boolean")
976
+ return protocol("communication.publish reply carries no handoff");
977
+ return { ok: true, value: { handoff, created } };
978
+ });
979
+ },
980
+ async communicationInbox(request) {
981
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
982
+ const answer = await call("communication.inbox", request);
983
+ if (answer.ok === false) return answer;
984
+ const handoffIds = recordAt(answer.value, "handoff_ids");
985
+ if (!Array.isArray(handoffIds) || handoffIds.some((id) => !isUuid(id)))
986
+ return protocol("communication.inbox reply carries malformed handoff IDs");
987
+ return { ok: true, value: answer.value };
988
+ });
989
+ },
990
+ async communicationGet(request) {
991
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
992
+ const answer = await call("communication.get", request);
993
+ if (answer.ok === false) return answer;
994
+ const handoff = handoffAt(recordAt(answer.value, "handoff"));
995
+ const receiptValue = recordAt(answer.value, "receipt");
996
+ const receipt = receiptValue === null ? null : handoffReceiptAt(receiptValue);
997
+ if (handoff === void 0 || receiptValue !== null && receipt === void 0)
998
+ return protocol("communication.get reply carries malformed content or receipt");
999
+ return { ok: true, value: { handoff, receipt: receipt ?? null } };
1000
+ });
1001
+ },
1002
+ async communicationAck(request) {
1003
+ return withContract(RADAR_COMMUNICATION_CONTRACT, async () => {
1004
+ const answer = await call("communication.ack", request);
1005
+ if (answer.ok === false) return answer;
1006
+ const receipt = handoffReceiptAt(recordAt(answer.value, "receipt"));
1007
+ const created = recordAt(answer.value, "created");
1008
+ if (receipt === void 0 || typeof created !== "boolean")
1009
+ return protocol("communication.ack reply carries no receipt");
1010
+ return { ok: true, value: { receipt, created } };
1011
+ });
1012
+ },
918
1013
  async events(request) {
919
1014
  if (!Number.isSafeInteger(request.after) || request.after < 0 || !Number.isSafeInteger(request.wait_ms) || request.wait_ms < 0 || request.wait_ms > RADAR_EVENT_WAIT_MS)
920
1015
  return {
@@ -1135,6 +1230,47 @@ function boundedRadarSnapshot(snapshot) {
1135
1230
  };
1136
1231
  }
1137
1232
 
1233
+ // extension/radar-communication.ts
1234
+ var radar_communication_exports = {};
1235
+ __export(radar_communication_exports, {
1236
+ createRadarHandoffPort: () => createRadarHandoffPort
1237
+ });
1238
+ function createRadarHandoffPort(client, publication, parentSubjectId = () => process.env.PI_RADAR_MANAGED_PARENT_SUBJECT_ID) {
1239
+ async function agentId() {
1240
+ const subject = await publication.subject();
1241
+ return subject === void 0 ? { ok: false, code: "absent", message: "this process has no current Radar registration" } : { ok: true, value: subject.agent_id };
1242
+ }
1243
+ return {
1244
+ async publish(request) {
1245
+ const identity = await agentId();
1246
+ if (identity.ok === false) return identity;
1247
+ const parent = parentSubjectId();
1248
+ if (parent === void 0)
1249
+ return { ok: false, code: "absent", message: "this managed execution has no Radar parent subject" };
1250
+ return client.communicationPublish({
1251
+ ...request,
1252
+ sender_agent_id: identity.value,
1253
+ recipient_subject_id: parent
1254
+ });
1255
+ },
1256
+ async inbox() {
1257
+ const identity = await agentId();
1258
+ if (identity.ok === false) return identity;
1259
+ return client.communicationInbox({ agent_id: identity.value });
1260
+ },
1261
+ async get(handoff_id) {
1262
+ const identity = await agentId();
1263
+ if (identity.ok === false) return identity;
1264
+ return client.communicationGet({ agent_id: identity.value, handoff_id });
1265
+ },
1266
+ async ack(handoff_id) {
1267
+ const identity = await agentId();
1268
+ if (identity.ok === false) return identity;
1269
+ return client.communicationAck({ agent_id: identity.value, handoff_id });
1270
+ }
1271
+ };
1272
+ }
1273
+
1138
1274
  // extension/radar-execution.ts
1139
1275
  var radar_execution_exports = {};
1140
1276
  __export(radar_execution_exports, {
@@ -1173,6 +1309,77 @@ import {
1173
1309
  writeSync
1174
1310
  } from "node:fs";
1175
1311
  import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
1312
+
1313
+ // extension/radar-session-marker.ts
1314
+ var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
1315
+ var RADAR_LAUNCH_MARKER_VERSION = 1;
1316
+ var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
1317
+ var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
1318
+ var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
1319
+ var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
1320
+ 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;
1321
+ function decodeRadarLaunchMarker(value) {
1322
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
1323
+ const record = value;
1324
+ if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
1325
+ return void 0;
1326
+ }
1327
+ 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) {
1328
+ return void 0;
1329
+ }
1330
+ return {
1331
+ version: RADAR_LAUNCH_MARKER_VERSION,
1332
+ subject_id: record.subject_id,
1333
+ launch_id: record.launch_id,
1334
+ execution_generation: record.execution_generation
1335
+ };
1336
+ }
1337
+ function appendRadarLaunchMarker(appendEntry, marker) {
1338
+ const validated = decodeRadarLaunchMarker(marker);
1339
+ if (!validated) throw new Error("invalid Radar launch identity marker");
1340
+ appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
1341
+ }
1342
+ function latestRadarLaunchMarker(branch) {
1343
+ let latest;
1344
+ for (const entry of branch) {
1345
+ if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
1346
+ latest = entry;
1347
+ }
1348
+ }
1349
+ if (!latest) return { status: "absent" };
1350
+ const marker = decodeRadarLaunchMarker(latest.data);
1351
+ return marker ? { status: "valid", marker } : { status: "invalid" };
1352
+ }
1353
+ function radarLaunchMarkerFromEnvironment(env = process.env) {
1354
+ const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
1355
+ if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
1356
+ return void 0;
1357
+ }
1358
+ return decodeRadarLaunchMarker({
1359
+ version: RADAR_LAUNCH_MARKER_VERSION,
1360
+ subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
1361
+ launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
1362
+ execution_generation: Number(rawGeneration)
1363
+ });
1364
+ }
1365
+ function activeRadarLaunchMarker(host) {
1366
+ if (!host.sessionManager) return { status: "absent" };
1367
+ return latestRadarLaunchMarker(host.sessionManager.getBranch());
1368
+ }
1369
+ function persistRadarLaunchMarker(host, env = process.env) {
1370
+ const marker = radarLaunchMarkerFromEnvironment(env);
1371
+ if (!marker) return "absent";
1372
+ if (!host.appendEntry || !host.sessionManager) return "unsupported";
1373
+ const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
1374
+ if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
1375
+ appendRadarLaunchMarker(host.appendEntry, marker);
1376
+ return "written";
1377
+ }
1378
+ function sameMarker(a, b) {
1379
+ return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
1380
+ }
1381
+
1382
+ // extension/radar-publication.ts
1176
1383
  var RADAR_PUBLICATION_VERSION = 1;
1177
1384
  var RADAR_LEASE_MS = 3e4;
1178
1385
  var RADAR_HEARTBEAT_MS = 15e3;
@@ -1483,15 +1690,21 @@ function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
1483
1690
  return { sequence, snapshot, lease_ms: lease, observed_at: observed };
1484
1691
  }
1485
1692
  var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
1693
+ var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
1486
1694
  var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
1695
+ var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
1487
1696
  function sessionAt(value) {
1488
1697
  if (typeof value !== "object" || value === null) return void 0;
1489
1698
  const found = value.session;
1490
1699
  if (found === null) return null;
1491
1700
  return typeof found === "string" ? found : void 0;
1492
1701
  }
1493
- function contextReportAt(value, keys) {
1494
- if (!exactKeys(value, keys)) return void 0;
1702
+ function sameLaunchIdentity(a, b) {
1703
+ 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;
1704
+ }
1705
+ function contextReportAt(value, keys, markedKeys) {
1706
+ const marked = exactKeys(value, markedKeys);
1707
+ if (!exactKeys(value, keys) && !marked) return void 0;
1495
1708
  const sequence = numberAt2(value, "sequence");
1496
1709
  const lease = numberAt2(value, "lease_ms");
1497
1710
  const observed = textAt(value, "observed_at");
@@ -1499,7 +1712,15 @@ function contextReportAt(value, keys) {
1499
1712
  if (sequence === void 0 || sequence < 1) return void 0;
1500
1713
  if (lease === void 0 || observed === void 0 || session === void 0)
1501
1714
  return void 0;
1502
- return { sequence, session, lease_ms: lease, observed_at: observed };
1715
+ const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
1716
+ if (marked && marker === void 0) return void 0;
1717
+ return {
1718
+ sequence,
1719
+ session,
1720
+ lease_ms: lease,
1721
+ observed_at: observed,
1722
+ ...marker !== void 0 ? { launch_identity: marker } : {}
1723
+ };
1503
1724
  }
1504
1725
  function createChannelWriter(input) {
1505
1726
  const { store, client, scheduler, recordName, channel, publisher } = input;
@@ -1738,6 +1959,7 @@ function createContextWriter(input) {
1738
1959
  const { store, client, scheduler, recordName, publisher } = input;
1739
1960
  let record = loadRecord();
1740
1961
  let desired;
1962
+ let desiredMarker;
1741
1963
  let running = false;
1742
1964
  let again = false;
1743
1965
  let recovering = false;
@@ -1763,9 +1985,17 @@ function createContextWriter(input) {
1763
1985
  const generation = numberAt2(writer, "generation");
1764
1986
  if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
1765
1987
  return blank(agentId);
1766
- const pending = contextReportAt(recordAt2(value, "pending"), CONTEXT_PENDING_KEYS);
1988
+ const pending = contextReportAt(
1989
+ recordAt2(value, "pending"),
1990
+ CONTEXT_PENDING_KEYS,
1991
+ CONTEXT_PENDING_MARKED_KEYS
1992
+ );
1767
1993
  const acceptedValue = recordAt2(value, "accepted");
1768
- const accepted = contextReportAt(acceptedValue, CONTEXT_ACCEPTED_KEYS);
1994
+ const accepted = contextReportAt(
1995
+ acceptedValue,
1996
+ CONTEXT_ACCEPTED_KEYS,
1997
+ CONTEXT_ACCEPTED_MARKED_KEYS
1998
+ );
1769
1999
  const sentAt = numberAt2(acceptedValue, "sent_at");
1770
2000
  return {
1771
2001
  version: RADAR_PUBLICATION_VERSION,
@@ -1825,7 +2055,10 @@ function createContextWriter(input) {
1825
2055
  sequence: pending.sequence,
1826
2056
  lease_ms: pending.lease_ms,
1827
2057
  observed_at: pending.observed_at,
1828
- context: { session: pending.session }
2058
+ context: {
2059
+ session: pending.session,
2060
+ ...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
2061
+ }
1829
2062
  });
1830
2063
  if (answer.ok === true) {
1831
2064
  recovering = false;
@@ -1856,12 +2089,13 @@ function createContextWriter(input) {
1856
2089
  return;
1857
2090
  }
1858
2091
  }
1859
- async function stage(session) {
2092
+ async function stage(session, launchIdentity) {
1860
2093
  const pending = {
1861
2094
  sequence: record.sequence + 1,
1862
2095
  session,
1863
2096
  lease_ms: input.leaseMs,
1864
- observed_at: new Date(scheduler.now()).toISOString()
2097
+ observed_at: new Date(scheduler.now()).toISOString(),
2098
+ ...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
1865
2099
  };
1866
2100
  record.pending = pending;
1867
2101
  save();
@@ -1876,11 +2110,11 @@ function createContextWriter(input) {
1876
2110
  if (desired === void 0) return;
1877
2111
  if (!await ensureSubject()) return;
1878
2112
  if (fenced) return;
1879
- if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired) {
1880
- await stage(desired);
2113
+ if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
2114
+ await stage(desired, desiredMarker);
1881
2115
  return;
1882
2116
  }
1883
- if (heartbeatDue()) await stage(record.accepted.session);
2117
+ if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
1884
2118
  }
1885
2119
  async function drain() {
1886
2120
  if (running || stopped || fenced) return;
@@ -1912,9 +2146,11 @@ function createContextWriter(input) {
1912
2146
  void drain().catch(() => void 0);
1913
2147
  }
1914
2148
  return {
1915
- update(session) {
1916
- if (fenced || session === desired) return;
2149
+ update(session, launchIdentity) {
2150
+ if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
2151
+ return;
1917
2152
  desired = session;
2153
+ desiredMarker = launchIdentity;
1918
2154
  if (stopped) return;
1919
2155
  kick();
1920
2156
  },
@@ -2187,8 +2423,10 @@ function createRadarPublicationInner(options) {
2187
2423
  const record = await ensureRegistration();
2188
2424
  if (record === void 0) return void 0;
2189
2425
  const location = record.registration.location;
2426
+ const subjectId = process.env.PI_RADAR_MANAGED_SUBJECT_ID;
2190
2427
  return {
2191
2428
  agent_id: agentId2,
2429
+ ...subjectId !== void 0 ? { subject_id: subjectId } : {},
2192
2430
  ...location !== void 0 ? { location } : {}
2193
2431
  };
2194
2432
  },
@@ -2326,11 +2564,11 @@ function createRadarExecutionAdapter(pi, options) {
2326
2564
  ...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
2327
2565
  };
2328
2566
  }
2329
- function reportSession(context) {
2567
+ function reportSession(context, launchIdentity) {
2330
2568
  const session = context?.sessionManager?.getSessionId?.();
2331
2569
  if (!isUuid(session)) return;
2332
2570
  try {
2333
- publication.context.update(session);
2571
+ publication.context.update(session, launchIdentity);
2334
2572
  } catch {
2335
2573
  }
2336
2574
  }
@@ -2394,8 +2632,38 @@ function createRadarExecutionAdapter(pi, options) {
2394
2632
  })
2395
2633
  );
2396
2634
  on("session_start", (_event, context) => {
2635
+ const sessionHost = {
2636
+ appendEntry: pi.appendEntry ? (customType, data) => pi.appendEntry?.(customType, data) : void 0,
2637
+ sessionManager: context?.sessionManager
2638
+ };
2639
+ let launchIdentity;
2640
+ try {
2641
+ const result = persistRadarLaunchMarker(
2642
+ sessionHost,
2643
+ options.markerEnvironment
2644
+ );
2645
+ if (result === "written" || result === "unchanged") {
2646
+ const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
2647
+ const activeMarker = activeRadarLaunchMarker(sessionHost);
2648
+ if (expected && activeMarker.status === "valid" && JSON.stringify(activeMarker.marker) === JSON.stringify(expected)) {
2649
+ launchIdentity = activeMarker.marker;
2650
+ } else {
2651
+ options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
2652
+ }
2653
+ } else if (result === "unsupported") {
2654
+ options.diagnostic?.("Pi host does not support Radar launch identity session entries");
2655
+ } else if (options.markerEnvironment && [
2656
+ "PI_RADAR_MANAGED_SUBJECT_ID",
2657
+ "PI_RADAR_MANAGED_LAUNCH_ID",
2658
+ "PI_RADAR_MANAGED_EXECUTION_GENERATION"
2659
+ ].some((key) => options.markerEnvironment?.[key] !== void 0)) {
2660
+ options.diagnostic?.("incomplete or invalid Radar launch identity environment");
2661
+ }
2662
+ } catch (error) {
2663
+ options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
2664
+ }
2397
2665
  rootSession = true;
2398
- reportSession(context);
2666
+ reportSession(context, launchIdentity);
2399
2667
  publishFacts(context);
2400
2668
  publication.reopen();
2401
2669
  active = context?.isIdle?.() === false;
@@ -2582,7 +2850,8 @@ async function resolveRadarProcessLocation(client, env = process.env) {
2582
2850
  const backend = ready.value.backend;
2583
2851
  if (backend === void 0 || backend === "none")
2584
2852
  return { state: "absent", reason: "the daemon serves no physical backend" };
2585
- const own = qualifiedOwnPane(env, backend, ready.value.instance);
2853
+ const instance = ready.value.instance ?? void 0;
2854
+ const own = qualifiedOwnPane(env, backend, instance);
2586
2855
  if ("absent" in own) return { state: "absent", reason: own.absent };
2587
2856
  if ("pending" in own) return { state: "pending", reason: own.pending };
2588
2857
  const paneId = own.pane;
@@ -2614,7 +2883,6 @@ async function resolveRadarProcessLocation(client, env = process.env) {
2614
2883
  if ("ambiguous" in containment)
2615
2884
  return { state: "pending", reason: containment.ambiguous };
2616
2885
  const row = containment.pane;
2617
- const instance = ready.value.instance;
2618
2886
  return {
2619
2887
  state: "resolved",
2620
2888
  location: {
@@ -2760,6 +3028,10 @@ async function launchRadarWorker(options) {
2760
3028
  return refusedLaunch(
2761
3029
  "the launch has no exact parent registration to spawn under"
2762
3030
  );
3031
+ if (!isUuid(launch.parent.subject_id))
3032
+ return refusedLaunch(
3033
+ "the launch has no managed Radar subject for its parent"
3034
+ );
2763
3035
  if (pane === void 0 || pane === "")
2764
3036
  return refusedLaunch(
2765
3037
  "this process's Radar registration names no pane, so it cannot be a spawn parent"
@@ -2769,6 +3041,7 @@ async function launchRadarWorker(options) {
2769
3041
  parent: launch.parent.agent_id,
2770
3042
  parent_session_id: launch.parent_session_id,
2771
3043
  executable: launch.executable,
3044
+ managed: { parent_subject_id: launch.parent.subject_id },
2772
3045
  argv: launch.argv,
2773
3046
  ...launch.cwd !== "" ? { cwd: launch.cwd } : {},
2774
3047
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
@@ -2895,11 +3168,57 @@ async function waitForChild(client, requestId, options) {
2895
3168
  });
2896
3169
  }
2897
3170
  }
3171
+
3172
+ // extension/radar-runtime.ts
3173
+ var radar_runtime_exports = {};
3174
+ __export(radar_runtime_exports, {
3175
+ RADAR_RUNTIME_REQUEST: () => RADAR_RUNTIME_REQUEST,
3176
+ RADAR_RUNTIME_VERSION: () => RADAR_RUNTIME_VERSION,
3177
+ lazyRadarRuntime: () => lazyRadarRuntime,
3178
+ provideRadarRuntime: () => provideRadarRuntime,
3179
+ requestRadarRuntime: () => requestRadarRuntime
3180
+ });
3181
+ var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
3182
+ var RADAR_RUNTIME_VERSION = 2;
3183
+ function lazyRadarRuntime(start) {
3184
+ let pending;
3185
+ return {
3186
+ version: RADAR_RUNTIME_VERSION,
3187
+ start: () => pending ??= Promise.resolve().then(start)
3188
+ };
3189
+ }
3190
+ function provideRadarRuntime(events, handle) {
3191
+ return events.on(RADAR_RUNTIME_REQUEST, (value) => {
3192
+ if (!isRuntimeRequest(value)) return;
3193
+ value.provide(handle);
3194
+ });
3195
+ }
3196
+ function requestRadarRuntime(events) {
3197
+ const handles = [];
3198
+ events.emit(RADAR_RUNTIME_REQUEST, {
3199
+ version: RADAR_RUNTIME_VERSION,
3200
+ provide: (candidate) => {
3201
+ if (isRadarRuntimeHandle(candidate)) handles.push(candidate);
3202
+ }
3203
+ });
3204
+ if (handles.length === 1) return { ok: true, handle: handles[0] };
3205
+ return { ok: false, reason: handles.length === 0 ? "missing" : "duplicate" };
3206
+ }
3207
+ function isRuntimeRequest(value) {
3208
+ return typeof value === "object" && value !== null && value.version === RADAR_RUNTIME_VERSION && typeof value.provide === "function";
3209
+ }
3210
+ function isRadarRuntimeHandle(value) {
3211
+ if (typeof value !== "object" || value === null) return false;
3212
+ const handle = value;
3213
+ return handle.version === RADAR_RUNTIME_VERSION && typeof handle.start === "function";
3214
+ }
2898
3215
  export {
2899
3216
  radar_client_exports as client,
3217
+ radar_communication_exports as communication,
2900
3218
  radar_execution_exports as execution,
2901
3219
  radar_inspection_exports as inspection,
2902
3220
  radar_launch_exports as launch,
2903
3221
  radar_location_exports as location,
2904
- radar_publication_exports as publication
3222
+ radar_publication_exports as publication,
3223
+ radar_runtime_exports as runtime
2905
3224
  };
@@ -0,0 +1,39 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { createRadarClient } from "./radar-client.ts";
4
+ import { createRadarHandoffPort } from "./radar-communication.ts";
5
+ import {
6
+ createRadarExecutionAdapter,
7
+ type RadarExecutionEventApi,
8
+ } from "./radar-execution.ts";
9
+ import { resolveRadarProcessLocation } from "./radar-location.ts";
10
+ import {
11
+ lazyRadarRuntime,
12
+ provideRadarRuntime,
13
+ type RadarExtensionEventBus,
14
+ } from "./radar-runtime.ts";
15
+
16
+ /** Minimal structural Pi API used by the baseline extension. */
17
+ type PiRadarHost = RadarExecutionEventApi & {
18
+ events: RadarExtensionEventBus;
19
+ };
20
+
21
+ /**
22
+ * Register pi-radar as the one baseline registrar for this Pi process.
23
+ * Herdsman integrations request the same session-scoped runtime handle over
24
+ * `pi.events`; they add orchestration data without creating another publisher.
25
+ */
26
+ export default function registerPiRadar(pi: PiRadarHost): void {
27
+ const client = createRadarClient();
28
+ const execution = createRadarExecutionAdapter(pi, {
29
+ client,
30
+ dataRoot: join(homedir(), ".pi", "agent", "pi-radar"),
31
+ location: () => resolveRadarProcessLocation(client),
32
+ });
33
+ const runtime = lazyRadarRuntime(async () => ({
34
+ publication: execution.publication,
35
+ communication: createRadarHandoffPort(client, execution.publication),
36
+ execution,
37
+ }));
38
+ provideRadarRuntime(pi.events, runtime);
39
+ }