pi-radar 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +33 -2
- package/dist/extension/baseline.js +2626 -0
- package/dist/extension/generated-contracts.js +0 -0
- package/dist/extension/radar-client.js +1149 -0
- package/dist/extension/radar-execution.js +1523 -0
- package/dist/extension/radar-inspection.js +45 -0
- package/dist/extension/radar-launch.js +289 -0
- package/dist/extension/radar-location.js +115 -0
- package/dist/extension/radar-publication.js +1213 -0
- package/dist/extension/radar-runtime.js +42 -0
- package/dist/extension/radar-session-marker.js +82 -0
- package/dist/index.js +3100 -0
- package/extension/baseline.ts +37 -0
- package/extension/generated-contracts.ts +87 -0
- package/extension/radar-client.ts +1949 -0
- package/extension/radar-execution.ts +488 -0
- package/extension/radar-inspection.ts +85 -0
- package/extension/radar-launch.ts +591 -0
- package/extension/radar-location.ts +200 -0
- package/extension/radar-publication.ts +1745 -0
- package/extension/radar-runtime.ts +94 -0
- package/extension/radar-session-marker.ts +146 -0
- package/index.ts +7 -0
- package/package.json +92 -4
- package/schema/public/v1/daemon-ping.deserialize.schema.json +65 -0
- package/schema/public/v1/daemon-ping.serialize.schema.json +69 -0
- package/schema/public/v1/facts-snapshot.deserialize.schema.json +100 -0
- package/schema/public/v1/facts-snapshot.serialize.schema.json +78 -0
- package/schema/public/v1/launch-identity-marker.deserialize.schema.json +32 -0
- package/schema/public/v1/launch-identity-marker.serialize.schema.json +32 -0
- package/schema/public/v1/managed-launch-metadata.deserialize.schema.json +39 -0
- package/schema/public/v1/managed-launch-metadata.serialize.schema.json +39 -0
- package/schema/public/v1/spawn-exit-observation.deserialize.schema.json +49 -0
- package/schema/public/v1/spawn-exit-observation.serialize.schema.json +53 -0
|
@@ -0,0 +1,2626 @@
|
|
|
1
|
+
// extension/baseline.ts
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join as join3 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// extension/radar-client.ts
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { lstatSync } from "node:fs";
|
|
8
|
+
import { createConnection } from "node:net";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
var RADAR_PROTOCOL_VERSION = 2;
|
|
11
|
+
var RADAR_MINIMUM_REVISION = 3;
|
|
12
|
+
var RADAR_REGISTRY_CONTRACT = "registry.v1";
|
|
13
|
+
var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
|
|
14
|
+
var RADAR_SPAWN_CONTRACT = "spawn.v1";
|
|
15
|
+
var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
|
|
16
|
+
var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
|
|
17
|
+
var RADAR_EVENTS_CONTRACT = "events.v1";
|
|
18
|
+
var RADAR_REQUESTER = "pi-radar";
|
|
19
|
+
var RADAR_MAX_LINE_BYTES = 1024 * 1024;
|
|
20
|
+
var RADAR_CALL_TIMEOUT_MS = 2e3;
|
|
21
|
+
var DAEMON_ERROR_CODES = [
|
|
22
|
+
"bad_version",
|
|
23
|
+
"bad_request",
|
|
24
|
+
"bad_params",
|
|
25
|
+
"unknown_method",
|
|
26
|
+
"refused",
|
|
27
|
+
"not_found",
|
|
28
|
+
"busy",
|
|
29
|
+
"backend_unavailable",
|
|
30
|
+
"internal"
|
|
31
|
+
];
|
|
32
|
+
var DAEMON_CODE_ALIASES = {
|
|
33
|
+
outcome_unknown: "unknown"
|
|
34
|
+
};
|
|
35
|
+
var wallClock = { setTimeout, clearTimeout, setInterval, clearInterval };
|
|
36
|
+
var systemScheduler = {
|
|
37
|
+
setTimeout: (handler, ms) => wallClock.setTimeout(handler, ms),
|
|
38
|
+
clearTimeout: (timer) => wallClock.clearTimeout(timer),
|
|
39
|
+
setInterval: (handler, ms) => wallClock.setInterval(handler, ms),
|
|
40
|
+
clearInterval: (timer) => wallClock.clearInterval(timer),
|
|
41
|
+
now: () => Date.now()
|
|
42
|
+
};
|
|
43
|
+
function radarControlSocketPath(env, uid) {
|
|
44
|
+
const override = env.RADAR_CONTROL_SOCKET?.trim();
|
|
45
|
+
if (override) return { path: override, explicit: true };
|
|
46
|
+
const runtime = env.XDG_RUNTIME_DIR?.trim();
|
|
47
|
+
const directory = runtime ? join(runtime, "agent-radar") : `/tmp/agent-radar-${uid}`;
|
|
48
|
+
return { path: join(directory, "control.sock"), explicit: false };
|
|
49
|
+
}
|
|
50
|
+
function untrustedControlSocket(path, uid) {
|
|
51
|
+
const directory = dirname(path);
|
|
52
|
+
try {
|
|
53
|
+
const parent = lstatSync(directory);
|
|
54
|
+
if (parent.isSymbolicLink() || !parent.isDirectory())
|
|
55
|
+
return `${directory} is not a real directory`;
|
|
56
|
+
if (parent.uid !== uid)
|
|
57
|
+
return `${directory} is not owned by uid ${uid}`;
|
|
58
|
+
if ((parent.mode & 511) !== 448)
|
|
59
|
+
return `${directory} is not mode 0700`;
|
|
60
|
+
const socket = lstatSync(path);
|
|
61
|
+
if (socket.isSymbolicLink()) return `${path} is a symlink`;
|
|
62
|
+
if (!socket.isSocket()) return `${path} is not a socket`;
|
|
63
|
+
if (socket.uid !== uid) return `${path} is not owned by uid ${uid}`;
|
|
64
|
+
return void 0;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const code = error?.code ?? "unavailable";
|
|
67
|
+
return `${path} is unavailable (${code})`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function protocol(message) {
|
|
71
|
+
return { ok: false, code: "protocol", message };
|
|
72
|
+
}
|
|
73
|
+
function bounded(text, bytes) {
|
|
74
|
+
if (Buffer.byteLength(text, "utf8") <= bytes) return text;
|
|
75
|
+
let end = text.length;
|
|
76
|
+
while (end > 0 && Buffer.byteLength(text.slice(0, end), "utf8") > bytes)
|
|
77
|
+
end -= 1;
|
|
78
|
+
return text.slice(0, end);
|
|
79
|
+
}
|
|
80
|
+
function decodeReply(line, requestId) {
|
|
81
|
+
if (Buffer.byteLength(line, "utf8") > RADAR_MAX_LINE_BYTES)
|
|
82
|
+
return protocol("reply exceeds the control-plane line bound");
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = JSON.parse(line);
|
|
86
|
+
} catch {
|
|
87
|
+
return protocol("reply is not JSON");
|
|
88
|
+
}
|
|
89
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
90
|
+
return protocol("reply is not an object");
|
|
91
|
+
const answer = parsed;
|
|
92
|
+
if (answer.id !== requestId)
|
|
93
|
+
return protocol("reply does not match the request id");
|
|
94
|
+
if (answer.error !== void 0) {
|
|
95
|
+
const error = answer.error;
|
|
96
|
+
const code = error?.code;
|
|
97
|
+
const message = error?.message;
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
code: typeof code === "string" ? DAEMON_CODE_ALIASES[code] ?? (DAEMON_ERROR_CODES.includes(code) ? code : "internal") : "internal",
|
|
101
|
+
message: `${typeof code === "string" ? code : "error"}: ${typeof message === "string" ? message : "no message"}`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (!("result" in answer)) return protocol("reply carries no result");
|
|
105
|
+
return { ok: true, value: answer.result };
|
|
106
|
+
}
|
|
107
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
108
|
+
function recordAt(value, field) {
|
|
109
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
110
|
+
return value[field];
|
|
111
|
+
}
|
|
112
|
+
function isUuid(value) {
|
|
113
|
+
return typeof value === "string" && UUID.test(value);
|
|
114
|
+
}
|
|
115
|
+
function stringAt(value, field) {
|
|
116
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
117
|
+
const found = value[field];
|
|
118
|
+
return typeof found === "string" ? found : void 0;
|
|
119
|
+
}
|
|
120
|
+
function numberAt(value, field) {
|
|
121
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
122
|
+
const found = value[field];
|
|
123
|
+
return typeof found === "number" && Number.isFinite(found) ? found : void 0;
|
|
124
|
+
}
|
|
125
|
+
function writerBinding(value) {
|
|
126
|
+
const handle = stringAt(value, "handle");
|
|
127
|
+
const source = stringAt(value, "source");
|
|
128
|
+
const incarnation = stringAt(value, "incarnation");
|
|
129
|
+
const generation = numberAt(value, "generation");
|
|
130
|
+
const sequence = numberAt(value, "sequence");
|
|
131
|
+
if (!isUuid(handle) || source === void 0 || incarnation === void 0 || generation === void 0 || sequence === void 0)
|
|
132
|
+
return void 0;
|
|
133
|
+
const reportingOwner = stringAt(value, "reporting_owner");
|
|
134
|
+
const retiredAt = stringAt(value, "retired_at");
|
|
135
|
+
return {
|
|
136
|
+
handle,
|
|
137
|
+
source,
|
|
138
|
+
incarnation,
|
|
139
|
+
...reportingOwner !== void 0 ? { reporting_owner: reportingOwner } : {},
|
|
140
|
+
generation,
|
|
141
|
+
sequence,
|
|
142
|
+
...retiredAt !== void 0 ? { retired_at: retiredAt } : {}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function observedAgent(value) {
|
|
146
|
+
const location = recordAt(value, "location");
|
|
147
|
+
const workspaceId = stringAt(location, "workspace_id");
|
|
148
|
+
const tabId = stringAt(location, "tab_id");
|
|
149
|
+
const paneId = stringAt(location, "pane_id");
|
|
150
|
+
if (!workspaceId || !tabId || !paneId)
|
|
151
|
+
return protocol("observe reply carries a malformed agent location");
|
|
152
|
+
const observed = recordAt(value, "session");
|
|
153
|
+
let session;
|
|
154
|
+
if (observed !== void 0 && observed !== null) {
|
|
155
|
+
const kind = stringAt(observed, "kind");
|
|
156
|
+
const identity = stringAt(observed, "value");
|
|
157
|
+
if (kind === "uuid" && isUuid(identity)) session = { kind: "uuid", value: identity };
|
|
158
|
+
else if (kind === "reported") {
|
|
159
|
+
const reported = recordAt(observed, "value");
|
|
160
|
+
const reference = stringAt(reported, "value");
|
|
161
|
+
if (reference === void 0 || reference === "")
|
|
162
|
+
return protocol("observe reply carries a malformed session reference");
|
|
163
|
+
const source = stringAt(reported, "source");
|
|
164
|
+
session = {
|
|
165
|
+
kind: "reported",
|
|
166
|
+
value: reference,
|
|
167
|
+
...source !== void 0 ? { source } : {}
|
|
168
|
+
};
|
|
169
|
+
} else return protocol("observe reply carries an unrecognized session identity");
|
|
170
|
+
}
|
|
171
|
+
const facts = recordAt(value, "facts");
|
|
172
|
+
const name = stringAt(value, "name");
|
|
173
|
+
const status = runtimeStatus(value);
|
|
174
|
+
const managedLabel = stringAt(facts, "label");
|
|
175
|
+
const managedRun = stringAt(facts, "run");
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
value: {
|
|
179
|
+
workspace_id: workspaceId,
|
|
180
|
+
tab_id: tabId,
|
|
181
|
+
pane_id: paneId,
|
|
182
|
+
...name !== void 0 ? { name } : {},
|
|
183
|
+
...status !== void 0 ? { status } : {},
|
|
184
|
+
...managedLabel !== void 0 ? { managed_label: managedLabel } : {},
|
|
185
|
+
...managedRun !== void 0 ? { managed_run: managedRun } : {},
|
|
186
|
+
...session === void 0 ? {} : { session }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function runtimeStatus(value) {
|
|
191
|
+
const status = recordAt(value, "status");
|
|
192
|
+
if (typeof status === "string") return status;
|
|
193
|
+
return stringAt(status, "other");
|
|
194
|
+
}
|
|
195
|
+
function observedPane(value) {
|
|
196
|
+
const location = recordAt(value, "location");
|
|
197
|
+
const workspaceId = stringAt(location, "workspace_id");
|
|
198
|
+
const tabId = stringAt(location, "tab_id");
|
|
199
|
+
const paneId = stringAt(location, "pane_id");
|
|
200
|
+
if (!workspaceId || !tabId || !paneId)
|
|
201
|
+
return protocol("observe reply carries a malformed pane location");
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
value: { workspace_id: workspaceId, tab_id: tabId, pane_id: paneId }
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
var DISPATCH_REFUSED_CODES = [
|
|
208
|
+
"absent",
|
|
209
|
+
"unsupported",
|
|
210
|
+
"bad_version",
|
|
211
|
+
"bad_request",
|
|
212
|
+
"bad_params",
|
|
213
|
+
"unknown_method",
|
|
214
|
+
"refused",
|
|
215
|
+
"not_found",
|
|
216
|
+
"busy",
|
|
217
|
+
"backend_unavailable"
|
|
218
|
+
];
|
|
219
|
+
function unresolvedOperation(requestId, detail) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
code: "unknown",
|
|
223
|
+
message: `focus ${requestId} is unresolved (${detail})`
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
var RADAR_SPAWN_TOKEN_ENV = "PI_RADAR_SPAWN_TOKEN";
|
|
227
|
+
var RADAR_MANAGED_IDENTITY_ENV = /* @__PURE__ */ new Set([
|
|
228
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
229
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
230
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION"
|
|
231
|
+
]);
|
|
232
|
+
function unresolvedSpawn(requestId, detail) {
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
code: "unknown",
|
|
236
|
+
message: `spawn ${requestId} is unresolved (${detail})`
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
var EFFECT_OUTCOMES = [
|
|
240
|
+
"completed",
|
|
241
|
+
"refused",
|
|
242
|
+
"unknown"
|
|
243
|
+
];
|
|
244
|
+
function effectAt(value, field) {
|
|
245
|
+
const found = stringAt(value, field);
|
|
246
|
+
return found !== void 0 && EFFECT_OUTCOMES.includes(found) ? found : void 0;
|
|
247
|
+
}
|
|
248
|
+
function locationAt(value) {
|
|
249
|
+
const location = recordAt(value, "location");
|
|
250
|
+
const backend = stringAt(location, "backend");
|
|
251
|
+
if (backend === void 0 || backend === "") return void 0;
|
|
252
|
+
const instance = stringAt(location, "instance");
|
|
253
|
+
const workspace = stringAt(location, "workspace");
|
|
254
|
+
const tab = stringAt(location, "tab");
|
|
255
|
+
const pane = stringAt(location, "pane");
|
|
256
|
+
return {
|
|
257
|
+
backend,
|
|
258
|
+
...instance !== void 0 ? { instance } : {},
|
|
259
|
+
...workspace !== void 0 ? { workspace } : {},
|
|
260
|
+
...tab !== void 0 ? { tab } : {},
|
|
261
|
+
...pane !== void 0 ? { pane } : {}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function spawnEdge(value) {
|
|
265
|
+
const requestId = stringAt(value, "request_id");
|
|
266
|
+
const parent = stringAt(value, "parent");
|
|
267
|
+
const created = effectAt(value, "created");
|
|
268
|
+
const launched = effectAt(value, "launched");
|
|
269
|
+
if (!isUuid(requestId) || !isUuid(parent) || created === void 0 || launched === void 0)
|
|
270
|
+
return protocol("spawn reply carries no durable edge");
|
|
271
|
+
const location = locationAt(value);
|
|
272
|
+
const bound = recordAt(value, "bound");
|
|
273
|
+
const source = stringAt(bound, "source");
|
|
274
|
+
const incarnation = stringAt(bound, "incarnation");
|
|
275
|
+
const agentId = stringAt(bound, "agent_id");
|
|
276
|
+
const exit = exitAt(recordAt(value, "exit_observation"));
|
|
277
|
+
return {
|
|
278
|
+
ok: true,
|
|
279
|
+
value: {
|
|
280
|
+
request_id: requestId,
|
|
281
|
+
parent,
|
|
282
|
+
created,
|
|
283
|
+
launched,
|
|
284
|
+
...location !== void 0 ? { location } : {},
|
|
285
|
+
...source !== void 0 && incarnation !== void 0 ? {
|
|
286
|
+
bound: {
|
|
287
|
+
source,
|
|
288
|
+
incarnation,
|
|
289
|
+
...isUuid(agentId) ? { agent_id: agentId } : {}
|
|
290
|
+
}
|
|
291
|
+
} : {},
|
|
292
|
+
...exit !== void 0 ? { exit_observation: exit } : {}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
var PROCESS_VERDICTS = [
|
|
297
|
+
"verified",
|
|
298
|
+
"absent",
|
|
299
|
+
"mismatched",
|
|
300
|
+
"unavailable"
|
|
301
|
+
];
|
|
302
|
+
function agentProcessAt(agent) {
|
|
303
|
+
const process2 = recordAt(agent, "process");
|
|
304
|
+
const claimed = recordAt(process2, "claimed");
|
|
305
|
+
const verification = stringAt(process2, "verification");
|
|
306
|
+
if (typeof claimed !== "boolean" || verification === void 0 || !PROCESS_VERDICTS.includes(verification))
|
|
307
|
+
return protocol("agent.get reply carries no process verdict");
|
|
308
|
+
const registration = recordAt(agent, "registration");
|
|
309
|
+
const location = locationAt(registration);
|
|
310
|
+
const session = stringAt(registration, "session");
|
|
311
|
+
const context = recordAt(recordAt(agent, "context"), "context");
|
|
312
|
+
const contextSession = stringAt(context, "freshness") === "fresh" ? stringAt(context, "session") : void 0;
|
|
313
|
+
const execution = recordAt(recordAt(agent, "execution"), "snapshot");
|
|
314
|
+
const activity = stringAt(execution, "freshness") === "fresh" ? stringAt(recordAt(execution, "snapshot"), "activity") : void 0;
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
value: {
|
|
318
|
+
claimed,
|
|
319
|
+
verification,
|
|
320
|
+
...location !== void 0 ? { location } : {},
|
|
321
|
+
...session !== void 0 ? { session } : {},
|
|
322
|
+
...contextSession !== void 0 ? { contextSession } : {},
|
|
323
|
+
...activity !== void 0 ? { activity } : {}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function exitAt(value) {
|
|
328
|
+
if (value === void 0) return void 0;
|
|
329
|
+
if (stringAt(value, "state") === "unknown") return { state: "unknown" };
|
|
330
|
+
if (stringAt(value, "state") !== "exited") return void 0;
|
|
331
|
+
const code = recordAt(value, "code");
|
|
332
|
+
const signal = recordAt(value, "signal");
|
|
333
|
+
const observedAt = stringAt(value, "observed_at");
|
|
334
|
+
const isInt32OrNull = (found) => found === null || typeof found === "number" && Number.isInteger(found) && found >= -2147483648 && found <= 2147483647;
|
|
335
|
+
if (!isInt32OrNull(code) || !isInt32OrNull(signal) || observedAt === void 0)
|
|
336
|
+
return void 0;
|
|
337
|
+
return {
|
|
338
|
+
state: "exited",
|
|
339
|
+
code,
|
|
340
|
+
signal,
|
|
341
|
+
observed_at: observedAt
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function spawnTopology(value) {
|
|
345
|
+
const edge = spawnEdge(value);
|
|
346
|
+
if (edge.ok === false) return edge;
|
|
347
|
+
const state = stringAt(value, "state");
|
|
348
|
+
const freshness = stringAt(value, "freshness");
|
|
349
|
+
return {
|
|
350
|
+
ok: true,
|
|
351
|
+
value: {
|
|
352
|
+
edge: edge.value,
|
|
353
|
+
...state === "unbound" || state === "bound" || state === "unresolved" ? { state } : {},
|
|
354
|
+
...freshness === "fresh" || freshness === "stale" ? { freshness } : {}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function spawnRequestShape(request) {
|
|
359
|
+
if (!isUuid(request.request_id))
|
|
360
|
+
return "spawn requires a canonical request UUID";
|
|
361
|
+
if (!isUuid(request.parent))
|
|
362
|
+
return "spawn requires the parent's canonical agent UUID";
|
|
363
|
+
if (!isUuid(request.parent_session_id))
|
|
364
|
+
return "spawn requires a canonical parent session UUID";
|
|
365
|
+
if (!request.executable.startsWith("/") || new RegExp("\\p{Cc}", "u").test(request.executable))
|
|
366
|
+
return "spawn requires an absolute, printable executable path";
|
|
367
|
+
if (request.argv.some((argument) => new RegExp("\\p{Cc}", "u").test(argument)))
|
|
368
|
+
return "no supported backend carries a control character or a second line in an argument";
|
|
369
|
+
if (request.cwd !== void 0 && !request.cwd.startsWith("/"))
|
|
370
|
+
return "spawn cwd must be an absolute path";
|
|
371
|
+
const env = Object.entries(request.env ?? {});
|
|
372
|
+
if (env.length > 256) return "spawn env names more than 256 entries";
|
|
373
|
+
for (const [name, value] of env) {
|
|
374
|
+
if (name === RADAR_SPAWN_TOKEN_ENV)
|
|
375
|
+
return `spawn env cannot set the reserved ${RADAR_SPAWN_TOKEN_ENV}`;
|
|
376
|
+
if (RADAR_MANAGED_IDENTITY_ENV.has(name))
|
|
377
|
+
return `spawn env cannot set Radar-issued identity variable ${name}`;
|
|
378
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || new RegExp("\\p{Cc}", "u").test(value))
|
|
379
|
+
return `spawn env contains an invalid name or value: ${name}`;
|
|
380
|
+
}
|
|
381
|
+
if (request.managed !== void 0) {
|
|
382
|
+
if (typeof request.managed !== "object" || request.managed === null)
|
|
383
|
+
return "managed launch metadata must be an object";
|
|
384
|
+
for (const [name, value] of Object.entries(request.managed)) {
|
|
385
|
+
if (!["owner", "run", "label", "role", "parent_subject_id"].includes(name))
|
|
386
|
+
return `unknown managed launch metadata field: ${name}`;
|
|
387
|
+
if (value != null && (typeof value !== "string" || value.trim() === "" || new TextEncoder().encode(value).length > 256 || new RegExp("\\p{Cc}", "u").test(value)))
|
|
388
|
+
return `managed launch ${name} must be non-empty, control-free, and at most 256 UTF-8 bytes`;
|
|
389
|
+
}
|
|
390
|
+
if (request.managed.parent_subject_id != null && !isUuid(request.managed.parent_subject_id))
|
|
391
|
+
return "managed parent_subject_id must be a canonical UUID";
|
|
392
|
+
}
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
395
|
+
function focusOutcome(record, requestId) {
|
|
396
|
+
if (typeof record !== "object" || record === null)
|
|
397
|
+
return unresolvedOperation(requestId, "the daemon returned no operation record");
|
|
398
|
+
const outcome = stringAt(record, "outcome");
|
|
399
|
+
if (outcome === "completed") return { ok: true, value: { request_id: requestId } };
|
|
400
|
+
if (outcome === "refused")
|
|
401
|
+
return {
|
|
402
|
+
ok: false,
|
|
403
|
+
code: "refused",
|
|
404
|
+
message: stringAt(record, "message") ?? `the daemon refused focus ${requestId}`
|
|
405
|
+
};
|
|
406
|
+
return unresolvedOperation(
|
|
407
|
+
requestId,
|
|
408
|
+
`the record is ${stringAt(record, "state") ?? "unreadable"} with ${outcome === void 0 ? "no outcome" : `outcome ${outcome}`}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
function eventsReplyAt(after, value) {
|
|
412
|
+
const expired = recordAt(value, "cursor_expired");
|
|
413
|
+
if (expired !== void 0) {
|
|
414
|
+
const epoch2 = stringAt(expired, "epoch");
|
|
415
|
+
const sequence2 = numberAt(recordAt(expired, "cursor"), "sequence");
|
|
416
|
+
return epoch2 !== void 0 && sequence2 !== void 0 ? { kind: "expired", epoch: epoch2, sequence: sequence2 } : void 0;
|
|
417
|
+
}
|
|
418
|
+
const epoch = stringAt(value, "epoch");
|
|
419
|
+
const events = recordAt(value, "events");
|
|
420
|
+
const more = recordAt(value, "more");
|
|
421
|
+
if (epoch === void 0 || !Array.isArray(events) || typeof more !== "boolean")
|
|
422
|
+
return void 0;
|
|
423
|
+
let sequence = after;
|
|
424
|
+
for (const event of events) {
|
|
425
|
+
const seq = numberAt(event, "seq");
|
|
426
|
+
if (seq === void 0 || seq <= sequence) return void 0;
|
|
427
|
+
sequence = seq;
|
|
428
|
+
}
|
|
429
|
+
return { kind: "page", epoch, sequence, more };
|
|
430
|
+
}
|
|
431
|
+
var RADAR_EVENT_WAIT_MS = 1e3;
|
|
432
|
+
function createRadarClient(options = {}) {
|
|
433
|
+
const env = options.env ?? process.env;
|
|
434
|
+
const uid = options.uid ?? process.getuid?.() ?? 0;
|
|
435
|
+
const scheduler = options.scheduler ?? systemScheduler;
|
|
436
|
+
const timeoutMs = options.timeoutMs ?? RADAR_CALL_TIMEOUT_MS;
|
|
437
|
+
const requester = options.requester ?? RADAR_REQUESTER;
|
|
438
|
+
const dial = options.dial ?? ((path) => createConnection(path));
|
|
439
|
+
const currentPath = () => radarControlSocketPath(env, uid).path;
|
|
440
|
+
async function call(method, params, id = randomUUID()) {
|
|
441
|
+
const path = currentPath();
|
|
442
|
+
const refused = untrustedControlSocket(path, uid);
|
|
443
|
+
if (refused !== void 0)
|
|
444
|
+
return { ok: false, code: "absent", message: refused };
|
|
445
|
+
const line = `${JSON.stringify({
|
|
446
|
+
version: RADAR_PROTOCOL_VERSION,
|
|
447
|
+
id,
|
|
448
|
+
method,
|
|
449
|
+
params
|
|
450
|
+
})}
|
|
451
|
+
`;
|
|
452
|
+
if (Buffer.byteLength(line, "utf8") - 1 > RADAR_MAX_LINE_BYTES)
|
|
453
|
+
return {
|
|
454
|
+
ok: false,
|
|
455
|
+
code: "bad_params",
|
|
456
|
+
message: "request exceeds the control-plane line bound"
|
|
457
|
+
};
|
|
458
|
+
return new Promise((resolve2) => {
|
|
459
|
+
let settled = false;
|
|
460
|
+
let socket;
|
|
461
|
+
let timer;
|
|
462
|
+
let received = 0;
|
|
463
|
+
const chunks = [];
|
|
464
|
+
const finish = (result) => {
|
|
465
|
+
if (settled) return;
|
|
466
|
+
settled = true;
|
|
467
|
+
if (timer !== void 0) scheduler.clearTimeout(timer);
|
|
468
|
+
try {
|
|
469
|
+
socket?.destroy();
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
resolve2(result);
|
|
473
|
+
};
|
|
474
|
+
try {
|
|
475
|
+
socket = dial(path);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
finish({
|
|
478
|
+
ok: false,
|
|
479
|
+
code: "transport",
|
|
480
|
+
message: String(error?.message ?? error)
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
timer = scheduler.setTimeout(() => {
|
|
485
|
+
finish({
|
|
486
|
+
ok: false,
|
|
487
|
+
code: "timeout",
|
|
488
|
+
message: `${method} got no reply within ${timeoutMs}ms`
|
|
489
|
+
});
|
|
490
|
+
}, timeoutMs);
|
|
491
|
+
timer.unref?.();
|
|
492
|
+
socket.on("error", (error) => {
|
|
493
|
+
finish({
|
|
494
|
+
ok: false,
|
|
495
|
+
code: "transport",
|
|
496
|
+
message: String(error?.message ?? error)
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
socket.on("close", () => {
|
|
500
|
+
finish({
|
|
501
|
+
ok: false,
|
|
502
|
+
code: "transport",
|
|
503
|
+
message: `${method} connection closed without a reply`
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
socket.on("connect", () => {
|
|
507
|
+
try {
|
|
508
|
+
socket?.write(line);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
finish({
|
|
511
|
+
ok: false,
|
|
512
|
+
code: "transport",
|
|
513
|
+
message: String(error?.message ?? error)
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
socket.on("data", (value) => {
|
|
518
|
+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(String(value ?? ""), "utf8");
|
|
519
|
+
received += chunk.length;
|
|
520
|
+
if (received > RADAR_MAX_LINE_BYTES + 1) {
|
|
521
|
+
finish(protocol("reply exceeds the control-plane line bound"));
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
chunks.push(chunk);
|
|
525
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
526
|
+
const end = text.indexOf("\n");
|
|
527
|
+
if (end < 0) return;
|
|
528
|
+
finish(decodeReply(text.slice(0, end), id));
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
const socketPath = currentPath;
|
|
533
|
+
const probe = async () => {
|
|
534
|
+
const answer = await call("daemon.ping", {});
|
|
535
|
+
if (answer.ok === false) return answer;
|
|
536
|
+
const protocolVersion = numberAt(answer.value, "protocol");
|
|
537
|
+
if (protocolVersion !== RADAR_PROTOCOL_VERSION)
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
code: "unsupported",
|
|
541
|
+
message: `the daemon at ${currentPath()} speaks protocol ${protocolVersion ?? "unknown"}, not ${RADAR_PROTOCOL_VERSION}`
|
|
542
|
+
};
|
|
543
|
+
const revision = numberAt(answer.value, "revision");
|
|
544
|
+
if (revision === void 0 || !Number.isSafeInteger(revision) || revision < RADAR_MINIMUM_REVISION)
|
|
545
|
+
return {
|
|
546
|
+
ok: false,
|
|
547
|
+
code: "unsupported",
|
|
548
|
+
message: `the daemon at ${currentPath()} does not support wire revision ${RADAR_MINIMUM_REVISION}; run systemctl --user restart radar-daemon`
|
|
549
|
+
};
|
|
550
|
+
const backend = stringAt(answer.value, "backend");
|
|
551
|
+
const instance = recordAt(answer.value, "instance");
|
|
552
|
+
const rawContracts = recordAt(answer.value, "contracts");
|
|
553
|
+
const rawTasks = recordAt(answer.value, "tasks");
|
|
554
|
+
const taskError = recordAt(rawTasks, "error");
|
|
555
|
+
const taskAvailable = recordAt(rawTasks, "available");
|
|
556
|
+
if (backend === void 0 || instance !== null && typeof instance !== "string" || !Array.isArray(rawContracts) || rawContracts.some((name) => typeof name !== "string") || typeof taskAvailable !== "boolean" || taskError !== null && typeof taskError !== "string")
|
|
557
|
+
return protocol("daemon.ping reply does not match the public response schema");
|
|
558
|
+
return {
|
|
559
|
+
ok: true,
|
|
560
|
+
value: {
|
|
561
|
+
protocol: protocolVersion,
|
|
562
|
+
revision,
|
|
563
|
+
backend,
|
|
564
|
+
instance,
|
|
565
|
+
contracts: rawContracts,
|
|
566
|
+
tasks: { available: taskAvailable, error: taskError }
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
const ping = async () => {
|
|
571
|
+
const answer = await probe();
|
|
572
|
+
if (answer.ok === false) return answer;
|
|
573
|
+
if (!answer.value.contracts.includes(RADAR_REGISTRY_CONTRACT))
|
|
574
|
+
return {
|
|
575
|
+
ok: false,
|
|
576
|
+
code: "unsupported",
|
|
577
|
+
message: `the daemon at ${currentPath()} does not provide \`${RADAR_REGISTRY_CONTRACT}\``
|
|
578
|
+
};
|
|
579
|
+
return answer;
|
|
580
|
+
};
|
|
581
|
+
let handshake;
|
|
582
|
+
const negotiated = async () => {
|
|
583
|
+
handshake ??= probe();
|
|
584
|
+
const attempt = handshake;
|
|
585
|
+
try {
|
|
586
|
+
const result = await attempt;
|
|
587
|
+
if (result.ok === false && handshake === attempt) handshake = void 0;
|
|
588
|
+
return result;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
if (handshake === attempt) handshake = void 0;
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
const withContract = async (contract, operation) => {
|
|
595
|
+
const ready = await negotiated();
|
|
596
|
+
if (ready.ok === false) return ready;
|
|
597
|
+
return ready.value.contracts.includes(contract) ? operation() : {
|
|
598
|
+
ok: false,
|
|
599
|
+
code: "unsupported",
|
|
600
|
+
message: `the daemon at ${currentPath()} does not provide \`${contract}\``
|
|
601
|
+
};
|
|
602
|
+
};
|
|
603
|
+
const withContracts = async (needed, operation) => {
|
|
604
|
+
const ready = await negotiated();
|
|
605
|
+
if (ready.ok === false) return ready;
|
|
606
|
+
const missing = needed.find((contract) => !ready.value.contracts.includes(contract));
|
|
607
|
+
if (missing === void 0) return operation();
|
|
608
|
+
return {
|
|
609
|
+
ok: false,
|
|
610
|
+
code: "unsupported",
|
|
611
|
+
message: `the daemon at ${currentPath()} does not provide \`${missing}\``
|
|
612
|
+
};
|
|
613
|
+
};
|
|
614
|
+
const withRegistry = (operation) => withContract(RADAR_REGISTRY_CONTRACT, operation);
|
|
615
|
+
const withRegistryChannel = (channel, operation) => withContracts(
|
|
616
|
+
[
|
|
617
|
+
RADAR_REGISTRY_CONTRACT,
|
|
618
|
+
...channel === "facts" || channel === "assignment" ? [RADAR_REGISTRY_FACTS_CONTRACT] : []
|
|
619
|
+
],
|
|
620
|
+
operation
|
|
621
|
+
);
|
|
622
|
+
const withDaemon = async (operation) => {
|
|
623
|
+
const ready = await negotiated();
|
|
624
|
+
return ready.ok === false ? ready : operation();
|
|
625
|
+
};
|
|
626
|
+
return {
|
|
627
|
+
socketPath,
|
|
628
|
+
ping,
|
|
629
|
+
async register(registration) {
|
|
630
|
+
return withRegistry(async () => {
|
|
631
|
+
const answer = await call("agent.register", {
|
|
632
|
+
source: registration.source,
|
|
633
|
+
...registration.spawn_token !== void 0 ? { spawn_token: registration.spawn_token } : {},
|
|
634
|
+
incarnation: registration.incarnation,
|
|
635
|
+
...registration.session !== void 0 ? { session: registration.session } : {},
|
|
636
|
+
...registration.owner !== void 0 ? { owner: registration.owner } : {},
|
|
637
|
+
...registration.run !== void 0 ? { run: registration.run } : {},
|
|
638
|
+
...registration.label !== void 0 ? { label: registration.label } : {},
|
|
639
|
+
...registration.location !== void 0 ? { location: registration.location } : {},
|
|
640
|
+
...registration.process !== void 0 ? { process: registration.process } : {}
|
|
641
|
+
});
|
|
642
|
+
if (answer.ok === false) return answer;
|
|
643
|
+
const record = answer.value.registration;
|
|
644
|
+
const agentId = stringAt(record, "agent_id");
|
|
645
|
+
if (!isUuid(agentId)) return protocol("registration reply carries no agent_id");
|
|
646
|
+
return { ok: true, value: { agent_id: agentId } };
|
|
647
|
+
});
|
|
648
|
+
},
|
|
649
|
+
async acquire(request) {
|
|
650
|
+
return withRegistryChannel(request.channel, async () => {
|
|
651
|
+
const answer = await call("agent.acquire", {
|
|
652
|
+
agent_id: request.agent_id,
|
|
653
|
+
channel: request.channel,
|
|
654
|
+
publisher: request.publisher,
|
|
655
|
+
...request.replace !== void 0 ? { replace: request.replace } : {}
|
|
656
|
+
});
|
|
657
|
+
if (answer.ok === false) return answer;
|
|
658
|
+
const writer = writerBinding(
|
|
659
|
+
answer.value.writer
|
|
660
|
+
);
|
|
661
|
+
if (writer === void 0)
|
|
662
|
+
return protocol("acquire reply carries no writer binding");
|
|
663
|
+
return { ok: true, value: writer };
|
|
664
|
+
});
|
|
665
|
+
},
|
|
666
|
+
async publish(request) {
|
|
667
|
+
let snapshot = request.snapshot;
|
|
668
|
+
if (request.channel === "facts") {
|
|
669
|
+
try {
|
|
670
|
+
snapshot = boundedRadarFacts(request.snapshot);
|
|
671
|
+
} catch (error) {
|
|
672
|
+
return {
|
|
673
|
+
ok: false,
|
|
674
|
+
code: "bad_params",
|
|
675
|
+
message: error instanceof Error ? error.message : "invalid facts snapshot"
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return withRegistryChannel(request.channel, async () => {
|
|
680
|
+
const answer = await call("agent.publish", {
|
|
681
|
+
agent_id: request.agent_id,
|
|
682
|
+
channel: request.channel,
|
|
683
|
+
writer_handle: request.writer_handle,
|
|
684
|
+
sequence: request.sequence,
|
|
685
|
+
...request.lease_ms !== void 0 ? { lease_ms: request.lease_ms } : {},
|
|
686
|
+
...request.observed_at !== void 0 ? { observed_at: request.observed_at } : {},
|
|
687
|
+
snapshot
|
|
688
|
+
});
|
|
689
|
+
if (answer.ok === false) return answer;
|
|
690
|
+
const channel = answer.value.channel;
|
|
691
|
+
const writer = channel?.writer;
|
|
692
|
+
const sequence = numberAt(writer, "sequence");
|
|
693
|
+
if (sequence === void 0) return protocol("publish reply carries no accepted sequence");
|
|
694
|
+
return { ok: true, value: { sequence } };
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
async context(request) {
|
|
698
|
+
return withRegistry(async () => {
|
|
699
|
+
const answer = await call("agent.context", {
|
|
700
|
+
agent_id: request.agent_id,
|
|
701
|
+
publisher: request.publisher,
|
|
702
|
+
...request.writer_handle !== void 0 ? { writer_handle: request.writer_handle } : {},
|
|
703
|
+
...request.replace !== void 0 ? { replace: request.replace } : {},
|
|
704
|
+
sequence: request.sequence,
|
|
705
|
+
...request.lease_ms !== void 0 ? { lease_ms: request.lease_ms } : {},
|
|
706
|
+
...request.observed_at !== void 0 ? { observed_at: request.observed_at } : {},
|
|
707
|
+
context: request.context
|
|
708
|
+
});
|
|
709
|
+
if (answer.ok === false) return answer;
|
|
710
|
+
const writer = writerBinding(
|
|
711
|
+
answer.value.writer
|
|
712
|
+
);
|
|
713
|
+
if (writer === void 0)
|
|
714
|
+
return protocol("context reply carries no writer binding");
|
|
715
|
+
const warning = stringAt(answer.value, "warning");
|
|
716
|
+
return {
|
|
717
|
+
ok: true,
|
|
718
|
+
value: { writer, ...warning !== void 0 ? { warning } : {} }
|
|
719
|
+
};
|
|
720
|
+
});
|
|
721
|
+
},
|
|
722
|
+
async retire(request) {
|
|
723
|
+
return withRegistryChannel(request.channel, async () => {
|
|
724
|
+
const answer = await call("agent.retire", request);
|
|
725
|
+
return answer.ok === true ? { ok: true, value: void 0 } : answer;
|
|
726
|
+
});
|
|
727
|
+
},
|
|
728
|
+
async spawn(request) {
|
|
729
|
+
const shape = spawnRequestShape(request);
|
|
730
|
+
if (shape !== void 0)
|
|
731
|
+
return { ok: false, code: "bad_params", message: shape };
|
|
732
|
+
const carriesContext = request.cwd !== void 0 || request.env !== void 0 && Object.keys(request.env).length > 0;
|
|
733
|
+
return withContracts(
|
|
734
|
+
carriesContext ? [RADAR_SPAWN_CONTRACT, RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT] : [RADAR_SPAWN_CONTRACT],
|
|
735
|
+
async () => {
|
|
736
|
+
const answer = await call(
|
|
737
|
+
"spawn.create",
|
|
738
|
+
{
|
|
739
|
+
request: {
|
|
740
|
+
parent: request.parent,
|
|
741
|
+
parent_session_id: request.parent_session_id,
|
|
742
|
+
executable: request.executable,
|
|
743
|
+
argv: request.argv,
|
|
744
|
+
...request.cwd !== void 0 ? { cwd: request.cwd } : {},
|
|
745
|
+
...carriesContext && request.env !== void 0 ? { env: request.env } : {},
|
|
746
|
+
...request.managed !== void 0 ? { managed: request.managed } : {}
|
|
747
|
+
},
|
|
748
|
+
requester
|
|
749
|
+
},
|
|
750
|
+
request.request_id
|
|
751
|
+
);
|
|
752
|
+
if (answer.ok === true) return spawnEdge(recordAt(answer.value, "spawn"));
|
|
753
|
+
if (DISPATCH_REFUSED_CODES.includes(answer.code)) return answer;
|
|
754
|
+
const readback = await call("spawn.get", {
|
|
755
|
+
request_id: request.request_id
|
|
756
|
+
});
|
|
757
|
+
if (readback.ok === false)
|
|
758
|
+
return unresolvedSpawn(
|
|
759
|
+
request.request_id,
|
|
760
|
+
`${answer.message}; its edge could not be read back (${readback.code}: ${readback.message})`
|
|
761
|
+
);
|
|
762
|
+
const recovered = spawnTopology(recordAt(readback.value, "spawn"));
|
|
763
|
+
if (recovered.ok === false) return recovered;
|
|
764
|
+
const recorded = recovered.value.edge;
|
|
765
|
+
if (recorded.created === "completed" && recorded.launched === "completed")
|
|
766
|
+
return { ok: true, value: recorded };
|
|
767
|
+
return unresolvedSpawn(
|
|
768
|
+
request.request_id,
|
|
769
|
+
`${answer.message}; the recorded edge is created ${recorded.created} and launched ${recorded.launched}`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
},
|
|
774
|
+
async spawnGet(requestId) {
|
|
775
|
+
if (!isUuid(requestId))
|
|
776
|
+
return {
|
|
777
|
+
ok: false,
|
|
778
|
+
code: "bad_params",
|
|
779
|
+
message: "spawn.get requires a canonical request UUID"
|
|
780
|
+
};
|
|
781
|
+
return withRegistry(async () => {
|
|
782
|
+
const answer = await call("spawn.get", { request_id: requestId });
|
|
783
|
+
if (answer.ok === false) return answer;
|
|
784
|
+
return spawnTopology(recordAt(answer.value, "spawn"));
|
|
785
|
+
});
|
|
786
|
+
},
|
|
787
|
+
async agentGet(agentId) {
|
|
788
|
+
if (!isUuid(agentId))
|
|
789
|
+
return {
|
|
790
|
+
ok: false,
|
|
791
|
+
code: "bad_params",
|
|
792
|
+
message: "agent.get requires a canonical agent UUID"
|
|
793
|
+
};
|
|
794
|
+
return withRegistry(async () => {
|
|
795
|
+
const answer = await call("agent.get", { agent_id: agentId });
|
|
796
|
+
if (answer.ok === false) return answer;
|
|
797
|
+
return agentProcessAt(recordAt(answer.value, "agent"));
|
|
798
|
+
});
|
|
799
|
+
},
|
|
800
|
+
async childClose(request) {
|
|
801
|
+
if (!isUuid(request.request_id) || !isUuid(request.spawn_request_id) || !isUuid(request.incarnation))
|
|
802
|
+
return {
|
|
803
|
+
ok: false,
|
|
804
|
+
code: "bad_params",
|
|
805
|
+
message: "spawn.close requires canonical request, spawn and incarnation UUIDs"
|
|
806
|
+
};
|
|
807
|
+
return withContract(RADAR_SPAWN_CLOSE_CONTRACT, async () => {
|
|
808
|
+
const answer = await call(
|
|
809
|
+
"spawn.close",
|
|
810
|
+
{
|
|
811
|
+
spawn_request_id: request.spawn_request_id,
|
|
812
|
+
source: request.source,
|
|
813
|
+
incarnation: request.incarnation,
|
|
814
|
+
intent: request.intent
|
|
815
|
+
},
|
|
816
|
+
request.request_id
|
|
817
|
+
);
|
|
818
|
+
if (answer.ok === false) return answer;
|
|
819
|
+
const close = recordAt(answer.value, "close");
|
|
820
|
+
const outcome = stringAt(close, "outcome");
|
|
821
|
+
const pane = stringAt(close, "pane");
|
|
822
|
+
const detail = stringAt(close, "message");
|
|
823
|
+
if (outcome === "completed")
|
|
824
|
+
return pane !== void 0 ? { ok: true, value: { outcome, pane } } : protocol("spawn.close reply does not name the closed pane");
|
|
825
|
+
if (outcome === "already_exited") {
|
|
826
|
+
const exit = exitAt(recordAt(close, "exit_observation"));
|
|
827
|
+
return pane !== void 0 && exit !== void 0 ? {
|
|
828
|
+
ok: true,
|
|
829
|
+
value: { outcome, pane, exit_observation: exit }
|
|
830
|
+
} : protocol(
|
|
831
|
+
"spawn.close reply does not report the exit it cleaned up"
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
if (outcome === "refused" || outcome === "unknown")
|
|
835
|
+
return {
|
|
836
|
+
ok: true,
|
|
837
|
+
value: {
|
|
838
|
+
outcome,
|
|
839
|
+
...pane !== void 0 ? { pane } : {},
|
|
840
|
+
...detail !== void 0 ? { message: detail } : {}
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
return protocol("spawn.close reply carries no close outcome");
|
|
844
|
+
});
|
|
845
|
+
},
|
|
846
|
+
async events(request) {
|
|
847
|
+
if (!Number.isSafeInteger(request.after) || request.after < 0 || !Number.isSafeInteger(request.wait_ms) || request.wait_ms < 0 || request.wait_ms > RADAR_EVENT_WAIT_MS)
|
|
848
|
+
return {
|
|
849
|
+
ok: false,
|
|
850
|
+
code: "bad_params",
|
|
851
|
+
message: "events.read requires a sequence and a wait of 0 to 1000 ms"
|
|
852
|
+
};
|
|
853
|
+
return withContract(RADAR_EVENTS_CONTRACT, async () => {
|
|
854
|
+
const answer = await call("events.read", {
|
|
855
|
+
after: request.after,
|
|
856
|
+
wait_ms: request.wait_ms,
|
|
857
|
+
...request.epoch !== void 0 ? { epoch: request.epoch } : {}
|
|
858
|
+
});
|
|
859
|
+
if (answer.ok === false) return answer;
|
|
860
|
+
const reply = eventsReplyAt(request.after, answer.value);
|
|
861
|
+
return reply !== void 0 ? { ok: true, value: reply } : protocol("events.read reply carries no journal page or cursor_expired");
|
|
862
|
+
});
|
|
863
|
+
},
|
|
864
|
+
async observe() {
|
|
865
|
+
return withDaemon(async () => {
|
|
866
|
+
const answer = await call("mux.observe", {});
|
|
867
|
+
if (answer.ok === false) return answer;
|
|
868
|
+
const inventory = recordAt(answer.value, "inventory");
|
|
869
|
+
const workspaces = recordAt(inventory, "workspaces");
|
|
870
|
+
const tabs = recordAt(inventory, "tabs");
|
|
871
|
+
const panes = recordAt(inventory, "panes");
|
|
872
|
+
const agents = recordAt(inventory, "agents");
|
|
873
|
+
if (!Array.isArray(workspaces) || !Array.isArray(tabs) || !Array.isArray(panes) || !Array.isArray(agents))
|
|
874
|
+
return protocol(
|
|
875
|
+
"mux.observe reply carries no canonical fleet inventory"
|
|
876
|
+
);
|
|
877
|
+
const observed = {
|
|
878
|
+
workspaces: [],
|
|
879
|
+
tabs: [],
|
|
880
|
+
panes: [],
|
|
881
|
+
agents: []
|
|
882
|
+
};
|
|
883
|
+
for (const workspace of workspaces) {
|
|
884
|
+
const id = stringAt(workspace, "workspace_id");
|
|
885
|
+
if (id === void 0 || id === "")
|
|
886
|
+
return protocol("observe reply carries a malformed workspace");
|
|
887
|
+
observed.workspaces.push({ workspace_id: id });
|
|
888
|
+
}
|
|
889
|
+
for (const tab of tabs) {
|
|
890
|
+
const id = stringAt(tab, "tab_id");
|
|
891
|
+
const workspaceId = stringAt(tab, "workspace_id");
|
|
892
|
+
if (!id || !workspaceId)
|
|
893
|
+
return protocol("observe reply carries a malformed tab");
|
|
894
|
+
observed.tabs.push({ tab_id: id, workspace_id: workspaceId });
|
|
895
|
+
}
|
|
896
|
+
for (const pane of panes) {
|
|
897
|
+
const decoded = observedPane(pane);
|
|
898
|
+
if (decoded.ok === false) return decoded;
|
|
899
|
+
observed.panes.push(decoded.value);
|
|
900
|
+
}
|
|
901
|
+
for (const agent of agents) {
|
|
902
|
+
const decoded = observedAgent(agent);
|
|
903
|
+
if (decoded.ok === false) return decoded;
|
|
904
|
+
observed.agents.push(decoded.value);
|
|
905
|
+
}
|
|
906
|
+
return { ok: true, value: observed };
|
|
907
|
+
});
|
|
908
|
+
},
|
|
909
|
+
async output(request) {
|
|
910
|
+
return withDaemon(async () => {
|
|
911
|
+
const answer = await call("mux.output", request);
|
|
912
|
+
if (answer.ok === false) return answer;
|
|
913
|
+
const read = recordAt(answer.value, "output");
|
|
914
|
+
const text = stringAt(read, "text");
|
|
915
|
+
const truncated = recordAt(read, "truncated");
|
|
916
|
+
if (text === void 0 || typeof truncated !== "boolean")
|
|
917
|
+
return protocol("mux.output reply carries no output read");
|
|
918
|
+
const revision = recordAt(read, "revision");
|
|
919
|
+
return {
|
|
920
|
+
ok: true,
|
|
921
|
+
value: {
|
|
922
|
+
text,
|
|
923
|
+
truncated,
|
|
924
|
+
...typeof revision === "number" ? { revision } : {}
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
});
|
|
928
|
+
},
|
|
929
|
+
async processInfo(request) {
|
|
930
|
+
return withDaemon(async () => {
|
|
931
|
+
const answer = await call("mux.process_info", request);
|
|
932
|
+
if (answer.ok === false) return answer;
|
|
933
|
+
const evidence = recordAt(answer.value, "evidence");
|
|
934
|
+
const kind = stringAt(evidence, "kind");
|
|
935
|
+
if (kind === "shell") return { ok: true, value: { kind: "shell" } };
|
|
936
|
+
if (kind === "inconclusive")
|
|
937
|
+
return { ok: true, value: { kind: "inconclusive" } };
|
|
938
|
+
const pid = recordAt(evidence, "pid");
|
|
939
|
+
if (kind === "non_shell" && typeof pid === "number")
|
|
940
|
+
return {
|
|
941
|
+
ok: true,
|
|
942
|
+
value: {
|
|
943
|
+
kind: "non_shell",
|
|
944
|
+
pid,
|
|
945
|
+
...stringAt(evidence, "name") !== void 0 ? { name: stringAt(evidence, "name") } : {},
|
|
946
|
+
...stringAt(evidence, "command") !== void 0 ? { command: stringAt(evidence, "command") } : {}
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
return protocol("mux.process_info reply carries no foreground evidence");
|
|
950
|
+
});
|
|
951
|
+
},
|
|
952
|
+
async focus(request) {
|
|
953
|
+
if (!isUuid(request.request_id))
|
|
954
|
+
return {
|
|
955
|
+
ok: false,
|
|
956
|
+
code: "bad_params",
|
|
957
|
+
message: "focus requires a canonical operation UUID"
|
|
958
|
+
};
|
|
959
|
+
return withDaemon(async () => {
|
|
960
|
+
const answer = await call(
|
|
961
|
+
"mux.focus",
|
|
962
|
+
{
|
|
963
|
+
target: request.pane_id,
|
|
964
|
+
target_kind: "pane",
|
|
965
|
+
requester
|
|
966
|
+
},
|
|
967
|
+
request.request_id
|
|
968
|
+
);
|
|
969
|
+
if (answer.ok === true)
|
|
970
|
+
return focusOutcome(
|
|
971
|
+
recordAt(answer.value, "request"),
|
|
972
|
+
request.request_id
|
|
973
|
+
);
|
|
974
|
+
if (DISPATCH_REFUSED_CODES.includes(answer.code)) return answer;
|
|
975
|
+
const readback = await call("mux.request.get", {
|
|
976
|
+
id: request.request_id
|
|
977
|
+
});
|
|
978
|
+
if (readback.ok === false)
|
|
979
|
+
return unresolvedOperation(
|
|
980
|
+
request.request_id,
|
|
981
|
+
readback.code === "not_found" ? `${answer.message}; the daemon holds no record of it` : `${answer.message}; its record could not be read back (${readback.code}: ${readback.message})`
|
|
982
|
+
);
|
|
983
|
+
return focusOutcome(
|
|
984
|
+
recordAt(readback.value, "request"),
|
|
985
|
+
request.request_id
|
|
986
|
+
);
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
var RADAR_MAX_BACKGROUND_TASKS = 64;
|
|
992
|
+
var RADAR_MAX_AWAITED = 64;
|
|
993
|
+
function boundedRadarFacts(facts) {
|
|
994
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
995
|
+
"model",
|
|
996
|
+
"provider",
|
|
997
|
+
"thinking",
|
|
998
|
+
"context_window",
|
|
999
|
+
"session_name",
|
|
1000
|
+
"background_tasks",
|
|
1001
|
+
"background_started_unix_ms"
|
|
1002
|
+
]);
|
|
1003
|
+
const unknown = Object.keys(facts).find((key) => !allowed.has(key));
|
|
1004
|
+
if (unknown !== void 0) throw new RangeError(`unknown facts field ${unknown}`);
|
|
1005
|
+
const text = (value) => {
|
|
1006
|
+
if (typeof value !== "string") throw new RangeError("facts fields must be strings");
|
|
1007
|
+
return bounded(value, 1024);
|
|
1008
|
+
};
|
|
1009
|
+
const contextWindow = facts.context_window;
|
|
1010
|
+
if (contextWindow !== void 0 && contextWindow !== null) {
|
|
1011
|
+
if (typeof contextWindow !== "object" || Object.keys(contextWindow).sort().join(",") !== "tokens,window" || !Number.isSafeInteger(contextWindow.tokens) || contextWindow.tokens < 0 || !Number.isSafeInteger(contextWindow.window) || contextWindow.window <= 0 || contextWindow.tokens > contextWindow.window)
|
|
1012
|
+
throw new RangeError("context_window requires integer tokens within a positive window");
|
|
1013
|
+
}
|
|
1014
|
+
const backgroundTasks = facts.background_tasks;
|
|
1015
|
+
if (backgroundTasks !== void 0 && (!Array.isArray(backgroundTasks) || backgroundTasks.length > RADAR_MAX_BACKGROUND_TASKS))
|
|
1016
|
+
throw new RangeError(`background_tasks exceeds ${RADAR_MAX_BACKGROUND_TASKS} valid entries`);
|
|
1017
|
+
const taskIds = /* @__PURE__ */ new Set();
|
|
1018
|
+
const boundedTasks = backgroundTasks?.map((task) => {
|
|
1019
|
+
if (typeof task !== "object" || task === null || Array.isArray(task) || Object.keys(task).sort().join(",") !== "id,phase" || typeof task.id !== "string" || !task.id || typeof task.phase !== "string" || !task.phase)
|
|
1020
|
+
throw new RangeError("background_tasks requires non-empty id and phase fields");
|
|
1021
|
+
const id = text(task.id);
|
|
1022
|
+
if (taskIds.has(id)) throw new RangeError("background_tasks ids must be unique");
|
|
1023
|
+
taskIds.add(id);
|
|
1024
|
+
return { id, phase: text(task.phase) };
|
|
1025
|
+
});
|
|
1026
|
+
const started = facts.background_started_unix_ms;
|
|
1027
|
+
if (started !== void 0 && started !== null && (!Number.isSafeInteger(started) || started < 0))
|
|
1028
|
+
throw new RangeError("background_started_unix_ms must be a non-negative integer");
|
|
1029
|
+
const optionalText = (value) => value === void 0 ? void 0 : value === null ? null : text(value);
|
|
1030
|
+
return {
|
|
1031
|
+
...facts.model !== void 0 ? { model: optionalText(facts.model) } : {},
|
|
1032
|
+
...facts.provider !== void 0 ? { provider: optionalText(facts.provider) } : {},
|
|
1033
|
+
...facts.thinking !== void 0 ? { thinking: optionalText(facts.thinking) } : {},
|
|
1034
|
+
...contextWindow !== void 0 ? {
|
|
1035
|
+
context_window: contextWindow === null ? null : { tokens: contextWindow.tokens, window: contextWindow.window }
|
|
1036
|
+
} : {},
|
|
1037
|
+
...facts.session_name !== void 0 ? { session_name: optionalText(facts.session_name) } : {},
|
|
1038
|
+
...boundedTasks !== void 0 ? { background_tasks: boundedTasks } : {},
|
|
1039
|
+
...started !== void 0 ? { background_started_unix_ms: started } : {}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function boundedRadarAssignment(snapshot) {
|
|
1043
|
+
const base = boundedRadarSnapshot(snapshot);
|
|
1044
|
+
return {
|
|
1045
|
+
...base,
|
|
1046
|
+
...snapshot.request !== void 0 ? { request: bounded(snapshot.request, 1024) } : {},
|
|
1047
|
+
...snapshot.awaited !== void 0 ? {
|
|
1048
|
+
awaited: snapshot.awaited.slice(0, RADAR_MAX_AWAITED).map((item) => bounded(item, 1024))
|
|
1049
|
+
} : {}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function boundedRadarSnapshot(snapshot) {
|
|
1053
|
+
const text = (value) => bounded(value, 1024);
|
|
1054
|
+
return {
|
|
1055
|
+
activity: text(snapshot.activity),
|
|
1056
|
+
...snapshot.waiting_reason !== void 0 ? { waiting_reason: text(snapshot.waiting_reason) } : {},
|
|
1057
|
+
...snapshot.last_outcome !== void 0 ? {
|
|
1058
|
+
last_outcome: {
|
|
1059
|
+
result: text(snapshot.last_outcome.result),
|
|
1060
|
+
...snapshot.last_outcome.detail !== void 0 ? { detail: text(snapshot.last_outcome.detail) } : {}
|
|
1061
|
+
}
|
|
1062
|
+
} : {}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// extension/radar-publication.ts
|
|
1067
|
+
import { createHash, randomUUID as randomUUID2 } from "node:crypto";
|
|
1068
|
+
import {
|
|
1069
|
+
closeSync,
|
|
1070
|
+
constants,
|
|
1071
|
+
fstatSync,
|
|
1072
|
+
fsyncSync,
|
|
1073
|
+
lstatSync as lstatSync2,
|
|
1074
|
+
mkdirSync,
|
|
1075
|
+
openSync,
|
|
1076
|
+
readFileSync,
|
|
1077
|
+
renameSync,
|
|
1078
|
+
unlinkSync,
|
|
1079
|
+
writeSync
|
|
1080
|
+
} from "node:fs";
|
|
1081
|
+
import { dirname as dirname2, join as join2, parse, resolve, sep } from "node:path";
|
|
1082
|
+
|
|
1083
|
+
// extension/radar-session-marker.ts
|
|
1084
|
+
var RADAR_LAUNCH_MARKER_TYPE = "pi-radar.launch-identity";
|
|
1085
|
+
var RADAR_LAUNCH_MARKER_VERSION = 1;
|
|
1086
|
+
var RADAR_MAX_EXECUTION_GENERATION = 4294967295;
|
|
1087
|
+
var RADAR_MANAGED_SUBJECT_ENV = "PI_RADAR_MANAGED_SUBJECT_ID";
|
|
1088
|
+
var RADAR_MANAGED_LAUNCH_ENV = "PI_RADAR_MANAGED_LAUNCH_ID";
|
|
1089
|
+
var RADAR_MANAGED_GENERATION_ENV = "PI_RADAR_MANAGED_EXECUTION_GENERATION";
|
|
1090
|
+
var UUID2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
1091
|
+
function decodeRadarLaunchMarker(value) {
|
|
1092
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
1093
|
+
const record = value;
|
|
1094
|
+
if (Object.keys(record).sort().join(",") !== "execution_generation,launch_id,subject_id,version") {
|
|
1095
|
+
return void 0;
|
|
1096
|
+
}
|
|
1097
|
+
if (record.version !== RADAR_LAUNCH_MARKER_VERSION || typeof record.subject_id !== "string" || !UUID2.test(record.subject_id) || typeof record.launch_id !== "string" || !UUID2.test(record.launch_id) || !Number.isSafeInteger(record.execution_generation) || record.execution_generation < 1 || record.execution_generation > RADAR_MAX_EXECUTION_GENERATION) {
|
|
1098
|
+
return void 0;
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
version: RADAR_LAUNCH_MARKER_VERSION,
|
|
1102
|
+
subject_id: record.subject_id,
|
|
1103
|
+
launch_id: record.launch_id,
|
|
1104
|
+
execution_generation: record.execution_generation
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function appendRadarLaunchMarker(appendEntry, marker) {
|
|
1108
|
+
const validated = decodeRadarLaunchMarker(marker);
|
|
1109
|
+
if (!validated) throw new Error("invalid Radar launch identity marker");
|
|
1110
|
+
appendEntry(RADAR_LAUNCH_MARKER_TYPE, validated);
|
|
1111
|
+
}
|
|
1112
|
+
function latestRadarLaunchMarker(branch) {
|
|
1113
|
+
let latest;
|
|
1114
|
+
for (const entry of branch) {
|
|
1115
|
+
if (entry.type === "custom" && entry.customType === RADAR_LAUNCH_MARKER_TYPE) {
|
|
1116
|
+
latest = entry;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (!latest) return { status: "absent" };
|
|
1120
|
+
const marker = decodeRadarLaunchMarker(latest.data);
|
|
1121
|
+
return marker ? { status: "valid", marker } : { status: "invalid" };
|
|
1122
|
+
}
|
|
1123
|
+
function radarLaunchMarkerFromEnvironment(env = process.env) {
|
|
1124
|
+
const rawGeneration = env[RADAR_MANAGED_GENERATION_ENV];
|
|
1125
|
+
if (env[RADAR_MANAGED_SUBJECT_ENV] === void 0 || env[RADAR_MANAGED_LAUNCH_ENV] === void 0 || rawGeneration === void 0 || !/^[1-9][0-9]*$/.test(rawGeneration)) {
|
|
1126
|
+
return void 0;
|
|
1127
|
+
}
|
|
1128
|
+
return decodeRadarLaunchMarker({
|
|
1129
|
+
version: RADAR_LAUNCH_MARKER_VERSION,
|
|
1130
|
+
subject_id: env[RADAR_MANAGED_SUBJECT_ENV],
|
|
1131
|
+
launch_id: env[RADAR_MANAGED_LAUNCH_ENV],
|
|
1132
|
+
execution_generation: Number(rawGeneration)
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
function activeRadarLaunchMarker(host) {
|
|
1136
|
+
if (!host.sessionManager) return { status: "absent" };
|
|
1137
|
+
return latestRadarLaunchMarker(host.sessionManager.getBranch());
|
|
1138
|
+
}
|
|
1139
|
+
function persistRadarLaunchMarker(host, env = process.env) {
|
|
1140
|
+
const marker = radarLaunchMarkerFromEnvironment(env);
|
|
1141
|
+
if (!marker) return "absent";
|
|
1142
|
+
if (!host.appendEntry || !host.sessionManager) return "unsupported";
|
|
1143
|
+
const current = latestRadarLaunchMarker(host.sessionManager.getBranch());
|
|
1144
|
+
if (current.status === "valid" && sameMarker(current.marker, marker)) return "unchanged";
|
|
1145
|
+
appendRadarLaunchMarker(host.appendEntry, marker);
|
|
1146
|
+
return "written";
|
|
1147
|
+
}
|
|
1148
|
+
function sameMarker(a, b) {
|
|
1149
|
+
return a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// extension/radar-publication.ts
|
|
1153
|
+
var RADAR_PUBLICATION_VERSION = 1;
|
|
1154
|
+
var RADAR_LEASE_MS = 3e4;
|
|
1155
|
+
var RADAR_HEARTBEAT_MS = 15e3;
|
|
1156
|
+
function parseProcStat(text) {
|
|
1157
|
+
const close = text.lastIndexOf(")");
|
|
1158
|
+
const separator = text.indexOf(" ");
|
|
1159
|
+
if (close <= 0 || separator <= 0 || separator > close) return void 0;
|
|
1160
|
+
const pid = Number(text.slice(0, separator));
|
|
1161
|
+
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
1162
|
+
const startTicks = Number(fields[19]);
|
|
1163
|
+
if (!Number.isInteger(pid) || pid <= 0) return void 0;
|
|
1164
|
+
if (!Number.isInteger(startTicks) || startTicks <= 0) return void 0;
|
|
1165
|
+
return { pid, start_ticks: startTicks };
|
|
1166
|
+
}
|
|
1167
|
+
function readProcessBirth() {
|
|
1168
|
+
try {
|
|
1169
|
+
const bootId = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
1170
|
+
if (!/^[0-9a-f-]{8,64}$/i.test(bootId)) return void 0;
|
|
1171
|
+
const stat = parseProcStat(readFileSync("/proc/self/stat", "utf8"));
|
|
1172
|
+
return stat === void 0 ? void 0 : { boot_id: bootId, pid: stat.pid, start_ticks: stat.start_ticks };
|
|
1173
|
+
} catch {
|
|
1174
|
+
return void 0;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
var RECORD_NAME = /^(?:subjects|channels|context|bindings)\/[a-z0-9-]{1,64}\.json$/;
|
|
1178
|
+
var MAX_RECORD_BYTES = 64 * 1024;
|
|
1179
|
+
var PRIVATE_MODE = 448;
|
|
1180
|
+
var RECORD_MODE = 384;
|
|
1181
|
+
var PROCESS_SLOT = /* @__PURE__ */ Symbol.for("pi-radar.radar.process-slot.v1");
|
|
1182
|
+
function createPublicationStore(root) {
|
|
1183
|
+
const uid = process.getuid?.();
|
|
1184
|
+
if (uid === void 0) throw new Error("private Radar storage requires an effective uid");
|
|
1185
|
+
const ensureDirectory = (directory) => {
|
|
1186
|
+
let existed = true;
|
|
1187
|
+
try {
|
|
1188
|
+
lstatSync2(directory);
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
if (error.code !== "ENOENT") throw error;
|
|
1191
|
+
existed = false;
|
|
1192
|
+
}
|
|
1193
|
+
mkdirSync(directory, { recursive: true, mode: PRIVATE_MODE });
|
|
1194
|
+
const absolute = resolve(directory);
|
|
1195
|
+
const rootPath = parse(absolute).root;
|
|
1196
|
+
let current = rootPath;
|
|
1197
|
+
for (const part of absolute.slice(rootPath.length).split(/[\\/]+/u).filter(Boolean)) {
|
|
1198
|
+
current = join2(current, part);
|
|
1199
|
+
const info2 = lstatSync2(current);
|
|
1200
|
+
if (info2.isSymbolicLink() || !info2.isDirectory())
|
|
1201
|
+
throw new Error(`unsafe Radar storage directory: ${current}`);
|
|
1202
|
+
}
|
|
1203
|
+
const info = lstatSync2(absolute);
|
|
1204
|
+
if (info.uid !== uid) throw new Error(`Radar storage directory has wrong owner: ${absolute}`);
|
|
1205
|
+
if (existed && (info.mode & 511) !== PRIVATE_MODE)
|
|
1206
|
+
throw new Error(`Radar storage directory is not mode 0700: ${absolute}`);
|
|
1207
|
+
};
|
|
1208
|
+
const pathOf = (name) => {
|
|
1209
|
+
if (!RECORD_NAME.test(name))
|
|
1210
|
+
throw new Error(`invalid radar record name: ${name}`);
|
|
1211
|
+
return join2(root, name);
|
|
1212
|
+
};
|
|
1213
|
+
const ensureTrustedDirectory = (directory) => {
|
|
1214
|
+
const absoluteRoot = resolve(root);
|
|
1215
|
+
const absoluteDirectory = resolve(directory);
|
|
1216
|
+
if (absoluteDirectory !== absoluteRoot && !absoluteDirectory.startsWith(`${absoluteRoot}${sep}`))
|
|
1217
|
+
throw new Error(`unsafe Radar storage directory: ${directory}`);
|
|
1218
|
+
const rootInfo = lstatSync2(absoluteRoot);
|
|
1219
|
+
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory() || rootInfo.uid !== uid || (rootInfo.mode & 511) !== PRIVATE_MODE)
|
|
1220
|
+
throw new Error(`unsafe Radar storage directory: ${absoluteRoot}`);
|
|
1221
|
+
if (absoluteDirectory !== absoluteRoot) {
|
|
1222
|
+
const info = lstatSync2(absoluteDirectory);
|
|
1223
|
+
if (info.isSymbolicLink() || !info.isDirectory() || info.uid !== uid || (info.mode & 511) !== PRIVATE_MODE)
|
|
1224
|
+
throw new Error(`unsafe Radar storage directory: ${absoluteDirectory}`);
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
ensureDirectory(root);
|
|
1228
|
+
return {
|
|
1229
|
+
path: pathOf,
|
|
1230
|
+
read(name) {
|
|
1231
|
+
const path = pathOf(name);
|
|
1232
|
+
const directory = dirname2(path);
|
|
1233
|
+
try {
|
|
1234
|
+
ensureTrustedDirectory(directory);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
if (error.code === "ENOENT") return void 0;
|
|
1237
|
+
throw error;
|
|
1238
|
+
}
|
|
1239
|
+
let descriptor;
|
|
1240
|
+
try {
|
|
1241
|
+
descriptor = openSync(
|
|
1242
|
+
path,
|
|
1243
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
1244
|
+
);
|
|
1245
|
+
const info = fstatSync(descriptor);
|
|
1246
|
+
if (!info.isFile() || info.uid !== uid || (info.mode & 511) !== RECORD_MODE)
|
|
1247
|
+
throw new Error(`unsafe Radar record: ${path}`);
|
|
1248
|
+
if (info.size > MAX_RECORD_BYTES)
|
|
1249
|
+
throw new Error(`oversized Radar record: ${path}`);
|
|
1250
|
+
const contents = readFileSync(descriptor, { encoding: "utf8" });
|
|
1251
|
+
if (Buffer.byteLength(contents, "utf8") > MAX_RECORD_BYTES)
|
|
1252
|
+
throw new Error(`oversized Radar record: ${path}`);
|
|
1253
|
+
return JSON.parse(contents);
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
if (error.code === "ENOENT") return void 0;
|
|
1256
|
+
throw error;
|
|
1257
|
+
} finally {
|
|
1258
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
write(name, value) {
|
|
1262
|
+
const path = pathOf(name);
|
|
1263
|
+
const directory = dirname2(path);
|
|
1264
|
+
ensureDirectory(directory);
|
|
1265
|
+
const temporary = join2(
|
|
1266
|
+
directory,
|
|
1267
|
+
`.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID2()}.tmp`
|
|
1268
|
+
);
|
|
1269
|
+
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
1270
|
+
if (bytes.length > MAX_RECORD_BYTES)
|
|
1271
|
+
throw new Error(`Radar record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
1272
|
+
let descriptor;
|
|
1273
|
+
try {
|
|
1274
|
+
descriptor = openSync(temporary, "wx", RECORD_MODE);
|
|
1275
|
+
let offset = 0;
|
|
1276
|
+
while (offset < bytes.length)
|
|
1277
|
+
offset += writeSync(descriptor, bytes, offset, bytes.length - offset);
|
|
1278
|
+
fsyncSync(descriptor);
|
|
1279
|
+
closeSync(descriptor);
|
|
1280
|
+
descriptor = void 0;
|
|
1281
|
+
const info = lstatSync2(directory);
|
|
1282
|
+
if (info.isSymbolicLink() || !info.isDirectory() || info.uid !== uid || (info.mode & 511) !== PRIVATE_MODE)
|
|
1283
|
+
throw new Error(`unsafe Radar storage directory: ${directory}`);
|
|
1284
|
+
renameSync(temporary, path);
|
|
1285
|
+
const directoryFd = openSync(directory, constants.O_RDONLY);
|
|
1286
|
+
try {
|
|
1287
|
+
fsyncSync(directoryFd);
|
|
1288
|
+
} finally {
|
|
1289
|
+
closeSync(directoryFd);
|
|
1290
|
+
}
|
|
1291
|
+
} finally {
|
|
1292
|
+
if (descriptor !== void 0) closeSync(descriptor);
|
|
1293
|
+
try {
|
|
1294
|
+
unlinkSync(temporary);
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
if (error.code !== "ENOENT") throw error;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
remove(name) {
|
|
1301
|
+
try {
|
|
1302
|
+
unlinkSync(pathOf(name));
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
if (error.code !== "ENOENT") throw error;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
function createRadarProcessSlot() {
|
|
1310
|
+
return { publications: /* @__PURE__ */ new Map() };
|
|
1311
|
+
}
|
|
1312
|
+
function radarProcessSlot() {
|
|
1313
|
+
const host = globalThis;
|
|
1314
|
+
return host[PROCESS_SLOT] ??= createRadarProcessSlot();
|
|
1315
|
+
}
|
|
1316
|
+
function radarProcessIncarnation(slot = radarProcessSlot()) {
|
|
1317
|
+
return slot.incarnation ??= randomUUID2();
|
|
1318
|
+
}
|
|
1319
|
+
function recordAt2(value, key) {
|
|
1320
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1321
|
+
return value[key];
|
|
1322
|
+
}
|
|
1323
|
+
function textAt(value, key) {
|
|
1324
|
+
const found = recordAt2(value, key);
|
|
1325
|
+
return typeof found === "string" ? found : void 0;
|
|
1326
|
+
}
|
|
1327
|
+
function numberAt2(value, key) {
|
|
1328
|
+
const found = recordAt2(value, key);
|
|
1329
|
+
return typeof found === "number" && Number.isFinite(found) ? found : void 0;
|
|
1330
|
+
}
|
|
1331
|
+
var PENDING_KEYS = "lease_ms,observed_at,sequence,snapshot";
|
|
1332
|
+
var ACCEPTED_KEYS = "lease_ms,observed_at,sequence,sent_at,snapshot";
|
|
1333
|
+
var OUTCOME_KEYS = "detail,result";
|
|
1334
|
+
var OUTCOME_KEYS_WITHOUT_DETAIL = "result";
|
|
1335
|
+
var WRITER_KEYS = "generation,handle";
|
|
1336
|
+
function exactKeys(value, expected) {
|
|
1337
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).sort().join(",") === expected;
|
|
1338
|
+
}
|
|
1339
|
+
var SNAPSHOT_KEY_LIST = ["activity", "last_outcome", "waiting_reason"];
|
|
1340
|
+
var ASSIGNMENT_KEY_LIST = [...SNAPSHOT_KEY_LIST, "request", "awaited"];
|
|
1341
|
+
var FACTS_KEY_LIST = [
|
|
1342
|
+
"model",
|
|
1343
|
+
"provider",
|
|
1344
|
+
"thinking",
|
|
1345
|
+
"context_window",
|
|
1346
|
+
"session_name",
|
|
1347
|
+
"background_tasks",
|
|
1348
|
+
"background_started_unix_ms"
|
|
1349
|
+
];
|
|
1350
|
+
function snapshotAt(value, keys = SNAPSHOT_KEY_LIST) {
|
|
1351
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !Object.keys(value).every((key) => keys.includes(key)))
|
|
1352
|
+
return void 0;
|
|
1353
|
+
const activity = textAt(value, "activity");
|
|
1354
|
+
if (activity === void 0) return void 0;
|
|
1355
|
+
const snapshot = { activity };
|
|
1356
|
+
const reason = textAt(value, "waiting_reason");
|
|
1357
|
+
if (reason !== void 0) snapshot.waiting_reason = reason;
|
|
1358
|
+
const outcome = recordAt2(value, "last_outcome");
|
|
1359
|
+
if (outcome !== void 0) {
|
|
1360
|
+
if (!exactKeys(outcome, OUTCOME_KEYS) && !exactKeys(outcome, OUTCOME_KEYS_WITHOUT_DETAIL))
|
|
1361
|
+
return void 0;
|
|
1362
|
+
const result = textAt(outcome, "result");
|
|
1363
|
+
if (result === void 0) return void 0;
|
|
1364
|
+
const detail = textAt(outcome, "detail");
|
|
1365
|
+
snapshot.last_outcome = {
|
|
1366
|
+
result,
|
|
1367
|
+
...detail !== void 0 ? { detail } : {}
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
return snapshot;
|
|
1371
|
+
}
|
|
1372
|
+
var executionCodec = {
|
|
1373
|
+
parse: (value) => snapshotAt(value),
|
|
1374
|
+
bound: (snapshot) => boundedRadarSnapshot(snapshot)
|
|
1375
|
+
};
|
|
1376
|
+
function assignmentAt(value) {
|
|
1377
|
+
const base = snapshotAt(value, ASSIGNMENT_KEY_LIST);
|
|
1378
|
+
if (base === void 0) return void 0;
|
|
1379
|
+
const request = textAt(value, "request");
|
|
1380
|
+
const awaitedValue = recordAt2(value, "awaited");
|
|
1381
|
+
if (awaitedValue !== void 0 && (!Array.isArray(awaitedValue) || awaitedValue.length > RADAR_MAX_AWAITED || !awaitedValue.every((item) => typeof item === "string")))
|
|
1382
|
+
return void 0;
|
|
1383
|
+
if (recordAt2(value, "request") !== void 0 && request === void 0)
|
|
1384
|
+
return void 0;
|
|
1385
|
+
return {
|
|
1386
|
+
...base,
|
|
1387
|
+
...request !== void 0 ? { request } : {},
|
|
1388
|
+
...awaitedValue !== void 0 ? { awaited: awaitedValue } : {}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
var assignmentCodec = {
|
|
1392
|
+
parse: (value) => assignmentAt(value),
|
|
1393
|
+
bound: (snapshot) => boundedRadarAssignment(snapshot)
|
|
1394
|
+
};
|
|
1395
|
+
function factsAt(value) {
|
|
1396
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1397
|
+
return void 0;
|
|
1398
|
+
const record = value;
|
|
1399
|
+
if (!Object.keys(record).every((key) => FACTS_KEY_LIST.includes(key)))
|
|
1400
|
+
return void 0;
|
|
1401
|
+
const facts = {};
|
|
1402
|
+
for (const key of FACTS_KEY_LIST) {
|
|
1403
|
+
const found = record[key];
|
|
1404
|
+
if (found === void 0) continue;
|
|
1405
|
+
if (key === "context_window") {
|
|
1406
|
+
if (typeof found !== "object" || found === null || Array.isArray(found))
|
|
1407
|
+
return void 0;
|
|
1408
|
+
const window = found;
|
|
1409
|
+
if (Object.keys(window).sort().join(",") !== "tokens,window") return void 0;
|
|
1410
|
+
const tokens = window.tokens;
|
|
1411
|
+
const size = window.window;
|
|
1412
|
+
if (!Number.isSafeInteger(tokens) || tokens < 0 || !Number.isSafeInteger(size) || size <= 0 || tokens > size)
|
|
1413
|
+
return void 0;
|
|
1414
|
+
facts[key] = { tokens, window: size };
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
if (key === "background_tasks") {
|
|
1418
|
+
if (!Array.isArray(found) || found.length > RADAR_MAX_BACKGROUND_TASKS)
|
|
1419
|
+
return void 0;
|
|
1420
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1421
|
+
const tasks = [];
|
|
1422
|
+
for (const item of found) {
|
|
1423
|
+
if (typeof item !== "object" || item === null || Array.isArray(item))
|
|
1424
|
+
return void 0;
|
|
1425
|
+
const task = item;
|
|
1426
|
+
if (Object.keys(task).sort().join(",") !== "id,phase" || typeof task.id !== "string" || !task.id || typeof task.phase !== "string" || !task.phase || ids.has(task.id))
|
|
1427
|
+
return void 0;
|
|
1428
|
+
ids.add(task.id);
|
|
1429
|
+
tasks.push({ id: task.id, phase: task.phase });
|
|
1430
|
+
}
|
|
1431
|
+
facts[key] = tasks;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
if (key === "background_started_unix_ms") {
|
|
1435
|
+
if (!Number.isSafeInteger(found) || found < 0) return void 0;
|
|
1436
|
+
facts[key] = found;
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (typeof found !== "string") return void 0;
|
|
1440
|
+
facts[key] = found;
|
|
1441
|
+
}
|
|
1442
|
+
return facts;
|
|
1443
|
+
}
|
|
1444
|
+
var factsCodec = {
|
|
1445
|
+
parse: (value) => factsAt(value),
|
|
1446
|
+
bound: (snapshot) => boundedRadarFacts(snapshot)
|
|
1447
|
+
};
|
|
1448
|
+
function pendingAt(value, keys = PENDING_KEYS, codec = executionCodec) {
|
|
1449
|
+
if (!exactKeys(value, keys)) return void 0;
|
|
1450
|
+
const sequence = numberAt2(value, "sequence");
|
|
1451
|
+
const lease = numberAt2(value, "lease_ms");
|
|
1452
|
+
const observed = textAt(value, "observed_at");
|
|
1453
|
+
const snapshot = codec.parse(recordAt2(value, "snapshot"));
|
|
1454
|
+
if (sequence === void 0 || sequence < 1) return void 0;
|
|
1455
|
+
if (lease === void 0 || observed === void 0 || snapshot === void 0)
|
|
1456
|
+
return void 0;
|
|
1457
|
+
return { sequence, snapshot, lease_ms: lease, observed_at: observed };
|
|
1458
|
+
}
|
|
1459
|
+
var CONTEXT_PENDING_KEYS = "lease_ms,observed_at,sequence,session";
|
|
1460
|
+
var CONTEXT_PENDING_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sequence,session";
|
|
1461
|
+
var CONTEXT_ACCEPTED_KEYS = "lease_ms,observed_at,sent_at,sequence,session";
|
|
1462
|
+
var CONTEXT_ACCEPTED_MARKED_KEYS = "launch_identity,lease_ms,observed_at,sent_at,sequence,session";
|
|
1463
|
+
function sessionAt(value) {
|
|
1464
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1465
|
+
const found = value.session;
|
|
1466
|
+
if (found === null) return null;
|
|
1467
|
+
return typeof found === "string" ? found : void 0;
|
|
1468
|
+
}
|
|
1469
|
+
function sameLaunchIdentity(a, b) {
|
|
1470
|
+
return a === b || a !== void 0 && b !== void 0 && a.version === b.version && a.subject_id === b.subject_id && a.launch_id === b.launch_id && a.execution_generation === b.execution_generation;
|
|
1471
|
+
}
|
|
1472
|
+
function contextReportAt(value, keys, markedKeys) {
|
|
1473
|
+
const marked = exactKeys(value, markedKeys);
|
|
1474
|
+
if (!exactKeys(value, keys) && !marked) return void 0;
|
|
1475
|
+
const sequence = numberAt2(value, "sequence");
|
|
1476
|
+
const lease = numberAt2(value, "lease_ms");
|
|
1477
|
+
const observed = textAt(value, "observed_at");
|
|
1478
|
+
const session = sessionAt(value);
|
|
1479
|
+
if (sequence === void 0 || sequence < 1) return void 0;
|
|
1480
|
+
if (lease === void 0 || observed === void 0 || session === void 0)
|
|
1481
|
+
return void 0;
|
|
1482
|
+
const marker = marked ? decodeRadarLaunchMarker(value.launch_identity) : void 0;
|
|
1483
|
+
if (marked && marker === void 0) return void 0;
|
|
1484
|
+
return {
|
|
1485
|
+
sequence,
|
|
1486
|
+
session,
|
|
1487
|
+
lease_ms: lease,
|
|
1488
|
+
observed_at: observed,
|
|
1489
|
+
...marker !== void 0 ? { launch_identity: marker } : {}
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function createChannelWriter(input) {
|
|
1493
|
+
const { store, client, scheduler, recordName, channel, publisher } = input;
|
|
1494
|
+
const codec = input.codec ?? executionCodec;
|
|
1495
|
+
let record = loadRecord();
|
|
1496
|
+
let desired;
|
|
1497
|
+
let running = false;
|
|
1498
|
+
let again = false;
|
|
1499
|
+
let recovering = false;
|
|
1500
|
+
let stopped = false;
|
|
1501
|
+
let fenced = false;
|
|
1502
|
+
let retired = false;
|
|
1503
|
+
let pump;
|
|
1504
|
+
function blank(agentId) {
|
|
1505
|
+
return {
|
|
1506
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
1507
|
+
agent_id: agentId,
|
|
1508
|
+
channel,
|
|
1509
|
+
publisher,
|
|
1510
|
+
sequence: 0
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function loadRecord() {
|
|
1514
|
+
const value = store.read(recordName);
|
|
1515
|
+
const agentId = textAt(value, "agent_id");
|
|
1516
|
+
const storedPublisher = recordAt2(value, "publisher");
|
|
1517
|
+
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) || textAt(value, "channel") !== channel || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
|
|
1518
|
+
return blank("");
|
|
1519
|
+
const writer = recordAt2(value, "writer");
|
|
1520
|
+
const handle = textAt(writer, "handle");
|
|
1521
|
+
const generation = numberAt2(writer, "generation");
|
|
1522
|
+
if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
|
|
1523
|
+
return blank(agentId);
|
|
1524
|
+
const pending = pendingAt(recordAt2(value, "pending"), PENDING_KEYS, codec);
|
|
1525
|
+
const acceptedValue = recordAt2(value, "accepted");
|
|
1526
|
+
const accepted = pendingAt(acceptedValue, ACCEPTED_KEYS, codec);
|
|
1527
|
+
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
1528
|
+
return {
|
|
1529
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
1530
|
+
agent_id: agentId,
|
|
1531
|
+
channel,
|
|
1532
|
+
publisher,
|
|
1533
|
+
...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
|
|
1534
|
+
...textAt(value, "retired_at") !== void 0 ? { retired_at: textAt(value, "retired_at") } : {},
|
|
1535
|
+
sequence: numberAt2(value, "sequence") ?? 0,
|
|
1536
|
+
...pending !== void 0 ? { pending } : {},
|
|
1537
|
+
...accepted !== void 0 && sentAt !== void 0 ? { accepted: { ...accepted, sent_at: sentAt } } : {},
|
|
1538
|
+
...textAt(value, "diagnostic") !== void 0 ? { diagnostic: textAt(value, "diagnostic") } : {}
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function note(message) {
|
|
1542
|
+
if (record.diagnostic !== void 0) return;
|
|
1543
|
+
record.diagnostic = message.slice(0, 256);
|
|
1544
|
+
save();
|
|
1545
|
+
}
|
|
1546
|
+
function save() {
|
|
1547
|
+
store.write(recordName, record);
|
|
1548
|
+
}
|
|
1549
|
+
function sameSnapshot(left, right) {
|
|
1550
|
+
return JSON.stringify(codec.bound(left)) === JSON.stringify(codec.bound(right));
|
|
1551
|
+
}
|
|
1552
|
+
function fence(message) {
|
|
1553
|
+
fenced = true;
|
|
1554
|
+
stopPump();
|
|
1555
|
+
note(`channel ${channel} stopped: ${message}`);
|
|
1556
|
+
}
|
|
1557
|
+
function startPump() {
|
|
1558
|
+
if (pump !== void 0 || stopped || fenced || retired) return;
|
|
1559
|
+
pump = scheduler.setInterval(() => kick(), input.heartbeatMs);
|
|
1560
|
+
pump.unref?.();
|
|
1561
|
+
}
|
|
1562
|
+
function stopPump() {
|
|
1563
|
+
if (pump === void 0) return;
|
|
1564
|
+
scheduler.clearInterval(pump);
|
|
1565
|
+
pump = void 0;
|
|
1566
|
+
}
|
|
1567
|
+
async function ensureWriter() {
|
|
1568
|
+
if (record.writer !== void 0) return true;
|
|
1569
|
+
const agentId = await input.subject(recovering);
|
|
1570
|
+
if (agentId === void 0) return false;
|
|
1571
|
+
if (record.agent_id !== agentId) {
|
|
1572
|
+
record = blank(agentId);
|
|
1573
|
+
save();
|
|
1574
|
+
}
|
|
1575
|
+
const answer = await client.acquire({
|
|
1576
|
+
agent_id: agentId,
|
|
1577
|
+
channel,
|
|
1578
|
+
publisher
|
|
1579
|
+
});
|
|
1580
|
+
if (answer.ok === false) {
|
|
1581
|
+
if (answer.code === "not_found") {
|
|
1582
|
+
recovering = true;
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1585
|
+
if (answer.code === "refused" || answer.code === "bad_params")
|
|
1586
|
+
fence(answer.message);
|
|
1587
|
+
return false;
|
|
1588
|
+
}
|
|
1589
|
+
recovering = false;
|
|
1590
|
+
record.writer = {
|
|
1591
|
+
handle: answer.value.handle,
|
|
1592
|
+
generation: answer.value.generation
|
|
1593
|
+
};
|
|
1594
|
+
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
1595
|
+
save();
|
|
1596
|
+
startPump();
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1599
|
+
function heartbeatDue() {
|
|
1600
|
+
return record.accepted !== void 0 && scheduler.now() - record.accepted.sent_at >= input.heartbeatMs;
|
|
1601
|
+
}
|
|
1602
|
+
async function send(pending) {
|
|
1603
|
+
const writer = record.writer;
|
|
1604
|
+
if (writer === void 0) return;
|
|
1605
|
+
const answer = await client.publish({
|
|
1606
|
+
agent_id: record.agent_id,
|
|
1607
|
+
channel,
|
|
1608
|
+
writer_handle: writer.handle,
|
|
1609
|
+
sequence: pending.sequence,
|
|
1610
|
+
lease_ms: pending.lease_ms,
|
|
1611
|
+
observed_at: pending.observed_at,
|
|
1612
|
+
snapshot: pending.snapshot
|
|
1613
|
+
});
|
|
1614
|
+
if (answer.ok === true) {
|
|
1615
|
+
recovering = false;
|
|
1616
|
+
record.sequence = Math.max(record.sequence, answer.value.sequence);
|
|
1617
|
+
record.accepted = { ...pending, sent_at: scheduler.now() };
|
|
1618
|
+
record.pending = void 0;
|
|
1619
|
+
save();
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
if (answer.code === "refused" || answer.code === "bad_params") {
|
|
1623
|
+
fence(answer.message);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
if (answer.code === "not_found") {
|
|
1627
|
+
record.writer = void 0;
|
|
1628
|
+
recovering = true;
|
|
1629
|
+
save();
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
async function stage(snapshot) {
|
|
1634
|
+
const pending = {
|
|
1635
|
+
sequence: record.sequence + 1,
|
|
1636
|
+
snapshot: codec.bound(snapshot),
|
|
1637
|
+
lease_ms: input.leaseMs,
|
|
1638
|
+
observed_at: new Date(scheduler.now()).toISOString()
|
|
1639
|
+
};
|
|
1640
|
+
record.pending = pending;
|
|
1641
|
+
save();
|
|
1642
|
+
await send(pending);
|
|
1643
|
+
}
|
|
1644
|
+
async function drainOnce() {
|
|
1645
|
+
if (fenced || retired) return;
|
|
1646
|
+
if (!await ensureWriter()) return;
|
|
1647
|
+
if (fenced) return;
|
|
1648
|
+
if (record.pending !== void 0) {
|
|
1649
|
+
await send(record.pending);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (desired !== void 0 && (record.accepted === void 0 || !sameSnapshot(desired, record.accepted.snapshot))) {
|
|
1653
|
+
await stage(desired);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (heartbeatDue() && record.accepted !== void 0)
|
|
1657
|
+
await stage(record.accepted.snapshot);
|
|
1658
|
+
}
|
|
1659
|
+
async function drain() {
|
|
1660
|
+
if (running || stopped || fenced || retired) return;
|
|
1661
|
+
running = true;
|
|
1662
|
+
try {
|
|
1663
|
+
await drainOnce();
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
fenced = true;
|
|
1666
|
+
stopPump();
|
|
1667
|
+
try {
|
|
1668
|
+
note(`channel ${channel} storage failure: ${String(error).slice(0, 180)}`);
|
|
1669
|
+
} catch {
|
|
1670
|
+
}
|
|
1671
|
+
} finally {
|
|
1672
|
+
running = false;
|
|
1673
|
+
if (again && !stopped && !fenced && !retired) {
|
|
1674
|
+
again = false;
|
|
1675
|
+
kick();
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
function kick() {
|
|
1680
|
+
if (stopped || fenced || retired) return;
|
|
1681
|
+
startPump();
|
|
1682
|
+
if (running) {
|
|
1683
|
+
again = true;
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
void drain().catch(() => void 0);
|
|
1687
|
+
}
|
|
1688
|
+
return {
|
|
1689
|
+
update(snapshot) {
|
|
1690
|
+
if (stopped || fenced || retired) return;
|
|
1691
|
+
desired = snapshot;
|
|
1692
|
+
kick();
|
|
1693
|
+
},
|
|
1694
|
+
retire() {
|
|
1695
|
+
if (stopped || fenced || retired) return;
|
|
1696
|
+
retired = true;
|
|
1697
|
+
stopPump();
|
|
1698
|
+
const writer = record.writer;
|
|
1699
|
+
if (writer === void 0) return;
|
|
1700
|
+
const agentId = record.agent_id;
|
|
1701
|
+
try {
|
|
1702
|
+
void client.retire({ agent_id: agentId, channel, writer_handle: writer.handle }).then((answer) => {
|
|
1703
|
+
if (answer.ok === true) {
|
|
1704
|
+
try {
|
|
1705
|
+
record.retired_at = new Date(scheduler.now()).toISOString();
|
|
1706
|
+
save();
|
|
1707
|
+
} catch {
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}).catch(() => void 0);
|
|
1711
|
+
} catch {
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
stop() {
|
|
1715
|
+
stopped = true;
|
|
1716
|
+
stopPump();
|
|
1717
|
+
},
|
|
1718
|
+
reopen() {
|
|
1719
|
+
stopped = false;
|
|
1720
|
+
if (desired !== void 0 || record.pending !== void 0) kick();
|
|
1721
|
+
},
|
|
1722
|
+
diagnostic: () => record.diagnostic
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function createContextWriter(input) {
|
|
1726
|
+
const { store, client, scheduler, recordName, publisher } = input;
|
|
1727
|
+
let record = loadRecord();
|
|
1728
|
+
let desired;
|
|
1729
|
+
let desiredMarker;
|
|
1730
|
+
let running = false;
|
|
1731
|
+
let again = false;
|
|
1732
|
+
let recovering = false;
|
|
1733
|
+
let stopped = false;
|
|
1734
|
+
let fenced = false;
|
|
1735
|
+
let pump;
|
|
1736
|
+
function blank(agentId) {
|
|
1737
|
+
return {
|
|
1738
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
1739
|
+
agent_id: agentId,
|
|
1740
|
+
publisher,
|
|
1741
|
+
sequence: 0
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function loadRecord() {
|
|
1745
|
+
const value = store.read(recordName);
|
|
1746
|
+
const agentId = textAt(value, "agent_id");
|
|
1747
|
+
const storedPublisher = recordAt2(value, "publisher");
|
|
1748
|
+
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || agentId === void 0 || !isUuid(agentId) && agentId !== "" || textAt(storedPublisher, "source") !== publisher.source || textAt(storedPublisher, "incarnation") !== publisher.incarnation || textAt(storedPublisher, "reporting_owner") !== publisher.reporting_owner)
|
|
1749
|
+
return blank("");
|
|
1750
|
+
const writer = recordAt2(value, "writer");
|
|
1751
|
+
const handle = textAt(writer, "handle");
|
|
1752
|
+
const generation = numberAt2(writer, "generation");
|
|
1753
|
+
if (writer !== void 0 && (!exactKeys(writer, WRITER_KEYS) || !isUuid(handle) || generation === void 0))
|
|
1754
|
+
return blank(agentId);
|
|
1755
|
+
const pending = contextReportAt(
|
|
1756
|
+
recordAt2(value, "pending"),
|
|
1757
|
+
CONTEXT_PENDING_KEYS,
|
|
1758
|
+
CONTEXT_PENDING_MARKED_KEYS
|
|
1759
|
+
);
|
|
1760
|
+
const acceptedValue = recordAt2(value, "accepted");
|
|
1761
|
+
const accepted = contextReportAt(
|
|
1762
|
+
acceptedValue,
|
|
1763
|
+
CONTEXT_ACCEPTED_KEYS,
|
|
1764
|
+
CONTEXT_ACCEPTED_MARKED_KEYS
|
|
1765
|
+
);
|
|
1766
|
+
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
1767
|
+
return {
|
|
1768
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
1769
|
+
agent_id: agentId,
|
|
1770
|
+
publisher,
|
|
1771
|
+
...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
|
|
1772
|
+
sequence: numberAt2(value, "sequence") ?? 0,
|
|
1773
|
+
...pending !== void 0 ? { pending } : {},
|
|
1774
|
+
...accepted !== void 0 && sentAt !== void 0 ? { accepted: { ...accepted, sent_at: sentAt } } : {},
|
|
1775
|
+
...textAt(value, "diagnostic") !== void 0 ? { diagnostic: textAt(value, "diagnostic") } : {}
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
function note(message) {
|
|
1779
|
+
if (record.diagnostic !== void 0) return;
|
|
1780
|
+
record.diagnostic = message.slice(0, 256);
|
|
1781
|
+
save();
|
|
1782
|
+
}
|
|
1783
|
+
function save() {
|
|
1784
|
+
store.write(recordName, record);
|
|
1785
|
+
}
|
|
1786
|
+
function fence(message) {
|
|
1787
|
+
fenced = true;
|
|
1788
|
+
stopPump();
|
|
1789
|
+
note(`context stopped: ${message}`);
|
|
1790
|
+
}
|
|
1791
|
+
function startPump() {
|
|
1792
|
+
if (pump !== void 0 || stopped || fenced) return;
|
|
1793
|
+
pump = scheduler.setInterval(() => kick(), input.heartbeatMs);
|
|
1794
|
+
pump.unref?.();
|
|
1795
|
+
}
|
|
1796
|
+
function stopPump() {
|
|
1797
|
+
if (pump === void 0) return;
|
|
1798
|
+
scheduler.clearInterval(pump);
|
|
1799
|
+
pump = void 0;
|
|
1800
|
+
}
|
|
1801
|
+
async function ensureSubject() {
|
|
1802
|
+
if (record.writer !== void 0) return true;
|
|
1803
|
+
const agentId = await input.subject(recovering);
|
|
1804
|
+
if (agentId === void 0) return false;
|
|
1805
|
+
if (record.agent_id !== agentId) {
|
|
1806
|
+
record = blank(agentId);
|
|
1807
|
+
save();
|
|
1808
|
+
}
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1811
|
+
function heartbeatDue() {
|
|
1812
|
+
return record.accepted !== void 0 && scheduler.now() - record.accepted.sent_at >= input.heartbeatMs;
|
|
1813
|
+
}
|
|
1814
|
+
async function send(pending) {
|
|
1815
|
+
const writer = record.writer;
|
|
1816
|
+
const answer = await client.context({
|
|
1817
|
+
agent_id: record.agent_id,
|
|
1818
|
+
publisher,
|
|
1819
|
+
// A first publish presents no handle: the daemon binds the writer the
|
|
1820
|
+
// publisher already is, which is the binding this writer then keeps.
|
|
1821
|
+
...writer !== void 0 ? { writer_handle: writer.handle } : {},
|
|
1822
|
+
sequence: pending.sequence,
|
|
1823
|
+
lease_ms: pending.lease_ms,
|
|
1824
|
+
observed_at: pending.observed_at,
|
|
1825
|
+
context: {
|
|
1826
|
+
session: pending.session,
|
|
1827
|
+
...pending.launch_identity !== void 0 ? { launch_identity: pending.launch_identity } : {}
|
|
1828
|
+
}
|
|
1829
|
+
});
|
|
1830
|
+
if (answer.ok === true) {
|
|
1831
|
+
recovering = false;
|
|
1832
|
+
record.writer = {
|
|
1833
|
+
handle: answer.value.writer.handle,
|
|
1834
|
+
generation: answer.value.writer.generation
|
|
1835
|
+
};
|
|
1836
|
+
record.sequence = Math.max(record.sequence, answer.value.writer.sequence);
|
|
1837
|
+
record.accepted = {
|
|
1838
|
+
...pending,
|
|
1839
|
+
sent_at: answer.value.warning === void 0 ? scheduler.now() : record.accepted?.sent_at ?? scheduler.now()
|
|
1840
|
+
};
|
|
1841
|
+
record.pending = void 0;
|
|
1842
|
+
save();
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
if (answer.code === "refused" || answer.code === "bad_params" || // A daemon that speaks the registry but does not serve this record would
|
|
1846
|
+
// refuse every retry the same way; one diagnostic beats a silent retry
|
|
1847
|
+
// loop once per heartbeat.
|
|
1848
|
+
answer.code === "unknown_method") {
|
|
1849
|
+
fence(answer.message);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (answer.code === "not_found") {
|
|
1853
|
+
record = blank("");
|
|
1854
|
+
recovering = true;
|
|
1855
|
+
save();
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
async function stage(session, launchIdentity) {
|
|
1860
|
+
const pending = {
|
|
1861
|
+
sequence: record.sequence + 1,
|
|
1862
|
+
session,
|
|
1863
|
+
lease_ms: input.leaseMs,
|
|
1864
|
+
observed_at: new Date(scheduler.now()).toISOString(),
|
|
1865
|
+
...launchIdentity !== void 0 ? { launch_identity: launchIdentity } : {}
|
|
1866
|
+
};
|
|
1867
|
+
record.pending = pending;
|
|
1868
|
+
save();
|
|
1869
|
+
await send(pending);
|
|
1870
|
+
}
|
|
1871
|
+
async function drainOnce() {
|
|
1872
|
+
if (fenced) return;
|
|
1873
|
+
if (record.pending !== void 0) {
|
|
1874
|
+
await send(record.pending);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (desired === void 0) return;
|
|
1878
|
+
if (!await ensureSubject()) return;
|
|
1879
|
+
if (fenced) return;
|
|
1880
|
+
if (record.writer === void 0 || record.accepted === void 0 || record.accepted.session !== desired || !sameLaunchIdentity(record.accepted.launch_identity, desiredMarker)) {
|
|
1881
|
+
await stage(desired, desiredMarker);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (heartbeatDue()) await stage(record.accepted.session, desiredMarker);
|
|
1885
|
+
}
|
|
1886
|
+
async function drain() {
|
|
1887
|
+
if (running || stopped || fenced) return;
|
|
1888
|
+
running = true;
|
|
1889
|
+
try {
|
|
1890
|
+
await drainOnce();
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
fenced = true;
|
|
1893
|
+
stopPump();
|
|
1894
|
+
try {
|
|
1895
|
+
note(`context storage failure: ${String(error).slice(0, 180)}`);
|
|
1896
|
+
} catch {
|
|
1897
|
+
}
|
|
1898
|
+
} finally {
|
|
1899
|
+
running = false;
|
|
1900
|
+
if (again && !stopped && !fenced) {
|
|
1901
|
+
again = false;
|
|
1902
|
+
kick();
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
function kick() {
|
|
1907
|
+
if (stopped || fenced) return;
|
|
1908
|
+
startPump();
|
|
1909
|
+
if (running) {
|
|
1910
|
+
again = true;
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
void drain().catch(() => void 0);
|
|
1914
|
+
}
|
|
1915
|
+
return {
|
|
1916
|
+
update(session, launchIdentity) {
|
|
1917
|
+
if (fenced || session === desired && sameLaunchIdentity(desiredMarker, launchIdentity))
|
|
1918
|
+
return;
|
|
1919
|
+
desired = session;
|
|
1920
|
+
desiredMarker = launchIdentity;
|
|
1921
|
+
if (stopped) return;
|
|
1922
|
+
kick();
|
|
1923
|
+
},
|
|
1924
|
+
stop() {
|
|
1925
|
+
stopped = true;
|
|
1926
|
+
stopPump();
|
|
1927
|
+
},
|
|
1928
|
+
reopen() {
|
|
1929
|
+
stopped = false;
|
|
1930
|
+
if (desired !== void 0 || record.pending !== void 0) kick();
|
|
1931
|
+
},
|
|
1932
|
+
diagnostic: () => record.diagnostic
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
var STORE_IDENTITY = /* @__PURE__ */ new WeakMap();
|
|
1936
|
+
function storeIdentity(store) {
|
|
1937
|
+
const known = STORE_IDENTITY.get(store);
|
|
1938
|
+
if (known !== void 0) return known;
|
|
1939
|
+
const created = randomUUID2();
|
|
1940
|
+
STORE_IDENTITY.set(store, created);
|
|
1941
|
+
return created;
|
|
1942
|
+
}
|
|
1943
|
+
function createRadarPublication(options) {
|
|
1944
|
+
try {
|
|
1945
|
+
return createRadarPublicationInner(options);
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
return unavailablePublication(error);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
function unavailablePublication(error) {
|
|
1951
|
+
const diagnostic = `Radar publication unavailable${error ? `: ${String(error).slice(0, 180)}` : ""}`;
|
|
1952
|
+
const writer = {
|
|
1953
|
+
update: () => void 0,
|
|
1954
|
+
retire: () => void 0,
|
|
1955
|
+
stop: () => void 0,
|
|
1956
|
+
reopen: () => void 0,
|
|
1957
|
+
diagnostic: () => diagnostic
|
|
1958
|
+
};
|
|
1959
|
+
return {
|
|
1960
|
+
execution: writer,
|
|
1961
|
+
context: {
|
|
1962
|
+
update: () => void 0,
|
|
1963
|
+
stop: () => void 0,
|
|
1964
|
+
reopen: () => void 0,
|
|
1965
|
+
diagnostic: () => diagnostic
|
|
1966
|
+
},
|
|
1967
|
+
facts: { ...writer, update: () => void 0 },
|
|
1968
|
+
assignment: () => writer,
|
|
1969
|
+
binding: () => void 0,
|
|
1970
|
+
subject: async () => void 0,
|
|
1971
|
+
retireAssignment: () => void 0,
|
|
1972
|
+
diagnostic: () => diagnostic,
|
|
1973
|
+
stop: () => void 0,
|
|
1974
|
+
reopen: () => void 0
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function createRadarPublicationInner(options) {
|
|
1978
|
+
const dataRoot = options.dataRoot;
|
|
1979
|
+
const store = options.store ?? createPublicationStore(dataRoot);
|
|
1980
|
+
const scheduler = options.scheduler ?? systemScheduler;
|
|
1981
|
+
const client = options.client;
|
|
1982
|
+
const spawnToken = options.spawnToken;
|
|
1983
|
+
const slot = options.slot ?? radarProcessSlot();
|
|
1984
|
+
const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
|
|
1985
|
+
const cacheKey = `${options.store === void 0 ? dataRoot : storeIdentity(options.store)}\0${incarnation}`;
|
|
1986
|
+
const cached = slot.publications.get(cacheKey);
|
|
1987
|
+
if (cached !== void 0) {
|
|
1988
|
+
cached.reopen();
|
|
1989
|
+
return cached;
|
|
1990
|
+
}
|
|
1991
|
+
const subjectName = `subjects/${createHash("sha256").update(`${options.registration.source}\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
1992
|
+
const channelRecord = (channel, target) => `channels/${createHash("sha256").update(`${channel}\0${incarnation}\0${target ?? ""}`).digest("hex").slice(0, 32)}.json`;
|
|
1993
|
+
const contextRecord = `context/${createHash("sha256").update(`context\0${incarnation}`).digest("hex").slice(0, 32)}.json`;
|
|
1994
|
+
function readSubject() {
|
|
1995
|
+
const value = store.read(subjectName);
|
|
1996
|
+
const registration = recordAt2(value, "registration");
|
|
1997
|
+
if (numberAt2(value, "version") !== RADAR_PUBLICATION_VERSION || textAt(registration, "source") !== options.registration.source || textAt(registration, "incarnation") !== incarnation)
|
|
1998
|
+
return void 0;
|
|
1999
|
+
const storedAgentId = textAt(value, "agent_id");
|
|
2000
|
+
return {
|
|
2001
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2002
|
+
registration,
|
|
2003
|
+
...storedAgentId !== void 0 && isUuid(storedAgentId) ? { agent_id: storedAgentId } : {}
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
const stored = readSubject();
|
|
2007
|
+
let subject = stored;
|
|
2008
|
+
let registrationDiagnostic;
|
|
2009
|
+
let registrationRecord;
|
|
2010
|
+
async function ensureRegistration() {
|
|
2011
|
+
if (subject !== void 0) return subject;
|
|
2012
|
+
registrationRecord ??= (async () => {
|
|
2013
|
+
const resolution = options.location ? await options.location() : { state: "absent", reason: "this process publishes no location" };
|
|
2014
|
+
if (resolution.state === "pending") {
|
|
2015
|
+
registrationDiagnostic = `location bootstrap pending: ${resolution.reason}`.slice(0, 256);
|
|
2016
|
+
return void 0;
|
|
2017
|
+
}
|
|
2018
|
+
const built = {
|
|
2019
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2020
|
+
registration: {
|
|
2021
|
+
...options.registration,
|
|
2022
|
+
...resolution.state === "resolved" ? { location: resolution.location } : {},
|
|
2023
|
+
incarnation,
|
|
2024
|
+
...options.birth !== void 0 ? { process: options.birth } : {}
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
try {
|
|
2028
|
+
store.write(subjectName, built);
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
registrationDiagnostic ??= `private storage unavailable: ${String(error)}`.slice(0, 256);
|
|
2031
|
+
return void 0;
|
|
2032
|
+
}
|
|
2033
|
+
registrationDiagnostic = void 0;
|
|
2034
|
+
subject = built;
|
|
2035
|
+
return built;
|
|
2036
|
+
})();
|
|
2037
|
+
try {
|
|
2038
|
+
return await registrationRecord;
|
|
2039
|
+
} finally {
|
|
2040
|
+
registrationRecord = void 0;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
let agentId = stored?.agent_id;
|
|
2044
|
+
let subjectReady;
|
|
2045
|
+
async function ensureSubject(recheck) {
|
|
2046
|
+
if (recheck) agentId = void 0;
|
|
2047
|
+
if (agentId !== void 0) return agentId;
|
|
2048
|
+
subjectReady ??= (async () => {
|
|
2049
|
+
const ready = await client.ping();
|
|
2050
|
+
if (ready.ok === false) {
|
|
2051
|
+
if (ready.code !== "absent" && ready.code !== "timeout" && ready.code !== "transport")
|
|
2052
|
+
registrationDiagnostic ??= `daemon negotiation failed: ${ready.message}`.slice(0, 256);
|
|
2053
|
+
return void 0;
|
|
2054
|
+
}
|
|
2055
|
+
const record = await ensureRegistration();
|
|
2056
|
+
if (record === void 0) return void 0;
|
|
2057
|
+
const answer = await client.register({
|
|
2058
|
+
...record.registration,
|
|
2059
|
+
...spawnToken !== void 0 ? { spawn_token: spawnToken } : {}
|
|
2060
|
+
});
|
|
2061
|
+
if (answer.ok === false) {
|
|
2062
|
+
if (answer.code !== "absent" && answer.code !== "timeout" && answer.code !== "transport")
|
|
2063
|
+
registrationDiagnostic ??= `subject registration refused: ${answer.message}`.slice(0, 256);
|
|
2064
|
+
return void 0;
|
|
2065
|
+
}
|
|
2066
|
+
agentId = answer.value.agent_id;
|
|
2067
|
+
subject = { ...record, agent_id: agentId };
|
|
2068
|
+
store.write(subjectName, subject);
|
|
2069
|
+
if (options.binding !== void 0) writeBinding(options.binding);
|
|
2070
|
+
return agentId;
|
|
2071
|
+
})();
|
|
2072
|
+
try {
|
|
2073
|
+
return await subjectReady;
|
|
2074
|
+
} finally {
|
|
2075
|
+
subjectReady = void 0;
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
function writeBinding(binding) {
|
|
2079
|
+
if (agentId === void 0) return;
|
|
2080
|
+
store.write(`bindings/${binding.key}.json`, {
|
|
2081
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2082
|
+
agent_id: agentId,
|
|
2083
|
+
incarnation,
|
|
2084
|
+
run: binding.run,
|
|
2085
|
+
owner: binding.owner,
|
|
2086
|
+
label: binding.label,
|
|
2087
|
+
observed_at: new Date(scheduler.now()).toISOString()
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
const publisher = (source, reportingOwner) => ({
|
|
2091
|
+
source,
|
|
2092
|
+
incarnation,
|
|
2093
|
+
...reportingOwner !== void 0 ? { reporting_owner: reportingOwner } : {}
|
|
2094
|
+
});
|
|
2095
|
+
const execution = createChannelWriter({
|
|
2096
|
+
store,
|
|
2097
|
+
client,
|
|
2098
|
+
scheduler,
|
|
2099
|
+
recordName: channelRecord("execution"),
|
|
2100
|
+
channel: "execution",
|
|
2101
|
+
publisher: publisher("herdsman-pi"),
|
|
2102
|
+
subject: ensureSubject,
|
|
2103
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2104
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2105
|
+
});
|
|
2106
|
+
const context = createContextWriter({
|
|
2107
|
+
store,
|
|
2108
|
+
client,
|
|
2109
|
+
scheduler,
|
|
2110
|
+
recordName: contextRecord,
|
|
2111
|
+
publisher: publisher("herdsman-pi"),
|
|
2112
|
+
subject: ensureSubject,
|
|
2113
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2114
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2115
|
+
});
|
|
2116
|
+
const facts = createChannelWriter({
|
|
2117
|
+
store,
|
|
2118
|
+
client,
|
|
2119
|
+
scheduler,
|
|
2120
|
+
recordName: channelRecord("facts"),
|
|
2121
|
+
channel: "facts",
|
|
2122
|
+
publisher: publisher("pi"),
|
|
2123
|
+
codec: factsCodec,
|
|
2124
|
+
subject: ensureSubject,
|
|
2125
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2126
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2127
|
+
});
|
|
2128
|
+
const assignmentWriters = /* @__PURE__ */ new Map();
|
|
2129
|
+
const publication = {
|
|
2130
|
+
execution,
|
|
2131
|
+
context,
|
|
2132
|
+
facts,
|
|
2133
|
+
assignment(target, reportingOwner) {
|
|
2134
|
+
const existing = assignmentWriters.get(target);
|
|
2135
|
+
if (existing !== void 0) return existing;
|
|
2136
|
+
try {
|
|
2137
|
+
const writer = createChannelWriter({
|
|
2138
|
+
store,
|
|
2139
|
+
client,
|
|
2140
|
+
scheduler,
|
|
2141
|
+
recordName: channelRecord("assignment", target),
|
|
2142
|
+
channel: "assignment",
|
|
2143
|
+
publisher: publisher("herdsman-owner", reportingOwner),
|
|
2144
|
+
codec: assignmentCodec,
|
|
2145
|
+
// The owner publishes about a subject it did not register: the exact
|
|
2146
|
+
// agent_id from the child's sidecar is the whole correlation.
|
|
2147
|
+
subject: async () => target,
|
|
2148
|
+
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2149
|
+
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2150
|
+
});
|
|
2151
|
+
assignmentWriters.set(target, writer);
|
|
2152
|
+
return writer;
|
|
2153
|
+
} catch (error) {
|
|
2154
|
+
registrationDiagnostic ??= `Radar assignment unavailable: ${String(error).slice(0, 180)}`;
|
|
2155
|
+
return unavailablePublication(error).execution;
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
binding(key) {
|
|
2159
|
+
let value;
|
|
2160
|
+
try {
|
|
2161
|
+
value = store.read(`bindings/${key}.json`);
|
|
2162
|
+
} catch (error) {
|
|
2163
|
+
registrationDiagnostic ??= `Radar binding unavailable: ${String(error).slice(0, 180)}`;
|
|
2164
|
+
return void 0;
|
|
2165
|
+
}
|
|
2166
|
+
const bound = textAt(value, "agent_id");
|
|
2167
|
+
const boundIncarnation = textAt(value, "incarnation");
|
|
2168
|
+
const run = textAt(value, "run");
|
|
2169
|
+
const owner = textAt(value, "owner");
|
|
2170
|
+
const label = textAt(value, "label");
|
|
2171
|
+
const observedAt = textAt(value, "observed_at");
|
|
2172
|
+
if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
|
|
2173
|
+
return void 0;
|
|
2174
|
+
return {
|
|
2175
|
+
version: RADAR_PUBLICATION_VERSION,
|
|
2176
|
+
agent_id: bound,
|
|
2177
|
+
incarnation: boundIncarnation,
|
|
2178
|
+
run,
|
|
2179
|
+
owner,
|
|
2180
|
+
label,
|
|
2181
|
+
observed_at: observedAt
|
|
2182
|
+
};
|
|
2183
|
+
},
|
|
2184
|
+
retireAssignment(targetAgentId) {
|
|
2185
|
+
assignmentWriters.get(targetAgentId)?.retire();
|
|
2186
|
+
},
|
|
2187
|
+
async subject() {
|
|
2188
|
+
const agentId2 = await ensureSubject(false);
|
|
2189
|
+
if (agentId2 === void 0) return void 0;
|
|
2190
|
+
const record = await ensureRegistration();
|
|
2191
|
+
if (record === void 0) return void 0;
|
|
2192
|
+
const location = record.registration.location;
|
|
2193
|
+
return {
|
|
2194
|
+
agent_id: agentId2,
|
|
2195
|
+
...location !== void 0 ? { location } : {}
|
|
2196
|
+
};
|
|
2197
|
+
},
|
|
2198
|
+
diagnostic: () => registrationDiagnostic ?? execution.diagnostic() ?? context.diagnostic() ?? facts.diagnostic(),
|
|
2199
|
+
stop() {
|
|
2200
|
+
for (const writer of assignmentWriters.values()) writer.stop();
|
|
2201
|
+
execution.stop();
|
|
2202
|
+
context.stop();
|
|
2203
|
+
facts.stop();
|
|
2204
|
+
},
|
|
2205
|
+
reopen() {
|
|
2206
|
+
for (const writer of assignmentWriters.values()) writer.reopen();
|
|
2207
|
+
execution.reopen();
|
|
2208
|
+
context.reopen();
|
|
2209
|
+
facts.reopen();
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
slot.publications.set(cacheKey, publication);
|
|
2213
|
+
return publication;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// extension/radar-execution.ts
|
|
2217
|
+
var RADAR_SUBJECT_SOURCE = "herdsman-pi";
|
|
2218
|
+
function dialogLabel(event) {
|
|
2219
|
+
if (typeof event !== "object" || event === null) return "";
|
|
2220
|
+
const label = event.label;
|
|
2221
|
+
return typeof label === "string" && label.trim() ? label.trim().slice(0, 256) : "";
|
|
2222
|
+
}
|
|
2223
|
+
function assistantStop(event) {
|
|
2224
|
+
const message = typeof event === "object" && event !== null && "message" in event ? event.message : event;
|
|
2225
|
+
if (typeof message !== "object" || message === null) return {};
|
|
2226
|
+
const value = message;
|
|
2227
|
+
return value.role === "assistant" ? { stopReason: value.stopReason } : {};
|
|
2228
|
+
}
|
|
2229
|
+
function unavailablePublication2() {
|
|
2230
|
+
const writer = {
|
|
2231
|
+
update: () => void 0,
|
|
2232
|
+
retire: () => void 0,
|
|
2233
|
+
stop: () => void 0,
|
|
2234
|
+
reopen: () => void 0,
|
|
2235
|
+
diagnostic: () => "Radar private storage is unavailable"
|
|
2236
|
+
};
|
|
2237
|
+
return {
|
|
2238
|
+
execution: writer,
|
|
2239
|
+
facts: { ...writer, update: () => void 0 },
|
|
2240
|
+
context: {
|
|
2241
|
+
update: () => void 0,
|
|
2242
|
+
stop: () => void 0,
|
|
2243
|
+
reopen: () => void 0,
|
|
2244
|
+
diagnostic: () => "Radar private storage is unavailable"
|
|
2245
|
+
},
|
|
2246
|
+
assignment: () => writer,
|
|
2247
|
+
binding: () => void 0,
|
|
2248
|
+
subject: async () => void 0,
|
|
2249
|
+
retireAssignment: () => void 0,
|
|
2250
|
+
diagnostic: writer.diagnostic,
|
|
2251
|
+
stop: () => void 0,
|
|
2252
|
+
reopen: () => void 0
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
function factsFrom(context) {
|
|
2256
|
+
const facts = {};
|
|
2257
|
+
const model = context?.model;
|
|
2258
|
+
if (typeof model?.id === "string" && model.id.length > 0)
|
|
2259
|
+
facts.model = model.id;
|
|
2260
|
+
if (typeof model?.provider === "string" && model.provider.length > 0)
|
|
2261
|
+
facts.provider = model.provider;
|
|
2262
|
+
const thinking = context?.thinkingLevel;
|
|
2263
|
+
if (typeof thinking === "string" && thinking.length > 0)
|
|
2264
|
+
facts.thinking = thinking;
|
|
2265
|
+
const name = context?.sessionManager?.getSessionName?.();
|
|
2266
|
+
if (typeof name === "string" && name.trim().length > 0)
|
|
2267
|
+
facts.session_name = name.trim();
|
|
2268
|
+
return facts;
|
|
2269
|
+
}
|
|
2270
|
+
function createRadarExecutionAdapter(pi, options) {
|
|
2271
|
+
const slot = options.slot ?? radarProcessSlot();
|
|
2272
|
+
const incarnation = options.incarnation ?? radarProcessIncarnation(slot);
|
|
2273
|
+
const managed = options.managedBinding;
|
|
2274
|
+
const birth = "birth" in options ? options.birth : readProcessBirth();
|
|
2275
|
+
let publication;
|
|
2276
|
+
try {
|
|
2277
|
+
publication = options.publication ?? createRadarPublication({
|
|
2278
|
+
dataRoot: options.dataRoot,
|
|
2279
|
+
store: options.store,
|
|
2280
|
+
client: options.client,
|
|
2281
|
+
scheduler: options.scheduler,
|
|
2282
|
+
slot,
|
|
2283
|
+
incarnation,
|
|
2284
|
+
birth,
|
|
2285
|
+
registration: {
|
|
2286
|
+
source: RADAR_SUBJECT_SOURCE,
|
|
2287
|
+
...managed !== void 0 ? { owner: managed.owner, run: managed.run, label: managed.label } : {}
|
|
2288
|
+
},
|
|
2289
|
+
...options.location !== void 0 ? { location: options.location } : {},
|
|
2290
|
+
...managed !== void 0 ? { binding: managed } : {},
|
|
2291
|
+
...options.spawnToken !== void 0 ? { spawnToken: options.spawnToken } : {}
|
|
2292
|
+
});
|
|
2293
|
+
} catch {
|
|
2294
|
+
publication = unavailablePublication2();
|
|
2295
|
+
}
|
|
2296
|
+
const disposers = [];
|
|
2297
|
+
const keep = (disposer) => {
|
|
2298
|
+
if (typeof disposer === "function")
|
|
2299
|
+
disposers.push(disposer);
|
|
2300
|
+
};
|
|
2301
|
+
const on = (name, handler) => {
|
|
2302
|
+
keep(
|
|
2303
|
+
pi.on(name, (event, context) => {
|
|
2304
|
+
if (!stopped) handler(event, context);
|
|
2305
|
+
})
|
|
2306
|
+
);
|
|
2307
|
+
};
|
|
2308
|
+
let stopped = false;
|
|
2309
|
+
let rootSession = false;
|
|
2310
|
+
let active = false;
|
|
2311
|
+
let questionnaire;
|
|
2312
|
+
const permissionPrompts = /* @__PURE__ */ new Set();
|
|
2313
|
+
let lastOutcome;
|
|
2314
|
+
let lastPublished;
|
|
2315
|
+
function blocked() {
|
|
2316
|
+
return questionnaire !== void 0 || permissionPrompts.size > 0;
|
|
2317
|
+
}
|
|
2318
|
+
function currentSnapshot() {
|
|
2319
|
+
const reason = questionnaire ?? (permissionPrompts.size > 0 ? "permission" : void 0);
|
|
2320
|
+
return {
|
|
2321
|
+
activity: blocked() ? "blocked" : active ? "working" : "idle",
|
|
2322
|
+
...reason !== void 0 ? { waiting_reason: reason } : {},
|
|
2323
|
+
...lastOutcome !== void 0 ? { last_outcome: lastOutcome } : {}
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
function reportSession(context, launchIdentity) {
|
|
2327
|
+
const session = context?.sessionManager?.getSessionId?.();
|
|
2328
|
+
if (!isUuid(session)) return;
|
|
2329
|
+
try {
|
|
2330
|
+
publication.context.update(session, launchIdentity);
|
|
2331
|
+
} catch {
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
let lastFacts;
|
|
2335
|
+
function publishFacts(context) {
|
|
2336
|
+
if (stopped || !rootSession) return;
|
|
2337
|
+
const facts = factsFrom(context);
|
|
2338
|
+
const encoded = JSON.stringify(facts);
|
|
2339
|
+
if (encoded === lastFacts) return;
|
|
2340
|
+
if (lastFacts === void 0 && encoded === "{}") return;
|
|
2341
|
+
lastFacts = encoded;
|
|
2342
|
+
try {
|
|
2343
|
+
publication.facts.update(facts);
|
|
2344
|
+
} catch {
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
for (const name of [
|
|
2348
|
+
"model_select",
|
|
2349
|
+
"thinking_level_select",
|
|
2350
|
+
"session_info_changed",
|
|
2351
|
+
"turn_end"
|
|
2352
|
+
]) {
|
|
2353
|
+
on(name, (_event, context) => publishFacts(context));
|
|
2354
|
+
}
|
|
2355
|
+
function emit() {
|
|
2356
|
+
if (stopped || !rootSession) return;
|
|
2357
|
+
const snapshot = boundedRadarSnapshot(currentSnapshot());
|
|
2358
|
+
const encoded = JSON.stringify(snapshot);
|
|
2359
|
+
if (encoded === lastPublished) return;
|
|
2360
|
+
lastPublished = encoded;
|
|
2361
|
+
try {
|
|
2362
|
+
publication.execution.update(snapshot);
|
|
2363
|
+
} catch {
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
keep(
|
|
2367
|
+
pi.events?.on("permissions:ui_prompt", (event) => {
|
|
2368
|
+
const requestId = event?.requestId;
|
|
2369
|
+
if (!rootSession || typeof requestId !== "string") return;
|
|
2370
|
+
permissionPrompts.add(requestId);
|
|
2371
|
+
emit();
|
|
2372
|
+
})
|
|
2373
|
+
);
|
|
2374
|
+
keep(
|
|
2375
|
+
pi.events?.on("permissions:decision", (event) => {
|
|
2376
|
+
const requestId = event?.requestId;
|
|
2377
|
+
if (typeof requestId !== "string" || !permissionPrompts.delete(requestId))
|
|
2378
|
+
return;
|
|
2379
|
+
emit();
|
|
2380
|
+
})
|
|
2381
|
+
);
|
|
2382
|
+
if (options.questionnaireEvent !== void 0)
|
|
2383
|
+
keep(
|
|
2384
|
+
pi.events?.on(options.questionnaireEvent, (event) => {
|
|
2385
|
+
if (!rootSession || typeof event !== "object" || event === null)
|
|
2386
|
+
return;
|
|
2387
|
+
const value = event;
|
|
2388
|
+
if (typeof value.active !== "boolean") return;
|
|
2389
|
+
questionnaire = value.active ? dialogLabel(event) || "questionnaire" : void 0;
|
|
2390
|
+
emit();
|
|
2391
|
+
})
|
|
2392
|
+
);
|
|
2393
|
+
on("session_start", (_event, context) => {
|
|
2394
|
+
const sessionHost = {
|
|
2395
|
+
appendEntry: pi.appendEntry ? (customType, data) => pi.appendEntry?.(customType, data) : void 0,
|
|
2396
|
+
sessionManager: context?.sessionManager
|
|
2397
|
+
};
|
|
2398
|
+
let launchIdentity;
|
|
2399
|
+
try {
|
|
2400
|
+
const result = persistRadarLaunchMarker(
|
|
2401
|
+
sessionHost,
|
|
2402
|
+
options.markerEnvironment
|
|
2403
|
+
);
|
|
2404
|
+
if (result === "written" || result === "unchanged") {
|
|
2405
|
+
const expected = radarLaunchMarkerFromEnvironment(options.markerEnvironment);
|
|
2406
|
+
const activeMarker = activeRadarLaunchMarker(sessionHost);
|
|
2407
|
+
if (expected && activeMarker.status === "valid" && JSON.stringify(activeMarker.marker) === JSON.stringify(expected)) {
|
|
2408
|
+
launchIdentity = activeMarker.marker;
|
|
2409
|
+
} else {
|
|
2410
|
+
options.diagnostic?.("Radar launch marker was not visible on the active Pi session branch");
|
|
2411
|
+
}
|
|
2412
|
+
} else if (result === "unsupported") {
|
|
2413
|
+
options.diagnostic?.("Pi host does not support Radar launch identity session entries");
|
|
2414
|
+
} else if (options.markerEnvironment && [
|
|
2415
|
+
"PI_RADAR_MANAGED_SUBJECT_ID",
|
|
2416
|
+
"PI_RADAR_MANAGED_LAUNCH_ID",
|
|
2417
|
+
"PI_RADAR_MANAGED_EXECUTION_GENERATION"
|
|
2418
|
+
].some((key) => options.markerEnvironment?.[key] !== void 0)) {
|
|
2419
|
+
options.diagnostic?.("incomplete or invalid Radar launch identity environment");
|
|
2420
|
+
}
|
|
2421
|
+
} catch (error) {
|
|
2422
|
+
options.diagnostic?.(`could not persist Radar launch marker: ${String(error)}`);
|
|
2423
|
+
}
|
|
2424
|
+
rootSession = true;
|
|
2425
|
+
reportSession(context, launchIdentity);
|
|
2426
|
+
publishFacts(context);
|
|
2427
|
+
publication.reopen();
|
|
2428
|
+
active = context?.isIdle?.() === false;
|
|
2429
|
+
emit();
|
|
2430
|
+
});
|
|
2431
|
+
on("agent_start", () => {
|
|
2432
|
+
if (!rootSession) return;
|
|
2433
|
+
active = true;
|
|
2434
|
+
emit();
|
|
2435
|
+
});
|
|
2436
|
+
on("agent_settled", (event, context) => {
|
|
2437
|
+
if (!rootSession) return;
|
|
2438
|
+
if (event?.aborted === true) lastOutcome = { result: "aborted" };
|
|
2439
|
+
if (context?.isIdle?.() === true) active = false;
|
|
2440
|
+
emit();
|
|
2441
|
+
});
|
|
2442
|
+
on("message_end", (event) => {
|
|
2443
|
+
if (!rootSession) return;
|
|
2444
|
+
const { stopReason } = assistantStop(event);
|
|
2445
|
+
if (stopReason === "error") {
|
|
2446
|
+
lastOutcome = { result: "error" };
|
|
2447
|
+
} else if (stopReason === "aborted") {
|
|
2448
|
+
lastOutcome = { result: "aborted" };
|
|
2449
|
+
} else if (stopReason === "stop") {
|
|
2450
|
+
lastOutcome = { result: "finished" };
|
|
2451
|
+
} else {
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
emit();
|
|
2455
|
+
});
|
|
2456
|
+
on("session_shutdown", () => {
|
|
2457
|
+
suspend();
|
|
2458
|
+
});
|
|
2459
|
+
function suspend() {
|
|
2460
|
+
if (stopped) return;
|
|
2461
|
+
rootSession = false;
|
|
2462
|
+
active = false;
|
|
2463
|
+
questionnaire = void 0;
|
|
2464
|
+
permissionPrompts.clear();
|
|
2465
|
+
lastOutcome = void 0;
|
|
2466
|
+
lastPublished = void 0;
|
|
2467
|
+
publication.stop();
|
|
2468
|
+
}
|
|
2469
|
+
function stop() {
|
|
2470
|
+
if (stopped) return;
|
|
2471
|
+
suspend();
|
|
2472
|
+
stopped = true;
|
|
2473
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
2474
|
+
}
|
|
2475
|
+
return {
|
|
2476
|
+
publication,
|
|
2477
|
+
binding: () => managed !== void 0 ? publication.binding(managed.key) : void 0,
|
|
2478
|
+
blocked,
|
|
2479
|
+
stop
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
// extension/radar-location.ts
|
|
2484
|
+
import { basename } from "node:path";
|
|
2485
|
+
function radarLocalPane(env) {
|
|
2486
|
+
const pane = env.TMUX_PANE?.trim();
|
|
2487
|
+
return pane && /^%\d+$/.test(pane) ? pane : void 0;
|
|
2488
|
+
}
|
|
2489
|
+
function tmuxServerName(env) {
|
|
2490
|
+
const socket = env.TMUX?.trim().split(",")[0];
|
|
2491
|
+
return socket ? basename(socket) || void 0 : void 0;
|
|
2492
|
+
}
|
|
2493
|
+
function qualifiedOwnPane(env, backend, instance) {
|
|
2494
|
+
if (backend !== "tmux")
|
|
2495
|
+
return {
|
|
2496
|
+
absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
|
|
2497
|
+
};
|
|
2498
|
+
if (instance === void 0 || instance === "")
|
|
2499
|
+
return {
|
|
2500
|
+
pending: "the daemon did not name the tmux server it addresses"
|
|
2501
|
+
};
|
|
2502
|
+
const server = tmuxServerName(env);
|
|
2503
|
+
if (server === void 0)
|
|
2504
|
+
return {
|
|
2505
|
+
absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
|
|
2506
|
+
};
|
|
2507
|
+
if (server !== instance)
|
|
2508
|
+
return {
|
|
2509
|
+
absent: `this process is attached to tmux server ${server}, not to ${instance}`
|
|
2510
|
+
};
|
|
2511
|
+
const pane = radarLocalPane(env);
|
|
2512
|
+
if (pane === void 0)
|
|
2513
|
+
return {
|
|
2514
|
+
absent: "this process's own environment names no tmux pane of that server"
|
|
2515
|
+
};
|
|
2516
|
+
return { pane: `tmux:${instance}:${pane}` };
|
|
2517
|
+
}
|
|
2518
|
+
function observedContainment(inventory, paneId) {
|
|
2519
|
+
const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
|
|
2520
|
+
if (rows.length === 0) return void 0;
|
|
2521
|
+
if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
|
|
2522
|
+
const row = rows[0];
|
|
2523
|
+
const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
|
|
2524
|
+
if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
|
|
2525
|
+
return {
|
|
2526
|
+
ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
|
|
2527
|
+
};
|
|
2528
|
+
return { pane: row };
|
|
2529
|
+
}
|
|
2530
|
+
async function resolveRadarProcessLocation(client, env = process.env) {
|
|
2531
|
+
let ready;
|
|
2532
|
+
try {
|
|
2533
|
+
ready = await client.ping();
|
|
2534
|
+
} catch {
|
|
2535
|
+
return { state: "pending", reason: "the daemon did not answer this process" };
|
|
2536
|
+
}
|
|
2537
|
+
if (ready.ok === false)
|
|
2538
|
+
return {
|
|
2539
|
+
state: "pending",
|
|
2540
|
+
reason: `the daemon did not answer this process (${ready.code})`
|
|
2541
|
+
};
|
|
2542
|
+
const backend = ready.value.backend;
|
|
2543
|
+
if (backend === void 0 || backend === "none")
|
|
2544
|
+
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
2545
|
+
const instance = ready.value.instance ?? void 0;
|
|
2546
|
+
const own = qualifiedOwnPane(env, backend, instance);
|
|
2547
|
+
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
2548
|
+
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
2549
|
+
const paneId = own.pane;
|
|
2550
|
+
if (paneId === env.PI_HERDSMAN_PARENT_PANE_ID?.trim())
|
|
2551
|
+
return {
|
|
2552
|
+
state: "absent",
|
|
2553
|
+
reason: "this process is in the pane its launch was created beside, not in a pane of its own"
|
|
2554
|
+
};
|
|
2555
|
+
let inventory;
|
|
2556
|
+
try {
|
|
2557
|
+
inventory = await client.observe();
|
|
2558
|
+
} catch {
|
|
2559
|
+
return {
|
|
2560
|
+
state: "pending",
|
|
2561
|
+
reason: "the daemon's topology could not be read for this process's pane"
|
|
2562
|
+
};
|
|
2563
|
+
}
|
|
2564
|
+
if (inventory.ok === false)
|
|
2565
|
+
return {
|
|
2566
|
+
state: "pending",
|
|
2567
|
+
reason: `the daemon's topology could not be read (${inventory.code})`
|
|
2568
|
+
};
|
|
2569
|
+
const containment = observedContainment(inventory.value, paneId);
|
|
2570
|
+
if (containment === void 0)
|
|
2571
|
+
return {
|
|
2572
|
+
state: "pending",
|
|
2573
|
+
reason: `the daemon's topology does not report pane ${paneId} yet`
|
|
2574
|
+
};
|
|
2575
|
+
if ("ambiguous" in containment)
|
|
2576
|
+
return { state: "pending", reason: containment.ambiguous };
|
|
2577
|
+
const row = containment.pane;
|
|
2578
|
+
return {
|
|
2579
|
+
state: "resolved",
|
|
2580
|
+
location: {
|
|
2581
|
+
backend,
|
|
2582
|
+
...instance !== void 0 ? { instance } : {},
|
|
2583
|
+
workspace: row.workspace_id,
|
|
2584
|
+
tab: row.tab_id,
|
|
2585
|
+
pane: paneId
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// extension/radar-runtime.ts
|
|
2591
|
+
var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
|
|
2592
|
+
var RADAR_RUNTIME_VERSION = 1;
|
|
2593
|
+
function lazyRadarRuntime(start) {
|
|
2594
|
+
let pending;
|
|
2595
|
+
return {
|
|
2596
|
+
version: RADAR_RUNTIME_VERSION,
|
|
2597
|
+
start: () => pending ??= Promise.resolve().then(start)
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
function provideRadarRuntime(events, handle) {
|
|
2601
|
+
return events.on(RADAR_RUNTIME_REQUEST, (value) => {
|
|
2602
|
+
if (!isRuntimeRequest(value)) return;
|
|
2603
|
+
value.provide(handle);
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
function isRuntimeRequest(value) {
|
|
2607
|
+
return typeof value === "object" && value !== null && value.version === RADAR_RUNTIME_VERSION && typeof value.provide === "function";
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// extension/baseline.ts
|
|
2611
|
+
function registerPiRadar(pi) {
|
|
2612
|
+
const client = createRadarClient();
|
|
2613
|
+
const execution = createRadarExecutionAdapter(pi, {
|
|
2614
|
+
client,
|
|
2615
|
+
dataRoot: join3(homedir(), ".pi", "agent", "pi-radar"),
|
|
2616
|
+
location: () => resolveRadarProcessLocation(client)
|
|
2617
|
+
});
|
|
2618
|
+
const runtime = lazyRadarRuntime(async () => ({
|
|
2619
|
+
publication: execution.publication,
|
|
2620
|
+
execution
|
|
2621
|
+
}));
|
|
2622
|
+
provideRadarRuntime(pi.events, runtime);
|
|
2623
|
+
}
|
|
2624
|
+
export {
|
|
2625
|
+
registerPiRadar as default
|
|
2626
|
+
};
|