pi-radar 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +15 -1
  2. package/dist/extension/baseline.js +395 -125
  3. package/dist/extension/radar-children.js +240 -0
  4. package/dist/extension/radar-client.js +42 -8
  5. package/dist/extension/radar-execution.js +11 -10
  6. package/dist/extension/radar-launch.js +23 -24
  7. package/dist/extension/radar-publication.js +10 -9
  8. package/dist/extension/radar-runtime.js +1 -1
  9. package/dist/index.js +1772 -1615
  10. package/extension/baseline.ts +2 -0
  11. package/extension/generated-contracts.ts +12 -3
  12. package/extension/radar-children.ts +176 -0
  13. package/extension/radar-client.ts +60 -6
  14. package/extension/radar-execution.ts +5 -5
  15. package/extension/radar-launch.ts +42 -63
  16. package/extension/radar-publication.ts +12 -13
  17. package/extension/radar-runtime.ts +4 -1
  18. package/index.ts +1 -0
  19. package/package.json +8 -3
  20. package/schema/public/v1/observation-get-response.deserialize.schema.json +9 -1
  21. package/schema/public/v1/observation-get-response.serialize.schema.json +9 -1
  22. package/schema/public/v1/observation-snapshot.deserialize.schema.json +9 -1
  23. package/schema/public/v1/observation-snapshot.serialize.schema.json +9 -1
  24. package/schema/public/v1/presence-answer.deserialize.schema.json +1 -0
  25. package/schema/public/v1/presence-answer.serialize.schema.json +1 -0
  26. package/schema/public/v1/presence-report.deserialize.schema.json +0 -7
  27. package/schema/public/v1/presence-report.serialize.schema.json +0 -8
  28. package/schema/public/v1/spawn-list-page.deserialize.schema.json +7 -0
  29. package/schema/public/v1/spawn-list-page.serialize.schema.json +7 -0
@@ -0,0 +1,240 @@
1
+ // extension/radar-inspection.ts
2
+ var INSPECTION_LINES = 80;
3
+ var INSPECTION_OUTPUT_BYTES = 16 * 1024;
4
+ async function provedRead(client, target) {
5
+ const read = await client.agentGet(target.agentId);
6
+ return read.ok === true && read.value.verification === "verified" && read.value.location?.pane === target.paneId && (target.session === void 0 || read.value.contextSession === target.session) ? read.value : void 0;
7
+ }
8
+ async function inspectRadarChild(client, target) {
9
+ const before = await provedRead(client, target);
10
+ if (before === void 0)
11
+ throw new Error("Inspection target identity did not match");
12
+ const output = await client.output({
13
+ pane_id: target.paneId,
14
+ source: "recent_unwrapped",
15
+ lines: INSPECTION_LINES
16
+ });
17
+ if (output.ok === false)
18
+ throw new Error(
19
+ `Inspection output unavailable (${output.code}: ${output.message})`
20
+ );
21
+ const foreground = await client.processInfo({ pane_id: target.paneId });
22
+ const after = await provedRead(client, target);
23
+ if (after === void 0)
24
+ throw new Error("Inspection target changed during capture");
25
+ const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
26
+ const bounded = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
27
+ return Object.freeze({
28
+ capturedAt: Date.now(),
29
+ recentOutputTruncated: output.value.truncated || bounded.truncated,
30
+ ...bounded.text ? { recentOutput: bounded.text } : {},
31
+ ...foreground.ok === true ? { foreground: foreground.value } : {},
32
+ ...after.activity !== void 0 ? { activity: after.activity } : {}
33
+ });
34
+ }
35
+ function utf8Tail(value, maxBytes) {
36
+ const bytes = Buffer.from(value);
37
+ if (bytes.length <= maxBytes) return { text: value, truncated: false };
38
+ let start = bytes.length - maxBytes;
39
+ while (start < bytes.length && (bytes[start] & 192) === 128) start++;
40
+ return { text: bytes.subarray(start).toString(), truncated: true };
41
+ }
42
+
43
+ // extension/radar-client.ts
44
+ var RADAR_MAX_LINE_BYTES = 1024 * 1024;
45
+
46
+ // extension/radar-launch.ts
47
+ function unknownChild(reason) {
48
+ return { kind: "unknown", diagnostic: trajectory(reason) };
49
+ }
50
+ function trajectory(reason) {
51
+ return `the daemon's current record of this launch's child is ${reason}`;
52
+ }
53
+ async function observeRadarChild(client, provenance) {
54
+ if (provenance.registration_id === void 0)
55
+ return unknownChild("not bound to a child by this launch");
56
+ if (provenance.pane_id === void 0)
57
+ return unknownChild("without a pane this launch recorded for it");
58
+ const read = await client.spawnGet(provenance.request_id);
59
+ if (read.ok === false)
60
+ return unknownChild(
61
+ `not readable (${read.code}: ${read.message})`
62
+ );
63
+ const topology = read.value;
64
+ const edge = topology.edge;
65
+ if (edge.bound === void 0 || edge.bound.registration_id === void 0)
66
+ return unknownChild("not bound to a child by the daemon");
67
+ if (edge.bound.registration_id !== provenance.registration_id)
68
+ return unknownChild(
69
+ `bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
70
+ );
71
+ if (edge.location?.pane === void 0)
72
+ return unknownChild("recorded without a pane");
73
+ if (edge.location.pane !== provenance.pane_id)
74
+ return unknownChild(
75
+ `recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
76
+ );
77
+ if (edge.exit_observation?.state !== "unknown")
78
+ return unknownChild("recorded with an observed process exit");
79
+ if (topology.freshness !== "fresh")
80
+ return unknownChild(
81
+ "on a pane the daemon's own inventory does not currently report"
82
+ );
83
+ if (topology.state !== "bound")
84
+ return unknownChild(
85
+ `not currently placed (state ${topology.state ?? "unreported"})`
86
+ );
87
+ const reply = await client.agentGet(edge.bound.registration_id);
88
+ if (reply.ok === false)
89
+ return unknownChild(
90
+ `process verdict not readable (${reply.code}: ${reply.message})`
91
+ );
92
+ const { verification, location, contextSession } = reply.value;
93
+ if (verification === "absent" || verification === "mismatched")
94
+ return {
95
+ kind: "gone",
96
+ verification,
97
+ diagnostic: trajectory(`the registered process is ${verification}`)
98
+ };
99
+ if (verification !== "verified")
100
+ return unknownChild(`process verdict is ${verification}`);
101
+ if (location?.pane !== provenance.pane_id)
102
+ return unknownChild(
103
+ `registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
104
+ );
105
+ if (provenance.session_id === void 0)
106
+ return unknownChild("without a session this launch recorded for it");
107
+ if (contextSession !== provenance.session_id)
108
+ return unknownChild(
109
+ `publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
110
+ );
111
+ return {
112
+ kind: "live",
113
+ evidence: {
114
+ request_id: provenance.request_id,
115
+ registration_id: edge.bound.registration_id,
116
+ source: edge.bound.source,
117
+ incarnation: edge.bound.incarnation,
118
+ pane_id: edge.location.pane
119
+ }
120
+ };
121
+ }
122
+
123
+ // extension/radar-children.ts
124
+ var PAGE = 100;
125
+ var MAX_PAGES = 20;
126
+ function createRadarChildren(client, publication) {
127
+ const held = /* @__PURE__ */ new Map();
128
+ async function resolveAll() {
129
+ const newest = /* @__PURE__ */ new Map();
130
+ let after;
131
+ for (let page = 0; page < MAX_PAGES; page++) {
132
+ const read = await client.spawnList({ ...after !== void 0 ? { after } : {}, limit: PAGE });
133
+ if (read.ok === false) return read;
134
+ for (const edge of read.value.spawns) {
135
+ const bound = edge.bound;
136
+ if (!bound?.subject_id || !bound.registration_id || edge.launched !== "completed") continue;
137
+ const at = Date.parse(bound.bound_at);
138
+ if (Number.isNaN(at)) continue;
139
+ const current = newest.get(bound.subject_id);
140
+ if (current !== void 0 && current.at >= at) continue;
141
+ newest.set(bound.subject_id, {
142
+ at,
143
+ registration_id: bound.registration_id,
144
+ child: {
145
+ subject_id: bound.subject_id,
146
+ request_id: edge.request_id,
147
+ ...edge.location?.pane ? { pane_id: edge.location.pane } : {},
148
+ state: edge.state,
149
+ exited: edge.exit_observation != null && edge.exit_observation.state !== "unknown"
150
+ }
151
+ });
152
+ }
153
+ if (!read.value.next) return { ok: true, value: newest };
154
+ after = read.value.next;
155
+ }
156
+ return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
157
+ }
158
+ async function resolve(subject) {
159
+ const all = await resolveAll();
160
+ return all.ok ? all.value.get(subject) : void 0;
161
+ }
162
+ async function resolveAndFollow(subject) {
163
+ const all = await resolveAll();
164
+ if (!all.ok) return void 0;
165
+ follow(all.value);
166
+ return all.value.get(subject);
167
+ }
168
+ function follow(all) {
169
+ for (const [subject, entry] of held) {
170
+ const target = all.get(subject);
171
+ if (target !== void 0 && entry.published !== target.registration_id)
172
+ entry.publish?.(target);
173
+ }
174
+ }
175
+ return {
176
+ async list() {
177
+ const all = await resolveAll();
178
+ if (!all.ok) return all;
179
+ follow(all.value);
180
+ return { ok: true, value: [...all.value.values()].map((entry) => entry.child) };
181
+ },
182
+ assignment(subject, owner) {
183
+ const existing = held.get(subject);
184
+ if (existing !== void 0) return existing.writer;
185
+ let latest;
186
+ const entry = { writer: void 0 };
187
+ const send = (target) => {
188
+ if (latest === void 0) return;
189
+ if (entry.published !== void 0 && entry.published !== target.registration_id)
190
+ publication.retireAssignment(entry.published);
191
+ entry.published = target.registration_id;
192
+ publication.assignment(target.registration_id, owner).update(latest);
193
+ };
194
+ entry.publish = send;
195
+ entry.writer = {
196
+ update(snapshot) {
197
+ latest = snapshot;
198
+ void resolve(subject).then((target) => {
199
+ if (target !== void 0) send(target);
200
+ });
201
+ },
202
+ retire() {
203
+ latest = void 0;
204
+ if (entry.published !== void 0) publication.retireAssignment(entry.published);
205
+ entry.published = void 0;
206
+ }
207
+ };
208
+ held.set(subject, entry);
209
+ return entry.writer;
210
+ },
211
+ retire(subject) {
212
+ held.get(subject)?.writer.retire();
213
+ held.delete(subject);
214
+ },
215
+ async observe(subject, session_id) {
216
+ const target = await resolveAndFollow(subject);
217
+ if (target === void 0)
218
+ return { kind: "unknown", diagnostic: "the daemon records no bound launch for this child" };
219
+ return observeRadarChild(client, {
220
+ request_id: target.child.request_id,
221
+ pane_id: target.child.pane_id,
222
+ registration_id: target.registration_id,
223
+ session_id
224
+ });
225
+ },
226
+ async inspect(subject, session_id) {
227
+ const target = await resolveAndFollow(subject);
228
+ if (target?.child.pane_id === void 0)
229
+ throw new Error("Inspection target is not a bound child with a pane");
230
+ return inspectRadarChild(client, {
231
+ agentId: target.registration_id,
232
+ paneId: target.child.pane_id,
233
+ ...session_id !== void 0 ? { session: session_id } : {}
234
+ });
235
+ }
236
+ };
237
+ }
238
+ export {
239
+ createRadarChildren
240
+ };
@@ -4,12 +4,13 @@ import { lstatSync } from "node:fs";
4
4
  import { createConnection } from "node:net";
5
5
  import { dirname, join } from "node:path";
6
6
  var RADAR_PROTOCOL_VERSION = 2;
7
- var RADAR_MINIMUM_REVISION = 5;
7
+ var RADAR_MINIMUM_REVISION = 7;
8
8
  var RADAR_REGISTRY_CONTRACT = "registry.v1";
9
9
  var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
10
10
  var RADAR_SPAWN_CONTRACT = "spawn.v1";
11
11
  var RADAR_SPAWN_LIST_CONTRACT = "spawn.list.v1";
12
12
  var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
13
+ var RADAR_LIFECYCLE_RESTART_CONTRACT = "lifecycle.restart.v1";
13
14
  var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
14
15
  var RADAR_EVENTS_CONTRACT = "events.v1";
15
16
  var RADAR_COMMUNICATION_CONTRACT = "communication.handoff.v1";
@@ -115,7 +116,7 @@ function decodePresenceAnswer(value) {
115
116
  const claimId = recordAt(value, "claim_id");
116
117
  const standing = recordAt(value, "standing");
117
118
  const subjectId = recordAt(value, "subject_id");
118
- if (!isUuid(registrationId) || !isUuid(claimId) || !["pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
119
+ if (!isUuid(registrationId) || !isUuid(claimId) || !["reserved", "pending", "granted", "duplicate", "released"].includes(String(standing)) || subjectId !== void 0 && !isUuid(subjectId) || standing === "granted" && !isUuid(subjectId))
119
120
  return protocol("presence reply carries no valid claim standing");
120
121
  return { ok: true, value };
121
122
  }
@@ -264,7 +265,7 @@ function unresolvedOperation(requestId, detail) {
264
265
  message: `focus ${requestId} is unresolved (${detail})`
265
266
  };
266
267
  }
267
- var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
268
+ var RADAR_REGISTRATION_ENV = "PI_RADAR_REGISTRATION_ID";
268
269
  var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
269
270
  "PI_RADAR_MANAGED_SUBJECT_ID",
270
271
  "PI_RADAR_MANAGED_LAUNCH_ID",
@@ -315,6 +316,7 @@ function spawnEdge(value) {
315
316
  const source = stringAt(bound, "source");
316
317
  const incarnation = stringAt(bound, "incarnation");
317
318
  const agentId = stringAt(bound, "registration_id");
319
+ const subjectId = stringAt(bound, "subject_id");
318
320
  const exit = exitAt(recordAt(value, "exit_observation"));
319
321
  return {
320
322
  ok: true,
@@ -328,7 +330,8 @@ function spawnEdge(value) {
328
330
  bound: {
329
331
  source,
330
332
  incarnation,
331
- ...isUuid(agentId) ? { registration_id: agentId } : {}
333
+ ...isUuid(agentId) ? { registration_id: agentId } : {},
334
+ ...isUuid(subjectId) ? { subject_id: subjectId } : {}
332
335
  }
333
336
  } : {},
334
337
  ...exit !== void 0 ? { exit_observation: exit } : {}
@@ -400,6 +403,8 @@ function spawnTopology(value) {
400
403
  function spawnRequestShape(request) {
401
404
  if (!isUuid(request.request_id))
402
405
  return "spawn requires a canonical request UUID";
406
+ if (request.continue_subject_id !== void 0 && !isUuid(request.continue_subject_id))
407
+ return "continue_subject_id must be a canonical UUID";
403
408
  if (!isUuid(request.parent))
404
409
  return "spawn requires the parent's canonical agent UUID";
405
410
  if (!isUuid(request.parent_session_id))
@@ -413,8 +418,8 @@ function spawnRequestShape(request) {
413
418
  const env = Object.entries(request.env ?? {});
414
419
  if (env.length > 256) return "spawn env names more than 256 entries";
415
420
  for (const [name, value] of env) {
416
- if (name === RADAR_SPAWN_TOKEN_ENV)
417
- return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
421
+ if (name === RADAR_REGISTRATION_ENV)
422
+ return `spawn env cannot set the reserved ${RADAR_REGISTRATION_ENV}`;
418
423
  if (RADAR_MANAGED_IDENTITY_ENV.has(name))
419
424
  return `spawn env cannot set Radar-issued identity variable ${name}`;
420
425
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
@@ -866,7 +871,8 @@ function createRadarClient(options = {}) {
866
871
  argv: request.argv,
867
872
  ...request.cwd !== void 0 ? { cwd: request.cwd } : {},
868
873
  ...carriesContext && request.env !== void 0 ? { env: request.env } : {},
869
- ...request.managed !== void 0 ? { managed: request.managed } : {}
874
+ ...request.managed !== void 0 ? { managed: request.managed } : {},
875
+ ...request.continue_subject_id !== void 0 ? { continue_subject_id: request.continue_subject_id } : {}
870
876
  },
871
877
  requester
872
878
  },
@@ -894,6 +900,33 @@ function createRadarClient(options = {}) {
894
900
  }
895
901
  );
896
902
  },
903
+ async restart(request) {
904
+ if (!isUuid(request.request_id) || !isUuid(request.parent) || !isUuid(request.subject_id))
905
+ return {
906
+ ok: false,
907
+ code: "bad_params",
908
+ message: "restart requires canonical request, parent and subject UUIDs"
909
+ };
910
+ return withContract(RADAR_LIFECYCLE_RESTART_CONTRACT, async () => {
911
+ const answer = await call(
912
+ "lifecycle.restart",
913
+ {
914
+ request: {
915
+ subject_id: request.subject_id,
916
+ parent: request.parent,
917
+ executable: request.executable,
918
+ argv: request.argv,
919
+ ...request.cwd !== void 0 ? { cwd: request.cwd } : {},
920
+ ...request.env !== void 0 ? { env: request.env } : {}
921
+ },
922
+ requester
923
+ },
924
+ request.request_id
925
+ );
926
+ if (answer.ok === false) return answer;
927
+ return focusOutcome(recordAt(answer.value, "request"), request.request_id);
928
+ });
929
+ },
897
930
  async spawnList(request) {
898
931
  const registrationId = processRegistrationId;
899
932
  if (registrationId === void 0)
@@ -1265,11 +1298,13 @@ export {
1265
1298
  RADAR_COMMUNICATION_CONTRACT,
1266
1299
  RADAR_EVENTS_CONTRACT,
1267
1300
  RADAR_EVENT_RETRY_MS,
1301
+ RADAR_LIFECYCLE_RESTART_CONTRACT,
1268
1302
  RADAR_MAX_AWAITED,
1269
1303
  RADAR_MAX_BACKGROUND_TASKS,
1270
1304
  RADAR_MAX_LINE_BYTES,
1271
1305
  RADAR_MINIMUM_REVISION,
1272
1306
  RADAR_PROTOCOL_VERSION,
1307
+ RADAR_REGISTRATION_ENV,
1273
1308
  RADAR_REGISTRY_CONTRACT,
1274
1309
  RADAR_REGISTRY_FACTS_CONTRACT,
1275
1310
  RADAR_REQUESTER,
@@ -1277,7 +1312,6 @@ export {
1277
1312
  RADAR_SPAWN_CONTRACT,
1278
1313
  RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT,
1279
1314
  RADAR_SPAWN_LIST_CONTRACT,
1280
- RADAR_SPAWN_TOKEN_ENV,
1281
1315
  boundedRadarAssignment,
1282
1316
  boundedRadarFacts,
1283
1317
  boundedRadarSnapshot,
@@ -272,7 +272,13 @@ function createRadarProcessSlot() {
272
272
  }
273
273
  function radarProcessSlot() {
274
274
  const host = globalThis;
275
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
275
+ if (!host[PROCESS_SLOT]) {
276
+ const slot = createRadarProcessSlot();
277
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
278
+ delete process.env.PI_RADAR_REGISTRATION_ID;
279
+ host[PROCESS_SLOT] = slot;
280
+ }
281
+ return host[PROCESS_SLOT];
276
282
  }
277
283
  function radarProcessIncarnation(slot = radarProcessSlot()) {
278
284
  return slot.incarnation ??= randomUUID();
@@ -702,11 +708,6 @@ function createPresenceReporter(input) {
702
708
  await releasePending();
703
709
  const registration = await input.registration();
704
710
  if (!registration?.process || state !== claim || stopped) return void 0;
705
- if (claim.initial_load) {
706
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
707
- claim.initial_load = false;
708
- if (!save()) return void 0;
709
- }
710
711
  const answer = await client.presence({
711
712
  ...registration,
712
713
  process: registration.process,
@@ -745,7 +746,7 @@ function createPresenceReporter(input) {
745
746
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
746
747
  }
747
748
  standing = void 0;
748
- state = { version: 2, session, claim_id: randomUUID(), initial_load: state === void 0, releases };
749
+ state = { version: 2, session, claim_id: randomUUID(), releases };
749
750
  save();
750
751
  stopped = false;
751
752
  void ensure();
@@ -828,8 +829,8 @@ function createRadarPublicationInner(options) {
828
829
  const store = options.store ?? createPublicationStore(dataRoot);
829
830
  const scheduler = options.scheduler ?? systemScheduler;
830
831
  const client = options.client;
831
- const spawnToken = options.spawnToken;
832
832
  const slot = options.slot ?? radarProcessSlot();
833
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
833
834
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
834
835
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
835
836
  const cached = slot.publications.get(cacheKey);
@@ -953,7 +954,7 @@ function createRadarPublicationInner(options) {
953
954
  return {
954
955
  ...identity,
955
956
  ...location?.state === "resolved" ? { location: location.location } : {},
956
- ...spawnToken ? { spawn_token: spawnToken } : {}
957
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
957
958
  };
958
959
  },
959
960
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -1146,7 +1147,7 @@ function createRadarExecutionAdapter(pi, options) {
1146
1147
  },
1147
1148
  ...options.location !== void 0 ? { location: options.location } : {},
1148
1149
  ...managed !== void 0 ? { binding: managed } : {},
1149
- ...options.spawnToken !== void 0 ? { spawnToken: options.spawnToken } : {}
1150
+ ...options.launchRegistrationId !== void 0 ? { launchRegistrationId: options.launchRegistrationId } : {}
1150
1151
  });
1151
1152
  } catch {
1152
1153
  publication = unavailablePublication2();
@@ -141,25 +141,31 @@ async function launchRadarWorker(options) {
141
141
  const pollMs = options.pollMs ?? 250;
142
142
  const deadline = Date.now() + options.timeoutMs;
143
143
  const remaining = () => Math.max(0, deadline - Date.now());
144
- const pane = launch.parent.location?.pane;
145
- if (!isUuid(launch.parent.registration_id))
144
+ const parent = await options.publication.subject();
145
+ if (parent === void 0 || !isUuid(parent.registration_id))
146
146
  return refusedLaunch(
147
- "the launch has no exact parent registration to spawn under"
147
+ "this process has no Radar registration, so a worker cannot be spawned under it"
148
148
  );
149
- if (!isUuid(launch.parent.subject_id))
149
+ if (parent.subject_id === void 0 || !isUuid(parent.subject_id))
150
150
  return refusedLaunch(
151
- "the launch has no managed Radar subject for its parent"
151
+ "this process has no managed Radar subject, so a worker cannot be spawned under it"
152
152
  );
153
+ const pane = parent.location?.pane;
153
154
  if (pane === void 0 || pane === "")
154
155
  return refusedLaunch(
155
- "this process's Radar registration names no pane, so it cannot be a spawn parent"
156
+ "this process's Radar registration names no pane, so a worker cannot be placed beside it"
157
+ );
158
+ if (pane !== launch.pane_id)
159
+ return refusedLaunch(
160
+ `this process's Radar registration places it in pane ${pane}, not in the pane this launch would split`
156
161
  );
157
162
  const spawned = await client.spawn({
158
163
  request_id: launch.request_id,
159
- parent: launch.parent.registration_id,
164
+ parent: parent.registration_id,
160
165
  parent_session_id: launch.parent_session_id,
161
166
  executable: launch.executable,
162
- managed: { parent_subject_id: launch.parent.subject_id },
167
+ managed: { parent_subject_id: parent.subject_id },
168
+ ...launch.continues !== void 0 ? { continue_subject_id: launch.continues } : {},
163
169
  argv: launch.argv,
164
170
  ...launch.cwd !== "" ? { cwd: launch.cwd } : {},
165
171
  ...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
@@ -182,16 +188,6 @@ async function launchRadarWorker(options) {
182
188
  });
183
189
  if (bound.kind !== "bound") return bound;
184
190
  const agentId = bound.registration_id;
185
- if (launch.onBound !== void 0) {
186
- try {
187
- launch.onBound({ registration_id: agentId, pane_id: bound.pane_id });
188
- } catch (error) {
189
- return unresolvedLaunch(
190
- launch.request_id,
191
- `the daemon's binding of the child ${agentId} could not be recorded (${String(error)})`
192
- );
193
- }
194
- }
195
191
  try {
196
192
  const state = await options.readers.waitForState(
197
193
  launch.mailbox,
@@ -205,19 +201,15 @@ async function launchRadarWorker(options) {
205
201
  // the pane the daemon created is what refuses a child that inherited
206
202
  // another process's coordinates.
207
203
  paneId: radarPaneHandle(bound.pane_id)
208
- }) && (launch.observedChildAgent === void 0 || launch.observedChildAgent() === agentId),
204
+ }),
209
205
  { timeoutMs: remaining(), ...signal !== void 0 ? { signal } : {} }
210
206
  );
211
207
  return {
212
208
  kind: "ready",
213
209
  value: {
214
210
  request_id: launch.request_id,
215
- registration_id: agentId,
211
+ subject_id: bound.subject_id,
216
212
  pane_id: bound.pane_id,
217
- bound_process: {
218
- source: bound.source,
219
- incarnation: bound.incarnation
220
- },
221
213
  state
222
214
  }
223
215
  };
@@ -267,8 +259,15 @@ async function waitForChild(client, requestId, options) {
267
259
  requestId,
268
260
  `the daemon bound the child ${agentId} without recording the process it registered as`
269
261
  );
262
+ const subjectId = edge.bound?.subject_id;
263
+ if (subjectId === void 0 || !isUuid(subjectId))
264
+ return unresolvedLaunch(
265
+ requestId,
266
+ `the daemon bound the child ${agentId} without recording its subject`
267
+ );
270
268
  return {
271
269
  kind: "bound",
270
+ subject_id: subjectId,
272
271
  registration_id: agentId,
273
272
  pane_id: pane,
274
273
  source,
@@ -274,7 +274,13 @@ function createRadarProcessSlot() {
274
274
  }
275
275
  function radarProcessSlot() {
276
276
  const host = globalThis;
277
- return host[PROCESS_SLOT] ??= createRadarProcessSlot();
277
+ if (!host[PROCESS_SLOT]) {
278
+ const slot = createRadarProcessSlot();
279
+ slot.launchRegistrationId = process.env.PI_RADAR_REGISTRATION_ID;
280
+ delete process.env.PI_RADAR_REGISTRATION_ID;
281
+ host[PROCESS_SLOT] = slot;
282
+ }
283
+ return host[PROCESS_SLOT];
278
284
  }
279
285
  function radarProcessIncarnation(slot = radarProcessSlot()) {
280
286
  return slot.incarnation ??= randomUUID();
@@ -707,11 +713,6 @@ function createPresenceReporter(input) {
707
713
  await releasePending();
708
714
  const registration = await input.registration();
709
715
  if (!registration?.process || state !== claim || stopped) return void 0;
710
- if (claim.initial_load) {
711
- if (registration.spawn_token) claim.claim_id = registration.spawn_token;
712
- claim.initial_load = false;
713
- if (!save()) return void 0;
714
- }
715
716
  const answer = await client.presence({
716
717
  ...registration,
717
718
  process: registration.process,
@@ -750,7 +751,7 @@ function createPresenceReporter(input) {
750
751
  releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
751
752
  }
752
753
  standing = void 0;
753
- state = { version: 2, session, claim_id: randomUUID(), initial_load: state === void 0, releases };
754
+ state = { version: 2, session, claim_id: randomUUID(), releases };
754
755
  save();
755
756
  stopped = false;
756
757
  void ensure();
@@ -833,8 +834,8 @@ function createRadarPublicationInner(options) {
833
834
  const store = options.store ?? createPublicationStore(dataRoot);
834
835
  const scheduler = options.scheduler ?? systemScheduler;
835
836
  const client = options.client;
836
- const spawnToken = options.spawnToken;
837
837
  const slot = options.slot ?? radarProcessSlot();
838
+ const launchRegistrationId = options.launchRegistrationId ?? slot.launchRegistrationId;
838
839
  const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
839
840
  const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
840
841
  const cached = slot.publications.get(cacheKey);
@@ -958,7 +959,7 @@ function createRadarPublicationInner(options) {
958
959
  return {
959
960
  ...identity,
960
961
  ...location?.state === "resolved" ? { location: location.location } : {},
961
- ...spawnToken ? { spawn_token: spawnToken } : {}
962
+ ...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
962
963
  };
963
964
  },
964
965
  heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
@@ -1,6 +1,6 @@
1
1
  // extension/radar-runtime.ts
2
2
  var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
3
- var RADAR_RUNTIME_VERSION = 2;
3
+ var RADAR_RUNTIME_VERSION = 3;
4
4
  function lazyRadarRuntime(start) {
5
5
  let pending;
6
6
  return {