pi-radar 0.7.0 → 0.10.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.
- package/README.md +12 -0
- package/dist/extension/baseline.js +386 -10
- package/dist/extension/radar-children.js +344 -0
- package/dist/extension/radar-client.js +47 -4
- package/dist/extension/radar-launch.js +42 -112
- package/dist/extension/radar-runtime.js +1 -1
- package/dist/index.js +471 -341
- package/extension/baseline.ts +2 -0
- package/extension/generated-contracts.ts +15 -2
- package/extension/radar-children.ts +222 -0
- package/extension/radar-client.ts +70 -2
- package/extension/radar-launch.ts +91 -274
- package/extension/radar-runtime.ts +4 -1
- package/index.ts +1 -0
- package/package.json +8 -3
- package/schema/public/v1/observation-get-response.deserialize.schema.json +1 -13
- package/schema/public/v1/observation-get-response.serialize.schema.json +3 -17
- package/schema/public/v1/observation-snapshot.deserialize.schema.json +1 -13
- package/schema/public/v1/observation-snapshot.serialize.schema.json +3 -17
- package/schema/public/v1/spawn-list-page.deserialize.schema.json +7 -0
- package/schema/public/v1/spawn-list-page.serialize.schema.json +7 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// extension/radar-children.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// extension/radar-inspection.ts
|
|
5
|
+
var INSPECTION_LINES = 80;
|
|
6
|
+
var INSPECTION_OUTPUT_BYTES = 16 * 1024;
|
|
7
|
+
async function provedRead(client, target) {
|
|
8
|
+
const read = await client.agentGet(target.agentId);
|
|
9
|
+
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;
|
|
10
|
+
}
|
|
11
|
+
async function inspectRadarChild(client, target) {
|
|
12
|
+
const before = await provedRead(client, target);
|
|
13
|
+
if (before === void 0)
|
|
14
|
+
throw new Error("Inspection target identity did not match");
|
|
15
|
+
const output = await client.output({
|
|
16
|
+
pane_id: target.paneId,
|
|
17
|
+
source: "recent_unwrapped",
|
|
18
|
+
lines: INSPECTION_LINES
|
|
19
|
+
});
|
|
20
|
+
if (output.ok === false)
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Inspection output unavailable (${output.code}: ${output.message})`
|
|
23
|
+
);
|
|
24
|
+
const foreground = await client.processInfo({ pane_id: target.paneId });
|
|
25
|
+
const after = await provedRead(client, target);
|
|
26
|
+
if (after === void 0)
|
|
27
|
+
throw new Error("Inspection target changed during capture");
|
|
28
|
+
const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
|
|
29
|
+
const bounded = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
capturedAt: Date.now(),
|
|
32
|
+
recentOutputTruncated: output.value.truncated || bounded.truncated,
|
|
33
|
+
...bounded.text ? { recentOutput: bounded.text } : {},
|
|
34
|
+
...foreground.ok === true ? { foreground: foreground.value } : {},
|
|
35
|
+
...after.activity !== void 0 ? { activity: after.activity } : {}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function utf8Tail(value, maxBytes) {
|
|
39
|
+
const bytes = Buffer.from(value);
|
|
40
|
+
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
41
|
+
let start = bytes.length - maxBytes;
|
|
42
|
+
while (start < bytes.length && (bytes[start] & 192) === 128) start++;
|
|
43
|
+
return { text: bytes.subarray(start).toString(), truncated: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// extension/radar-launch.ts
|
|
47
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
48
|
+
|
|
49
|
+
// extension/radar-client.ts
|
|
50
|
+
var RADAR_MAX_LINE_BYTES = 1024 * 1024;
|
|
51
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
52
|
+
function isUuid(value) {
|
|
53
|
+
return typeof value === "string" && UUID.test(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// extension/radar-launch.ts
|
|
57
|
+
function unknownChild(reason) {
|
|
58
|
+
return { kind: "unknown", diagnostic: trajectory(reason) };
|
|
59
|
+
}
|
|
60
|
+
function trajectory(reason) {
|
|
61
|
+
return `the daemon's current record of this launch's child is ${reason}`;
|
|
62
|
+
}
|
|
63
|
+
async function observeRadarChild(client, provenance) {
|
|
64
|
+
if (provenance.registration_id === void 0)
|
|
65
|
+
return unknownChild("not bound to a child by this launch");
|
|
66
|
+
if (provenance.pane_id === void 0)
|
|
67
|
+
return unknownChild("without a pane this launch recorded for it");
|
|
68
|
+
const read = await client.spawnGet(provenance.request_id);
|
|
69
|
+
if (read.ok === false)
|
|
70
|
+
return unknownChild(
|
|
71
|
+
`not readable (${read.code}: ${read.message})`
|
|
72
|
+
);
|
|
73
|
+
const topology = read.value;
|
|
74
|
+
const edge = topology.edge;
|
|
75
|
+
if (edge.bound === void 0 || edge.bound.registration_id === void 0)
|
|
76
|
+
return unknownChild("not bound to a child by the daemon");
|
|
77
|
+
if (edge.bound.registration_id !== provenance.registration_id)
|
|
78
|
+
return unknownChild(
|
|
79
|
+
`bound to another subject (${edge.bound.registration_id}), not to ${provenance.registration_id}`
|
|
80
|
+
);
|
|
81
|
+
if (edge.location?.pane === void 0)
|
|
82
|
+
return unknownChild("recorded without a pane");
|
|
83
|
+
if (edge.location.pane !== provenance.pane_id)
|
|
84
|
+
return unknownChild(
|
|
85
|
+
`recorded on pane ${edge.location.pane}, not on ${provenance.pane_id}`
|
|
86
|
+
);
|
|
87
|
+
if (edge.exit_observation?.state !== "unknown")
|
|
88
|
+
return unknownChild("recorded with an observed process exit");
|
|
89
|
+
if (topology.freshness !== "fresh")
|
|
90
|
+
return unknownChild(
|
|
91
|
+
"on a pane the daemon's own inventory does not currently report"
|
|
92
|
+
);
|
|
93
|
+
if (topology.state !== "bound")
|
|
94
|
+
return unknownChild(
|
|
95
|
+
`not currently placed (state ${topology.state ?? "unreported"})`
|
|
96
|
+
);
|
|
97
|
+
const reply = await client.agentGet(edge.bound.registration_id);
|
|
98
|
+
if (reply.ok === false)
|
|
99
|
+
return unknownChild(
|
|
100
|
+
`process verdict not readable (${reply.code}: ${reply.message})`
|
|
101
|
+
);
|
|
102
|
+
const { verification, location, contextSession } = reply.value;
|
|
103
|
+
if (verification === "absent" || verification === "mismatched")
|
|
104
|
+
return {
|
|
105
|
+
kind: "gone",
|
|
106
|
+
verification,
|
|
107
|
+
diagnostic: trajectory(`the registered process is ${verification}`)
|
|
108
|
+
};
|
|
109
|
+
if (verification !== "verified")
|
|
110
|
+
return unknownChild(`process verdict is ${verification}`);
|
|
111
|
+
if (location?.pane !== provenance.pane_id)
|
|
112
|
+
return unknownChild(
|
|
113
|
+
`registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
|
|
114
|
+
);
|
|
115
|
+
if (provenance.session_id === void 0)
|
|
116
|
+
return unknownChild("without a session this launch recorded for it");
|
|
117
|
+
if (contextSession !== provenance.session_id)
|
|
118
|
+
return unknownChild(
|
|
119
|
+
`publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
|
|
120
|
+
);
|
|
121
|
+
return {
|
|
122
|
+
kind: "live",
|
|
123
|
+
evidence: {
|
|
124
|
+
request_id: provenance.request_id,
|
|
125
|
+
registration_id: edge.bound.registration_id,
|
|
126
|
+
source: edge.bound.source,
|
|
127
|
+
incarnation: edge.bound.incarnation,
|
|
128
|
+
pane_id: edge.location.pane
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function refusedLaunch(message) {
|
|
133
|
+
return { kind: "failed", message };
|
|
134
|
+
}
|
|
135
|
+
function unresolvedLaunch(requestId, message) {
|
|
136
|
+
return {
|
|
137
|
+
kind: "unresolved",
|
|
138
|
+
request_id: requestId,
|
|
139
|
+
message: `spawn ${requestId} is unresolved (${message})`
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
var PRE_EFFECT_CODES = [
|
|
143
|
+
"absent",
|
|
144
|
+
"unsupported",
|
|
145
|
+
"bad_version",
|
|
146
|
+
"bad_request",
|
|
147
|
+
"bad_params",
|
|
148
|
+
"unknown_method",
|
|
149
|
+
"refused",
|
|
150
|
+
"not_found",
|
|
151
|
+
"busy",
|
|
152
|
+
"backend_unavailable"
|
|
153
|
+
];
|
|
154
|
+
function launchFailure(result, requestId) {
|
|
155
|
+
return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
|
|
156
|
+
}
|
|
157
|
+
async function waitForChild(client, requestId, options) {
|
|
158
|
+
let last = "the daemon has not answered";
|
|
159
|
+
for (; ; ) {
|
|
160
|
+
if (options.signal?.aborted)
|
|
161
|
+
return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
|
|
162
|
+
const answer = await client.spawnGet(requestId);
|
|
163
|
+
if (answer.ok === true) {
|
|
164
|
+
const { edge } = answer.value;
|
|
165
|
+
if (edge.withdrawn !== void 0)
|
|
166
|
+
return refusedLaunch(`the daemon withdrew this launch: ${edge.withdrawn.reason}`);
|
|
167
|
+
if (edge.granted_at === void 0) {
|
|
168
|
+
await delay(options.pollMs);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const pane = edge.location?.pane;
|
|
172
|
+
if (pane === void 0 || pane === "")
|
|
173
|
+
return unresolvedLaunch(
|
|
174
|
+
requestId,
|
|
175
|
+
"the daemon's launch recorded no pane to verify the child against"
|
|
176
|
+
);
|
|
177
|
+
const agentId = edge.bound?.registration_id;
|
|
178
|
+
if (agentId !== void 0 && isUuid(agentId)) {
|
|
179
|
+
const subjectId = edge.bound?.subject_id;
|
|
180
|
+
if (subjectId === void 0 || !isUuid(subjectId))
|
|
181
|
+
return unresolvedLaunch(
|
|
182
|
+
requestId,
|
|
183
|
+
`the daemon bound the child ${agentId} without recording its subject`
|
|
184
|
+
);
|
|
185
|
+
return {
|
|
186
|
+
kind: "bound",
|
|
187
|
+
subject_id: subjectId,
|
|
188
|
+
pane_id: pane
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
last = "no child has registered with this launch's token yet";
|
|
192
|
+
} else {
|
|
193
|
+
return unresolvedLaunch(requestId, answer.message);
|
|
194
|
+
}
|
|
195
|
+
await delay(options.pollMs);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// extension/radar-children.ts
|
|
200
|
+
var PAGE = 100;
|
|
201
|
+
var MAX_PAGES = 20;
|
|
202
|
+
function createRadarChildren(client, publication) {
|
|
203
|
+
const held = /* @__PURE__ */ new Map();
|
|
204
|
+
async function resolveAll() {
|
|
205
|
+
const newest = /* @__PURE__ */ new Map();
|
|
206
|
+
let after;
|
|
207
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
208
|
+
const read = await client.spawnList({ ...after !== void 0 ? { after } : {}, limit: PAGE });
|
|
209
|
+
if (read.ok === false) return read;
|
|
210
|
+
for (const edge of read.value.spawns) {
|
|
211
|
+
const bound = edge.bound;
|
|
212
|
+
if (!bound?.subject_id || !bound.registration_id || edge.granted_at == null || edge.withdrawn != null) continue;
|
|
213
|
+
const at = Date.parse(bound.bound_at);
|
|
214
|
+
if (Number.isNaN(at)) continue;
|
|
215
|
+
const current = newest.get(bound.subject_id);
|
|
216
|
+
if (current !== void 0 && current.at >= at) continue;
|
|
217
|
+
newest.set(bound.subject_id, {
|
|
218
|
+
at,
|
|
219
|
+
registration_id: bound.registration_id,
|
|
220
|
+
child: {
|
|
221
|
+
subject_id: bound.subject_id,
|
|
222
|
+
request_id: edge.request_id,
|
|
223
|
+
...edge.location?.pane ? { pane_id: edge.location.pane } : {},
|
|
224
|
+
state: edge.state,
|
|
225
|
+
exited: edge.exit_observation != null && edge.exit_observation.state !== "unknown"
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (!read.value.next) return { ok: true, value: newest };
|
|
230
|
+
after = read.value.next;
|
|
231
|
+
}
|
|
232
|
+
return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
|
|
233
|
+
}
|
|
234
|
+
async function resolve(subject) {
|
|
235
|
+
const all = await resolveAll();
|
|
236
|
+
return all.ok ? all.value.get(subject) : void 0;
|
|
237
|
+
}
|
|
238
|
+
async function resolveAndFollow(subject) {
|
|
239
|
+
const all = await resolveAll();
|
|
240
|
+
if (!all.ok) return void 0;
|
|
241
|
+
follow(all.value);
|
|
242
|
+
return all.value.get(subject);
|
|
243
|
+
}
|
|
244
|
+
function follow(all) {
|
|
245
|
+
for (const [subject, entry] of held) {
|
|
246
|
+
const target = all.get(subject);
|
|
247
|
+
if (target !== void 0 && entry.published !== target.registration_id)
|
|
248
|
+
entry.publish?.(target);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
async list() {
|
|
253
|
+
const all = await resolveAll();
|
|
254
|
+
if (!all.ok) return all;
|
|
255
|
+
follow(all.value);
|
|
256
|
+
return { ok: true, value: [...all.value.values()].map((entry) => entry.child) };
|
|
257
|
+
},
|
|
258
|
+
async restart(subject, program, options) {
|
|
259
|
+
const parent = await publication.subject();
|
|
260
|
+
if (parent?.subject_id === void 0)
|
|
261
|
+
return { kind: "failed", message: "this process has no Radar registration to restart a child under" };
|
|
262
|
+
const request_id = randomUUID();
|
|
263
|
+
const requested = await client.restart({
|
|
264
|
+
request_id,
|
|
265
|
+
parent: parent.registration_id,
|
|
266
|
+
subject_id: subject,
|
|
267
|
+
...program
|
|
268
|
+
});
|
|
269
|
+
if (requested.ok === false)
|
|
270
|
+
return launchFailure(requested, request_id);
|
|
271
|
+
const bound = await waitForChild(client, requested.value.launch_request_id, {
|
|
272
|
+
pollMs: options?.pollMs ?? 250,
|
|
273
|
+
...options?.signal !== void 0 ? { signal: options.signal } : {}
|
|
274
|
+
});
|
|
275
|
+
if (bound.kind !== "bound") return bound;
|
|
276
|
+
await resolveAndFollow(subject);
|
|
277
|
+
return {
|
|
278
|
+
kind: "ready",
|
|
279
|
+
value: {
|
|
280
|
+
request_id: requested.value.launch_request_id,
|
|
281
|
+
subject_id: bound.subject_id,
|
|
282
|
+
pane_id: bound.pane_id
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
assignment(subject, owner) {
|
|
287
|
+
const existing = held.get(subject);
|
|
288
|
+
if (existing !== void 0) return existing.writer;
|
|
289
|
+
let latest;
|
|
290
|
+
const entry = { writer: void 0 };
|
|
291
|
+
const send = (target) => {
|
|
292
|
+
if (latest === void 0) return;
|
|
293
|
+
if (entry.published !== void 0 && entry.published !== target.registration_id)
|
|
294
|
+
publication.retireAssignment(entry.published);
|
|
295
|
+
entry.published = target.registration_id;
|
|
296
|
+
publication.assignment(target.registration_id, owner).update(latest);
|
|
297
|
+
};
|
|
298
|
+
entry.publish = send;
|
|
299
|
+
entry.writer = {
|
|
300
|
+
update(snapshot) {
|
|
301
|
+
latest = snapshot;
|
|
302
|
+
void resolve(subject).then((target) => {
|
|
303
|
+
if (target !== void 0) send(target);
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
retire() {
|
|
307
|
+
latest = void 0;
|
|
308
|
+
if (entry.published !== void 0) publication.retireAssignment(entry.published);
|
|
309
|
+
entry.published = void 0;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
held.set(subject, entry);
|
|
313
|
+
return entry.writer;
|
|
314
|
+
},
|
|
315
|
+
retire(subject) {
|
|
316
|
+
held.get(subject)?.writer.retire();
|
|
317
|
+
held.delete(subject);
|
|
318
|
+
},
|
|
319
|
+
async observe(subject, session_id) {
|
|
320
|
+
const target = await resolveAndFollow(subject);
|
|
321
|
+
if (target === void 0)
|
|
322
|
+
return { kind: "unknown", diagnostic: "the daemon records no bound launch for this child" };
|
|
323
|
+
return observeRadarChild(client, {
|
|
324
|
+
request_id: target.child.request_id,
|
|
325
|
+
pane_id: target.child.pane_id,
|
|
326
|
+
registration_id: target.registration_id,
|
|
327
|
+
session_id
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
async inspect(subject, session_id) {
|
|
331
|
+
const target = await resolveAndFollow(subject);
|
|
332
|
+
if (target?.child.pane_id === void 0)
|
|
333
|
+
throw new Error("Inspection target is not a bound child with a pane");
|
|
334
|
+
return inspectRadarChild(client, {
|
|
335
|
+
agentId: target.registration_id,
|
|
336
|
+
paneId: target.child.pane_id,
|
|
337
|
+
...session_id !== void 0 ? { session: session_id } : {}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
export {
|
|
343
|
+
createRadarChildren
|
|
344
|
+
};
|
|
@@ -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 =
|
|
7
|
+
var RADAR_MINIMUM_REVISION = 8;
|
|
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";
|
|
@@ -315,7 +316,11 @@ 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"));
|
|
321
|
+
const withdrawnRecord = recordAt(value, "withdrawn");
|
|
322
|
+
const withdrawnAt = stringAt(withdrawnRecord, "at");
|
|
323
|
+
const withdrawnReason = stringAt(withdrawnRecord, "reason");
|
|
319
324
|
return {
|
|
320
325
|
ok: true,
|
|
321
326
|
value: {
|
|
@@ -328,10 +333,13 @@ function spawnEdge(value) {
|
|
|
328
333
|
bound: {
|
|
329
334
|
source,
|
|
330
335
|
incarnation,
|
|
331
|
-
...isUuid(agentId) ? { registration_id: agentId } : {}
|
|
336
|
+
...isUuid(agentId) ? { registration_id: agentId } : {},
|
|
337
|
+
...isUuid(subjectId) ? { subject_id: subjectId } : {}
|
|
332
338
|
}
|
|
333
339
|
} : {},
|
|
334
|
-
...exit !== void 0 ? { exit_observation: exit } : {}
|
|
340
|
+
...exit !== void 0 ? { exit_observation: exit } : {},
|
|
341
|
+
...stringAt(value, "granted_at") !== void 0 ? { granted_at: stringAt(value, "granted_at") } : {},
|
|
342
|
+
...withdrawnAt !== void 0 && withdrawnReason !== void 0 ? { withdrawn: { at: withdrawnAt, reason: withdrawnReason } } : {}
|
|
335
343
|
}
|
|
336
344
|
};
|
|
337
345
|
}
|
|
@@ -400,6 +408,8 @@ function spawnTopology(value) {
|
|
|
400
408
|
function spawnRequestShape(request) {
|
|
401
409
|
if (!isUuid(request.request_id))
|
|
402
410
|
return "spawn requires a canonical request UUID";
|
|
411
|
+
if (request.continue_subject_id !== void 0 && !isUuid(request.continue_subject_id))
|
|
412
|
+
return "continue_subject_id must be a canonical UUID";
|
|
403
413
|
if (!isUuid(request.parent))
|
|
404
414
|
return "spawn requires the parent's canonical agent UUID";
|
|
405
415
|
if (!isUuid(request.parent_session_id))
|
|
@@ -866,7 +876,8 @@ function createRadarClient(options = {}) {
|
|
|
866
876
|
argv: request.argv,
|
|
867
877
|
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
868
878
|
...carriesContext && request.env !== void 0 ? { env: request.env } : {},
|
|
869
|
-
...request.managed !== void 0 ? { managed: request.managed } : {}
|
|
879
|
+
...request.managed !== void 0 ? { managed: request.managed } : {},
|
|
880
|
+
...request.continue_subject_id !== void 0 ? { continue_subject_id: request.continue_subject_id } : {}
|
|
870
881
|
},
|
|
871
882
|
requester
|
|
872
883
|
},
|
|
@@ -894,6 +905,37 @@ function createRadarClient(options = {}) {
|
|
|
894
905
|
}
|
|
895
906
|
);
|
|
896
907
|
},
|
|
908
|
+
async restart(request) {
|
|
909
|
+
if (!isUuid(request.request_id) || !isUuid(request.parent) || !isUuid(request.subject_id))
|
|
910
|
+
return {
|
|
911
|
+
ok: false,
|
|
912
|
+
code: "bad_params",
|
|
913
|
+
message: "restart requires canonical request, parent and subject UUIDs"
|
|
914
|
+
};
|
|
915
|
+
return withContract(RADAR_LIFECYCLE_RESTART_CONTRACT, async () => {
|
|
916
|
+
const answer = await call(
|
|
917
|
+
"lifecycle.restart",
|
|
918
|
+
{
|
|
919
|
+
request: {
|
|
920
|
+
subject_id: request.subject_id,
|
|
921
|
+
parent: request.parent,
|
|
922
|
+
executable: request.executable,
|
|
923
|
+
argv: request.argv,
|
|
924
|
+
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
925
|
+
...request.env !== void 0 ? { env: request.env } : {}
|
|
926
|
+
},
|
|
927
|
+
requester
|
|
928
|
+
},
|
|
929
|
+
request.request_id
|
|
930
|
+
);
|
|
931
|
+
if (answer.ok === false) return answer;
|
|
932
|
+
const settled = focusOutcome(recordAt(answer.value, "request"), request.request_id);
|
|
933
|
+
if (settled.ok === false) return settled;
|
|
934
|
+
const launch = recordAt(recordAt(answer.value, "request"), "launch_request_id");
|
|
935
|
+
if (!isUuid(launch)) return protocol("lifecycle.restart reply names no launch");
|
|
936
|
+
return { ok: true, value: { ...settled.value, launch_request_id: launch } };
|
|
937
|
+
});
|
|
938
|
+
},
|
|
897
939
|
async spawnList(request) {
|
|
898
940
|
const registrationId = processRegistrationId;
|
|
899
941
|
if (registrationId === void 0)
|
|
@@ -1265,6 +1307,7 @@ export {
|
|
|
1265
1307
|
RADAR_COMMUNICATION_CONTRACT,
|
|
1266
1308
|
RADAR_EVENTS_CONTRACT,
|
|
1267
1309
|
RADAR_EVENT_RETRY_MS,
|
|
1310
|
+
RADAR_LIFECYCLE_RESTART_CONTRACT,
|
|
1268
1311
|
RADAR_MAX_AWAITED,
|
|
1269
1312
|
RADAR_MAX_BACKGROUND_TASKS,
|
|
1270
1313
|
RADAR_MAX_LINE_BYTES,
|
|
@@ -8,11 +8,6 @@ function isUuid(value) {
|
|
|
8
8
|
return typeof value === "string" && UUID.test(value);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// extension/radar-location.ts
|
|
12
|
-
function radarPaneHandle(qualified) {
|
|
13
|
-
return qualified.slice(qualified.lastIndexOf(":") + 1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
// extension/radar-launch.ts
|
|
17
12
|
function unknownChild(reason) {
|
|
18
13
|
return { kind: "unknown", diagnostic: trajectory(reason) };
|
|
@@ -104,7 +99,7 @@ async function relaunchRefusalForChild(client, agentId) {
|
|
|
104
99
|
return `the child's registered process is ${verification === "verified" ? "still running" : "not proved gone"}`;
|
|
105
100
|
}
|
|
106
101
|
function refusedLaunch(message) {
|
|
107
|
-
return { kind: "
|
|
102
|
+
return { kind: "failed", message };
|
|
108
103
|
}
|
|
109
104
|
function unresolvedLaunch(requestId, message) {
|
|
110
105
|
return {
|
|
@@ -113,14 +108,6 @@ function unresolvedLaunch(requestId, message) {
|
|
|
113
108
|
message: `spawn ${requestId} is unresolved (${message})`
|
|
114
109
|
};
|
|
115
110
|
}
|
|
116
|
-
function edgeStanding(edge) {
|
|
117
|
-
if (edge.created === "refused" || edge.launched === "refused")
|
|
118
|
-
return "refused";
|
|
119
|
-
return edge.created === "completed" && edge.launched === "completed" ? "launched" : "unknown";
|
|
120
|
-
}
|
|
121
|
-
function edgeMessage(edge) {
|
|
122
|
-
return `the daemon's spawn record is created ${edge.created} and launched ${edge.launched}`;
|
|
123
|
-
}
|
|
124
111
|
var PRE_EFFECT_CODES = [
|
|
125
112
|
"absent",
|
|
126
113
|
"unsupported",
|
|
@@ -133,101 +120,58 @@ var PRE_EFFECT_CODES = [
|
|
|
133
120
|
"busy",
|
|
134
121
|
"backend_unavailable"
|
|
135
122
|
];
|
|
136
|
-
function
|
|
123
|
+
function launchFailure(result, requestId) {
|
|
137
124
|
return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
|
|
138
125
|
}
|
|
139
126
|
async function launchRadarWorker(options) {
|
|
140
127
|
const { client, launch, signal } = options;
|
|
141
128
|
const pollMs = options.pollMs ?? 250;
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const pane = launch.parent.location?.pane;
|
|
145
|
-
if (!isUuid(launch.parent.registration_id))
|
|
129
|
+
const parent = await options.publication.subject();
|
|
130
|
+
if (parent === void 0 || !isUuid(parent.registration_id))
|
|
146
131
|
return refusedLaunch(
|
|
147
|
-
"
|
|
132
|
+
"this process has no Radar registration, so a worker cannot be spawned under it"
|
|
148
133
|
);
|
|
149
|
-
if (!isUuid(
|
|
134
|
+
if (parent.subject_id === void 0 || !isUuid(parent.subject_id))
|
|
150
135
|
return refusedLaunch(
|
|
151
|
-
"
|
|
136
|
+
"this process has no managed Radar subject, so a worker cannot be spawned under it"
|
|
152
137
|
);
|
|
138
|
+
const pane = parent.location?.pane;
|
|
153
139
|
if (pane === void 0 || pane === "")
|
|
154
140
|
return refusedLaunch(
|
|
155
|
-
"this process's Radar registration names no pane, so
|
|
141
|
+
"this process's Radar registration names no pane, so a worker cannot be placed beside it"
|
|
142
|
+
);
|
|
143
|
+
if (pane !== launch.pane_id)
|
|
144
|
+
return refusedLaunch(
|
|
145
|
+
`this process's Radar registration places it in pane ${pane}, not in the pane this launch would split`
|
|
156
146
|
);
|
|
157
147
|
const spawned = await client.spawn({
|
|
158
148
|
request_id: launch.request_id,
|
|
159
|
-
parent:
|
|
149
|
+
parent: parent.registration_id,
|
|
160
150
|
parent_session_id: launch.parent_session_id,
|
|
161
151
|
executable: launch.executable,
|
|
162
|
-
managed: { parent_subject_id:
|
|
152
|
+
managed: { parent_subject_id: parent.subject_id },
|
|
153
|
+
...launch.continues !== void 0 ? { continue_subject_id: launch.continues } : {},
|
|
163
154
|
argv: launch.argv,
|
|
164
155
|
...launch.cwd !== "" ? { cwd: launch.cwd } : {},
|
|
165
156
|
...Object.keys(launch.env).length > 0 ? { env: launch.env } : {}
|
|
166
157
|
});
|
|
167
158
|
if (spawned.ok === false) {
|
|
168
|
-
const refusal =
|
|
169
|
-
if (refusal.kind === "
|
|
170
|
-
}
|
|
171
|
-
if (spawned.ok === true) {
|
|
172
|
-
const standing = edgeStanding(spawned.value);
|
|
173
|
-
if (standing === "refused")
|
|
174
|
-
return refusedLaunch(edgeMessage(spawned.value));
|
|
175
|
-
if (standing === "unknown")
|
|
176
|
-
return unresolvedLaunch(launch.request_id, edgeMessage(spawned.value));
|
|
159
|
+
const refusal = launchFailure(spawned, launch.request_id);
|
|
160
|
+
if (refusal.kind === "failed") return refusal;
|
|
177
161
|
}
|
|
178
162
|
const bound = await waitForChild(client, launch.request_id, {
|
|
179
|
-
remaining,
|
|
180
163
|
pollMs,
|
|
181
164
|
...signal !== void 0 ? { signal } : {}
|
|
182
165
|
});
|
|
183
166
|
if (bound.kind !== "bound") return bound;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
launch.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
launch.request_id,
|
|
191
|
-
`the daemon's binding of the child ${agentId} could not be recorded (${String(error)})`
|
|
192
|
-
);
|
|
167
|
+
return {
|
|
168
|
+
kind: "ready",
|
|
169
|
+
value: {
|
|
170
|
+
request_id: launch.request_id,
|
|
171
|
+
subject_id: bound.subject_id,
|
|
172
|
+
pane_id: bound.pane_id
|
|
193
173
|
}
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
const state = await options.readers.waitForState(
|
|
197
|
-
launch.mailbox,
|
|
198
|
-
(candidate) => options.readers.startupAckMatches(candidate, {
|
|
199
|
-
generation: launch.generation,
|
|
200
|
-
runId: launch.identity.runId,
|
|
201
|
-
ownerSessionId: launch.identity.ownerSessionId,
|
|
202
|
-
workspaceId: launch.identity.workspaceId,
|
|
203
|
-
agentLabel: launch.identity.agentLabel,
|
|
204
|
-
// The child writes the raw TMUX_PANE of the pane it runs in. Requiring
|
|
205
|
-
// the pane the daemon created is what refuses a child that inherited
|
|
206
|
-
// another process's coordinates.
|
|
207
|
-
paneId: radarPaneHandle(bound.pane_id)
|
|
208
|
-
}) && (launch.observedChildAgent === void 0 || launch.observedChildAgent() === agentId),
|
|
209
|
-
{ timeoutMs: remaining(), ...signal !== void 0 ? { signal } : {} }
|
|
210
|
-
);
|
|
211
|
-
return {
|
|
212
|
-
kind: "ready",
|
|
213
|
-
value: {
|
|
214
|
-
request_id: launch.request_id,
|
|
215
|
-
registration_id: agentId,
|
|
216
|
-
pane_id: bound.pane_id,
|
|
217
|
-
bound_process: {
|
|
218
|
-
source: bound.source,
|
|
219
|
-
incarnation: bound.incarnation
|
|
220
|
-
},
|
|
221
|
-
state
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
} catch (error) {
|
|
225
|
-
const claimed = options.readers.readState(launch.mailbox)?.paneId;
|
|
226
|
-
return unresolvedLaunch(
|
|
227
|
-
launch.request_id,
|
|
228
|
-
claimed !== void 0 && claimed !== radarPaneHandle(bound.pane_id) ? `the bound child ${agentId} claims the pane ${claimed}, not the pane ${bound.pane_id} this launch created for it` : `the bound child ${agentId} has not acknowledged this launch's startup (${String(error)})`
|
|
229
|
-
);
|
|
230
|
-
}
|
|
174
|
+
};
|
|
231
175
|
}
|
|
232
176
|
async function waitForChild(client, requestId, options) {
|
|
233
177
|
let last = "the daemon has not answered";
|
|
@@ -236,59 +180,45 @@ async function waitForChild(client, requestId, options) {
|
|
|
236
180
|
return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
|
|
237
181
|
const answer = await client.spawnGet(requestId);
|
|
238
182
|
if (answer.ok === true) {
|
|
239
|
-
const { edge
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
183
|
+
const { edge } = answer.value;
|
|
184
|
+
if (edge.withdrawn !== void 0)
|
|
185
|
+
return refusedLaunch(`the daemon withdrew this launch: ${edge.withdrawn.reason}`);
|
|
186
|
+
if (edge.granted_at === void 0) {
|
|
187
|
+
await delay(options.pollMs);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
245
190
|
const pane = edge.location?.pane;
|
|
246
191
|
if (pane === void 0 || pane === "")
|
|
247
192
|
return unresolvedLaunch(
|
|
248
193
|
requestId,
|
|
249
194
|
"the daemon's launch recorded no pane to verify the child against"
|
|
250
195
|
);
|
|
251
|
-
if (freshness !== "fresh")
|
|
252
|
-
return unresolvedLaunch(
|
|
253
|
-
requestId,
|
|
254
|
-
state === "unresolved" ? `the daemon's inventory does not report the pane ${pane} its own record names for this child` : `the daemon cannot confirm the pane ${pane} it recorded for this child`
|
|
255
|
-
);
|
|
256
|
-
if (edge.exit_observation?.state === "exited")
|
|
257
|
-
return unresolvedLaunch(
|
|
258
|
-
requestId,
|
|
259
|
-
"the daemon observed the launched child's process exit"
|
|
260
|
-
);
|
|
261
196
|
const agentId = edge.bound?.registration_id;
|
|
262
197
|
if (agentId !== void 0 && isUuid(agentId)) {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
if (source === void 0 || source === "" || incarnation === void 0 || incarnation === "")
|
|
198
|
+
const subjectId = edge.bound?.subject_id;
|
|
199
|
+
if (subjectId === void 0 || !isUuid(subjectId))
|
|
266
200
|
return unresolvedLaunch(
|
|
267
201
|
requestId,
|
|
268
|
-
`the daemon bound the child ${agentId} without recording
|
|
202
|
+
`the daemon bound the child ${agentId} without recording its subject`
|
|
269
203
|
);
|
|
270
204
|
return {
|
|
271
205
|
kind: "bound",
|
|
272
|
-
|
|
273
|
-
pane_id: pane
|
|
274
|
-
source,
|
|
275
|
-
incarnation
|
|
206
|
+
subject_id: subjectId,
|
|
207
|
+
pane_id: pane
|
|
276
208
|
};
|
|
277
209
|
}
|
|
278
210
|
last = "no child has registered with this launch's token yet";
|
|
279
211
|
} else {
|
|
280
|
-
|
|
212
|
+
return unresolvedLaunch(requestId, answer.message);
|
|
281
213
|
}
|
|
282
|
-
|
|
283
|
-
return unresolvedLaunch(requestId, last);
|
|
284
|
-
await delay(options.pollMs, void 0, {
|
|
285
|
-
...options.signal !== void 0 ? { signal: options.signal } : {}
|
|
286
|
-
});
|
|
214
|
+
await delay(options.pollMs);
|
|
287
215
|
}
|
|
288
216
|
}
|
|
289
217
|
export {
|
|
218
|
+
launchFailure,
|
|
290
219
|
launchRadarWorker,
|
|
291
220
|
observeRadarChild,
|
|
292
221
|
readProcessGone,
|
|
293
|
-
relaunchRefusalForChild
|
|
222
|
+
relaunchRefusalForChild,
|
|
223
|
+
waitForChild
|
|
294
224
|
};
|