pi-radar 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -10
- package/dist/extension/baseline.js +343 -361
- package/dist/extension/radar-children.js +108 -218
- package/dist/extension/radar-client.js +103 -15
- package/dist/extension/radar-execution.js +26 -17
- package/dist/extension/radar-inspection.js +0 -45
- package/dist/extension/radar-launch.js +24 -185
- package/dist/extension/radar-publication.js +26 -17
- package/dist/extension/radar-runtime.js +1 -1
- package/dist/index.js +317 -488
- package/extension/generated-contracts.ts +362 -13
- package/extension/radar-children.ts +60 -155
- package/extension/radar-client.ts +130 -19
- package/extension/radar-inspection.ts +2 -77
- package/extension/radar-launch.ts +52 -391
- package/extension/radar-publication.ts +36 -53
- package/extension/radar-runtime.ts +1 -1
- package/package.json +1 -1
- package/schema/public/v1/observation-get-response.deserialize.schema.json +5 -25
- package/schema/public/v1/observation-get-response.serialize.schema.json +7 -29
- package/schema/public/v1/observation-snapshot.deserialize.schema.json +5 -25
- package/schema/public/v1/observation-snapshot.serialize.schema.json +7 -29
- package/schema/public/v1/spawn-list-page.deserialize.schema.json +40 -17
- package/schema/public/v1/spawn-list-page.serialize.schema.json +40 -17
- package/schema/public/v1/subject-assignment-acquire.deserialize.schema.json +78 -0
- package/schema/public/v1/subject-assignment-acquire.serialize.schema.json +79 -0
- package/schema/public/v1/subject-assignment-publish.deserialize.schema.json +310 -0
- package/schema/public/v1/subject-assignment-publish.serialize.schema.json +310 -0
- package/schema/public/v1/subject-assignment-retire.deserialize.schema.json +22 -0
- package/schema/public/v1/subject-assignment-retire.serialize.schema.json +22 -0
- package/schema/public/v1/subject-child-get-params.deserialize.schema.json +18 -0
- package/schema/public/v1/subject-child-get-params.serialize.schema.json +18 -0
- package/schema/public/v1/subject-children-params.deserialize.schema.json +51 -0
- package/schema/public/v1/subject-children-params.serialize.schema.json +54 -0
- package/schema/public/v1/subject-get-params.deserialize.schema.json +14 -0
- package/schema/public/v1/subject-get-params.serialize.schema.json +14 -0
- package/schema/public/v1/subject-get-result.deserialize.schema.json +566 -0
- package/schema/public/v1/subject-get-result.serialize.schema.json +573 -0
- package/schema/public/v1/subject-list-params.deserialize.schema.json +38 -0
- package/schema/public/v1/subject-list-params.serialize.schema.json +43 -0
- package/schema/public/v1/subject-list-result.deserialize.schema.json +575 -0
- package/schema/public/v1/subject-list-result.serialize.schema.json +583 -0
|
@@ -8,11 +8,10 @@ import { lstatSync } from "node:fs";
|
|
|
8
8
|
import { createConnection } from "node:net";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
var RADAR_PROTOCOL_VERSION = 2;
|
|
11
|
-
var RADAR_MINIMUM_REVISION =
|
|
11
|
+
var RADAR_MINIMUM_REVISION = 9;
|
|
12
12
|
var RADAR_REGISTRY_CONTRACT = "registry.v1";
|
|
13
13
|
var RADAR_REGISTRY_FACTS_CONTRACT = "registry.facts.v1";
|
|
14
14
|
var RADAR_SPAWN_CONTRACT = "spawn.v1";
|
|
15
|
-
var RADAR_SPAWN_LIST_CONTRACT = "spawn.list.v1";
|
|
16
15
|
var RADAR_SPAWN_CLOSE_CONTRACT = "spawn.close.v1";
|
|
17
16
|
var RADAR_LIFECYCLE_RESTART_CONTRACT = "lifecycle.restart.v1";
|
|
18
17
|
var RADAR_SPAWN_LAUNCH_CONTEXT_CONTRACT = "spawn.launch_context.v1";
|
|
@@ -318,6 +317,9 @@ function spawnEdge(value) {
|
|
|
318
317
|
const agentId = stringAt(bound, "registration_id");
|
|
319
318
|
const subjectId = stringAt(bound, "subject_id");
|
|
320
319
|
const exit = exitAt(recordAt(value, "exit_observation"));
|
|
320
|
+
const withdrawnRecord = recordAt(value, "withdrawn");
|
|
321
|
+
const withdrawnAt = stringAt(withdrawnRecord, "at");
|
|
322
|
+
const withdrawnReason = stringAt(withdrawnRecord, "reason");
|
|
321
323
|
return {
|
|
322
324
|
ok: true,
|
|
323
325
|
value: {
|
|
@@ -334,7 +336,9 @@ function spawnEdge(value) {
|
|
|
334
336
|
...isUuid(subjectId) ? { subject_id: subjectId } : {}
|
|
335
337
|
}
|
|
336
338
|
} : {},
|
|
337
|
-
...exit !== void 0 ? { exit_observation: exit } : {}
|
|
339
|
+
...exit !== void 0 ? { exit_observation: exit } : {},
|
|
340
|
+
...stringAt(value, "granted_at") !== void 0 ? { granted_at: stringAt(value, "granted_at") } : {},
|
|
341
|
+
...withdrawnAt !== void 0 && withdrawnReason !== void 0 ? { withdrawn: { at: withdrawnAt, reason: withdrawnReason } } : {}
|
|
338
342
|
}
|
|
339
343
|
};
|
|
340
344
|
}
|
|
@@ -344,6 +348,14 @@ var PROCESS_VERDICTS = [
|
|
|
344
348
|
"mismatched",
|
|
345
349
|
"unavailable"
|
|
346
350
|
];
|
|
351
|
+
function subjectAt(value) {
|
|
352
|
+
const subjectId = recordAt(value, "subject_id");
|
|
353
|
+
const session = recordAt(value, "session_id");
|
|
354
|
+
const state = recordAt(value, "state");
|
|
355
|
+
if (!isUuid(subjectId) || session != null && !isUuid(session) || !["starting", "live", "detached", "unknown"].includes(String(state)))
|
|
356
|
+
return protocol("subject read carries no valid identity or attachment state");
|
|
357
|
+
return { ok: true, value };
|
|
358
|
+
}
|
|
347
359
|
function agentProcessAt(agent) {
|
|
348
360
|
const process2 = recordAt(agent, "process");
|
|
349
361
|
const claimed = recordAt(process2, "claimed");
|
|
@@ -678,7 +690,9 @@ function createRadarClient(options = {}) {
|
|
|
678
690
|
return withRegistry(async () => {
|
|
679
691
|
const answer = await call("agent.presence", { ...request });
|
|
680
692
|
if (!answer.ok) return answer;
|
|
681
|
-
|
|
693
|
+
const decoded = decodePresenceAnswer(answer.value);
|
|
694
|
+
if (decoded.ok) processRegistrationId = decoded.value.registration_id;
|
|
695
|
+
return decoded;
|
|
682
696
|
});
|
|
683
697
|
},
|
|
684
698
|
async releasePresence(request) {
|
|
@@ -713,9 +727,13 @@ function createRadarClient(options = {}) {
|
|
|
713
727
|
},
|
|
714
728
|
async acquire(request) {
|
|
715
729
|
return withRegistryChannel(request.channel, async () => {
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
730
|
+
const assignment = request.channel === "assignment";
|
|
731
|
+
if (assignment && processRegistrationId === void 0)
|
|
732
|
+
return { ok: false, code: "refused", message: "assignment requires a granted parent" };
|
|
733
|
+
if (assignment && request.subject_id === void 0)
|
|
734
|
+
return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
|
|
735
|
+
const answer = await call(assignment ? "subject.assignment.acquire" : "agent.acquire", {
|
|
736
|
+
...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
|
|
719
737
|
publisher: request.publisher,
|
|
720
738
|
...request.replace !== void 0 ? { replace: request.replace } : {}
|
|
721
739
|
});
|
|
@@ -742,15 +760,19 @@ function createRadarClient(options = {}) {
|
|
|
742
760
|
}
|
|
743
761
|
}
|
|
744
762
|
return withRegistryChannel(request.channel, async () => {
|
|
745
|
-
const
|
|
746
|
-
registration_id: request.registration_id,
|
|
747
|
-
channel: request.channel,
|
|
763
|
+
const update = {
|
|
748
764
|
writer_handle: request.writer_handle,
|
|
749
765
|
sequence: request.sequence,
|
|
750
766
|
...request.lease_ms !== void 0 ? { lease_ms: request.lease_ms } : {},
|
|
751
767
|
...request.observed_at !== void 0 ? { observed_at: request.observed_at } : {},
|
|
752
768
|
snapshot
|
|
753
|
-
}
|
|
769
|
+
};
|
|
770
|
+
const assignment = request.channel === "assignment";
|
|
771
|
+
if (assignment && processRegistrationId === void 0)
|
|
772
|
+
return { ok: false, code: "refused", message: "assignment requires a granted parent" };
|
|
773
|
+
if (assignment && request.subject_id === void 0)
|
|
774
|
+
return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
|
|
775
|
+
const answer = await call(assignment ? "subject.assignment.publish" : "agent.publish", assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id, update } : { registration_id: request.registration_id, channel: request.channel, ...update });
|
|
754
776
|
if (answer.ok === false) return answer;
|
|
755
777
|
const channel = answer.value.channel;
|
|
756
778
|
const writer = channel?.writer;
|
|
@@ -791,7 +813,15 @@ function createRadarClient(options = {}) {
|
|
|
791
813
|
},
|
|
792
814
|
async retire(request) {
|
|
793
815
|
return withRegistryChannel(request.channel, async () => {
|
|
794
|
-
const
|
|
816
|
+
const assignment = request.channel === "assignment";
|
|
817
|
+
if (assignment && processRegistrationId === void 0)
|
|
818
|
+
return { ok: false, code: "refused", message: "assignment requires a granted parent" };
|
|
819
|
+
if (assignment && request.subject_id === void 0)
|
|
820
|
+
return { ok: false, code: "bad_params", message: "an assignment names a child subject" };
|
|
821
|
+
const answer = await call(assignment ? "subject.assignment.retire" : "agent.retire", {
|
|
822
|
+
...assignment ? { registration_id: processRegistrationId, subject_id: request.subject_id } : { registration_id: request.registration_id, channel: request.channel },
|
|
823
|
+
writer_handle: request.writer_handle
|
|
824
|
+
});
|
|
795
825
|
return answer.ok === true ? { ok: true, value: void 0 } : answer;
|
|
796
826
|
});
|
|
797
827
|
},
|
|
@@ -866,14 +896,73 @@ function createRadarClient(options = {}) {
|
|
|
866
896
|
request.request_id
|
|
867
897
|
);
|
|
868
898
|
if (answer.ok === false) return answer;
|
|
869
|
-
|
|
899
|
+
const settled = focusOutcome(recordAt(answer.value, "request"), request.request_id);
|
|
900
|
+
if (settled.ok === false) return settled;
|
|
901
|
+
const launch = recordAt(recordAt(answer.value, "request"), "launch_request_id");
|
|
902
|
+
if (!isUuid(launch)) return protocol("lifecycle.restart reply names no launch");
|
|
903
|
+
return { ok: true, value: { ...settled.value, launch_request_id: launch } };
|
|
904
|
+
});
|
|
905
|
+
},
|
|
906
|
+
async subjectChildren(request = {}) {
|
|
907
|
+
const registrationId = processRegistrationId;
|
|
908
|
+
if (registrationId === void 0)
|
|
909
|
+
return { ok: false, code: "refused", message: "this process has no Radar registration" };
|
|
910
|
+
return withRegistry(async () => {
|
|
911
|
+
const params = { ...request, registration_id: registrationId };
|
|
912
|
+
const answer = await call("subject.children", { ...params });
|
|
913
|
+
if (!answer.ok) return answer;
|
|
914
|
+
const subjects = recordAt(answer.value, "subjects");
|
|
915
|
+
const next = recordAt(answer.value, "next");
|
|
916
|
+
if (!Array.isArray(subjects) || next != null && !isUuid(next))
|
|
917
|
+
return protocol("subject.children reply carries no valid page");
|
|
918
|
+
for (const subject of subjects) {
|
|
919
|
+
const decoded = subjectAt(subject);
|
|
920
|
+
if (!decoded.ok) return decoded;
|
|
921
|
+
}
|
|
922
|
+
return { ok: true, value: answer.value };
|
|
923
|
+
});
|
|
924
|
+
},
|
|
925
|
+
async childGet(subject_id) {
|
|
926
|
+
const registrationId = processRegistrationId;
|
|
927
|
+
if (registrationId === void 0)
|
|
928
|
+
return { ok: false, code: "refused", message: "this process has no Radar registration" };
|
|
929
|
+
if (!isUuid(subject_id))
|
|
930
|
+
return { ok: false, code: "bad_params", message: "child requires a subject UUID" };
|
|
931
|
+
return withRegistry(async () => {
|
|
932
|
+
const params = { registration_id: registrationId, subject_id };
|
|
933
|
+
const answer = await call("subject.child.get", { ...params });
|
|
934
|
+
return answer.ok ? subjectAt(recordAt(answer.value, "subject")) : answer;
|
|
935
|
+
});
|
|
936
|
+
},
|
|
937
|
+
async childInspect(subject_id) {
|
|
938
|
+
const registration_id = processRegistrationId;
|
|
939
|
+
if (registration_id === void 0)
|
|
940
|
+
return { ok: false, code: "refused", message: "this process has no Radar registration" };
|
|
941
|
+
return withRegistry(async () => {
|
|
942
|
+
const answer = await call("subject.child.inspect", { registration_id, subject_id });
|
|
943
|
+
if (!answer.ok) return answer;
|
|
944
|
+
const subject = subjectAt(recordAt(answer.value, "subject"));
|
|
945
|
+
if (!subject.ok) return subject;
|
|
946
|
+
const output = recordAt(answer.value, "output");
|
|
947
|
+
const text = stringAt(output, "text");
|
|
948
|
+
if (text === void 0 || typeof recordAt(output, "truncated") !== "boolean")
|
|
949
|
+
return protocol("child inspection has no output evidence");
|
|
950
|
+
const foreground = recordAt(answer.value, "foreground");
|
|
951
|
+
const activity = subject.value.execution?.snapshot?.snapshot;
|
|
952
|
+
return { ok: true, value: {
|
|
953
|
+
capturedAt: Date.now(),
|
|
954
|
+
recentOutputTruncated: recordAt(output, "truncated"),
|
|
955
|
+
...text ? { recentOutput: text } : {},
|
|
956
|
+
...foreground ? { foreground } : {},
|
|
957
|
+
...activity && "activity" in activity ? { activity: activity.activity } : {}
|
|
958
|
+
} };
|
|
870
959
|
});
|
|
871
960
|
},
|
|
872
961
|
async spawnList(request) {
|
|
873
962
|
const registrationId = processRegistrationId;
|
|
874
963
|
if (registrationId === void 0)
|
|
875
964
|
return { ok: false, code: "refused", message: "spawn.list requires this client's process registration" };
|
|
876
|
-
return
|
|
965
|
+
return withRegistry(async () => {
|
|
877
966
|
const params = {
|
|
878
967
|
registration_id: registrationId,
|
|
879
968
|
...request.after !== void 0 ? { after: request.after } : {},
|
|
@@ -1236,344 +1325,121 @@ function boundedRadarSnapshot(snapshot) {
|
|
|
1236
1325
|
};
|
|
1237
1326
|
}
|
|
1238
1327
|
|
|
1239
|
-
// extension/radar-
|
|
1240
|
-
|
|
1241
|
-
var INSPECTION_OUTPUT_BYTES = 16 * 1024;
|
|
1242
|
-
async function provedRead(client, target) {
|
|
1243
|
-
const read = await client.agentGet(target.agentId);
|
|
1244
|
-
return read.ok === true && read.value.verification === "verified" && read.value.location?.pane === target.paneId && (target.session === void 0 || read.value.contextSession === target.session) ? read.value : void 0;
|
|
1245
|
-
}
|
|
1246
|
-
async function inspectRadarChild(client, target) {
|
|
1247
|
-
const before = await provedRead(client, target);
|
|
1248
|
-
if (before === void 0)
|
|
1249
|
-
throw new Error("Inspection target identity did not match");
|
|
1250
|
-
const output = await client.output({
|
|
1251
|
-
pane_id: target.paneId,
|
|
1252
|
-
source: "recent_unwrapped",
|
|
1253
|
-
lines: INSPECTION_LINES
|
|
1254
|
-
});
|
|
1255
|
-
if (output.ok === false)
|
|
1256
|
-
throw new Error(
|
|
1257
|
-
`Inspection output unavailable (${output.code}: ${output.message})`
|
|
1258
|
-
);
|
|
1259
|
-
const foreground = await client.processInfo({ pane_id: target.paneId });
|
|
1260
|
-
const after = await provedRead(client, target);
|
|
1261
|
-
if (after === void 0)
|
|
1262
|
-
throw new Error("Inspection target changed during capture");
|
|
1263
|
-
const lines = output.value.text.trim().split(/\r?\n/).slice(-INSPECTION_LINES);
|
|
1264
|
-
const bounded2 = utf8Tail(lines.join("\n"), INSPECTION_OUTPUT_BYTES);
|
|
1265
|
-
return Object.freeze({
|
|
1266
|
-
capturedAt: Date.now(),
|
|
1267
|
-
recentOutputTruncated: output.value.truncated || bounded2.truncated,
|
|
1268
|
-
...bounded2.text ? { recentOutput: bounded2.text } : {},
|
|
1269
|
-
...foreground.ok === true ? { foreground: foreground.value } : {},
|
|
1270
|
-
...after.activity !== void 0 ? { activity: after.activity } : {}
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
function utf8Tail(value, maxBytes) {
|
|
1274
|
-
const bytes = Buffer.from(value);
|
|
1275
|
-
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
1276
|
-
let start = bytes.length - maxBytes;
|
|
1277
|
-
while (start < bytes.length && (bytes[start] & 192) === 128) start++;
|
|
1278
|
-
return { text: bytes.subarray(start).toString(), truncated: true };
|
|
1279
|
-
}
|
|
1328
|
+
// extension/radar-children.ts
|
|
1329
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1280
1330
|
|
|
1281
|
-
// extension/radar-
|
|
1282
|
-
import {
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1285
|
-
return pane && /^%\d+$/.test(pane) ? pane : void 0;
|
|
1286
|
-
}
|
|
1287
|
-
function tmuxServerName(env) {
|
|
1288
|
-
const socket = env.TMUX?.trim().split(",")[0];
|
|
1289
|
-
return socket ? basename(socket) || void 0 : void 0;
|
|
1290
|
-
}
|
|
1291
|
-
function qualifiedOwnPane(env, backend, instance) {
|
|
1292
|
-
if (backend !== "tmux")
|
|
1293
|
-
return {
|
|
1294
|
-
absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
|
|
1295
|
-
};
|
|
1296
|
-
if (instance === void 0 || instance === "")
|
|
1297
|
-
return {
|
|
1298
|
-
pending: "the daemon did not name the tmux server it addresses"
|
|
1299
|
-
};
|
|
1300
|
-
const server = tmuxServerName(env);
|
|
1301
|
-
if (server === void 0)
|
|
1302
|
-
return {
|
|
1303
|
-
absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
|
|
1304
|
-
};
|
|
1305
|
-
if (server !== instance)
|
|
1306
|
-
return {
|
|
1307
|
-
absent: `this process is attached to tmux server ${server}, not to ${instance}`
|
|
1308
|
-
};
|
|
1309
|
-
const pane = radarLocalPane(env);
|
|
1310
|
-
if (pane === void 0)
|
|
1311
|
-
return {
|
|
1312
|
-
absent: "this process's own environment names no tmux pane of that server"
|
|
1313
|
-
};
|
|
1314
|
-
return { pane: `tmux:${instance}:${pane}` };
|
|
1315
|
-
}
|
|
1316
|
-
function observedContainment(inventory, paneId) {
|
|
1317
|
-
const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
|
|
1318
|
-
if (rows.length === 0) return void 0;
|
|
1319
|
-
if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
|
|
1320
|
-
const row = rows[0];
|
|
1321
|
-
const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
|
|
1322
|
-
if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
|
|
1323
|
-
return {
|
|
1324
|
-
ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
|
|
1325
|
-
};
|
|
1326
|
-
return { pane: row };
|
|
1331
|
+
// extension/radar-launch.ts
|
|
1332
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1333
|
+
function refusedLaunch(message) {
|
|
1334
|
+
return { kind: "failed", message };
|
|
1327
1335
|
}
|
|
1328
|
-
|
|
1329
|
-
let ready;
|
|
1330
|
-
try {
|
|
1331
|
-
ready = await client.ping();
|
|
1332
|
-
} catch {
|
|
1333
|
-
return { state: "pending", reason: "the daemon did not answer this process" };
|
|
1334
|
-
}
|
|
1335
|
-
if (ready.ok === false)
|
|
1336
|
-
return {
|
|
1337
|
-
state: "pending",
|
|
1338
|
-
reason: `the daemon did not answer this process (${ready.code})`
|
|
1339
|
-
};
|
|
1340
|
-
const backend = ready.value.backend;
|
|
1341
|
-
if (backend === void 0 || backend === "none")
|
|
1342
|
-
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
1343
|
-
const instance = ready.value.instance ?? void 0;
|
|
1344
|
-
const own = qualifiedOwnPane(env, backend, instance);
|
|
1345
|
-
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
1346
|
-
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
1347
|
-
const paneId = own.pane;
|
|
1348
|
-
if (paneId === env.PI_HERDSMAN_PARENT_PANE_ID?.trim())
|
|
1349
|
-
return {
|
|
1350
|
-
state: "absent",
|
|
1351
|
-
reason: "this process is in the pane its launch was created beside, not in a pane of its own"
|
|
1352
|
-
};
|
|
1353
|
-
let inventory;
|
|
1354
|
-
try {
|
|
1355
|
-
inventory = await client.observe();
|
|
1356
|
-
} catch {
|
|
1357
|
-
return {
|
|
1358
|
-
state: "pending",
|
|
1359
|
-
reason: "the daemon's topology could not be read for this process's pane"
|
|
1360
|
-
};
|
|
1361
|
-
}
|
|
1362
|
-
if (inventory.ok === false)
|
|
1363
|
-
return {
|
|
1364
|
-
state: "pending",
|
|
1365
|
-
reason: `the daemon's topology could not be read (${inventory.code})`
|
|
1366
|
-
};
|
|
1367
|
-
const containment = observedContainment(inventory.value, paneId);
|
|
1368
|
-
if (containment === void 0)
|
|
1369
|
-
return {
|
|
1370
|
-
state: "pending",
|
|
1371
|
-
reason: `the daemon's topology does not report pane ${paneId} yet`
|
|
1372
|
-
};
|
|
1373
|
-
if ("ambiguous" in containment)
|
|
1374
|
-
return { state: "pending", reason: containment.ambiguous };
|
|
1375
|
-
const row = containment.pane;
|
|
1336
|
+
function unresolvedLaunch(requestId, message) {
|
|
1376
1337
|
return {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
...instance !== void 0 ? { instance } : {},
|
|
1381
|
-
workspace: row.workspace_id,
|
|
1382
|
-
tab: row.tab_id,
|
|
1383
|
-
pane: paneId
|
|
1384
|
-
}
|
|
1338
|
+
kind: "unresolved",
|
|
1339
|
+
request_id: requestId,
|
|
1340
|
+
message: `spawn ${requestId} is unresolved (${message})`
|
|
1385
1341
|
};
|
|
1386
1342
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
diagnostic: trajectory(`the registered process is ${verification}`)
|
|
1440
|
-
};
|
|
1441
|
-
if (verification !== "verified")
|
|
1442
|
-
return unknownChild(`process verdict is ${verification}`);
|
|
1443
|
-
if (location?.pane !== provenance.pane_id)
|
|
1444
|
-
return unknownChild(
|
|
1445
|
-
`registered on ${location?.pane ?? "no pane"}, not on ${provenance.pane_id}`
|
|
1446
|
-
);
|
|
1447
|
-
if (provenance.session_id === void 0)
|
|
1448
|
-
return unknownChild("without a session this launch recorded for it");
|
|
1449
|
-
if (contextSession !== provenance.session_id)
|
|
1450
|
-
return unknownChild(
|
|
1451
|
-
`publishing session ${contextSession ?? "none"}, not ${provenance.session_id}`
|
|
1452
|
-
);
|
|
1453
|
-
return {
|
|
1454
|
-
kind: "live",
|
|
1455
|
-
evidence: {
|
|
1456
|
-
request_id: provenance.request_id,
|
|
1457
|
-
registration_id: edge.bound.registration_id,
|
|
1458
|
-
source: edge.bound.source,
|
|
1459
|
-
incarnation: edge.bound.incarnation,
|
|
1460
|
-
pane_id: edge.location.pane
|
|
1343
|
+
var PRE_EFFECT_CODES = [
|
|
1344
|
+
"absent",
|
|
1345
|
+
"unsupported",
|
|
1346
|
+
"bad_version",
|
|
1347
|
+
"bad_request",
|
|
1348
|
+
"bad_params",
|
|
1349
|
+
"unknown_method",
|
|
1350
|
+
"refused",
|
|
1351
|
+
"not_found",
|
|
1352
|
+
"busy",
|
|
1353
|
+
"backend_unavailable"
|
|
1354
|
+
];
|
|
1355
|
+
function launchFailure(result, requestId) {
|
|
1356
|
+
return PRE_EFFECT_CODES.includes(result.code) ? refusedLaunch(result.message) : unresolvedLaunch(requestId, result.message);
|
|
1357
|
+
}
|
|
1358
|
+
async function waitForChild(client, requestId, options) {
|
|
1359
|
+
let last = "the daemon has not answered";
|
|
1360
|
+
for (; ; ) {
|
|
1361
|
+
if (options.signal?.aborted)
|
|
1362
|
+
return unresolvedLaunch(requestId, `the launch was cancelled (${last})`);
|
|
1363
|
+
const answer = await client.spawnGet(requestId);
|
|
1364
|
+
if (answer.ok === true) {
|
|
1365
|
+
const { edge } = answer.value;
|
|
1366
|
+
if (edge.withdrawn !== void 0)
|
|
1367
|
+
return refusedLaunch(`the daemon withdrew this launch: ${edge.withdrawn.reason}`);
|
|
1368
|
+
if (edge.granted_at === void 0) {
|
|
1369
|
+
await delay(options.pollMs);
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
const pane = edge.location?.pane;
|
|
1373
|
+
if (pane === void 0 || pane === "")
|
|
1374
|
+
return unresolvedLaunch(
|
|
1375
|
+
requestId,
|
|
1376
|
+
"the daemon's launch recorded no pane to verify the child against"
|
|
1377
|
+
);
|
|
1378
|
+
const agentId = edge.bound?.registration_id;
|
|
1379
|
+
if (agentId !== void 0 && isUuid(agentId)) {
|
|
1380
|
+
const subjectId = edge.bound?.subject_id;
|
|
1381
|
+
if (subjectId === void 0 || !isUuid(subjectId))
|
|
1382
|
+
return unresolvedLaunch(
|
|
1383
|
+
requestId,
|
|
1384
|
+
`the daemon bound the child ${agentId} without recording its subject`
|
|
1385
|
+
);
|
|
1386
|
+
return {
|
|
1387
|
+
kind: "bound",
|
|
1388
|
+
subject_id: subjectId,
|
|
1389
|
+
pane_id: pane
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
last = "no child has registered with this launch's token yet";
|
|
1393
|
+
} else {
|
|
1394
|
+
return unresolvedLaunch(requestId, answer.message);
|
|
1461
1395
|
}
|
|
1462
|
-
|
|
1396
|
+
await delay(options.pollMs);
|
|
1397
|
+
}
|
|
1463
1398
|
}
|
|
1464
1399
|
|
|
1465
1400
|
// extension/radar-children.ts
|
|
1466
|
-
var PAGE = 100;
|
|
1467
|
-
var MAX_PAGES = 20;
|
|
1468
1401
|
function createRadarChildren(client, publication) {
|
|
1469
|
-
const held = /* @__PURE__ */ new Map();
|
|
1470
|
-
async function resolveAll() {
|
|
1471
|
-
const newest = /* @__PURE__ */ new Map();
|
|
1472
|
-
let after;
|
|
1473
|
-
for (let page = 0; page < MAX_PAGES; page++) {
|
|
1474
|
-
const read = await client.spawnList({ ...after !== void 0 ? { after } : {}, limit: PAGE });
|
|
1475
|
-
if (read.ok === false) return read;
|
|
1476
|
-
for (const edge of read.value.spawns) {
|
|
1477
|
-
const bound = edge.bound;
|
|
1478
|
-
if (!bound?.subject_id || !bound.registration_id || edge.launched !== "completed") continue;
|
|
1479
|
-
const at = Date.parse(bound.bound_at);
|
|
1480
|
-
if (Number.isNaN(at)) continue;
|
|
1481
|
-
const current = newest.get(bound.subject_id);
|
|
1482
|
-
if (current !== void 0 && current.at >= at) continue;
|
|
1483
|
-
newest.set(bound.subject_id, {
|
|
1484
|
-
at,
|
|
1485
|
-
registration_id: bound.registration_id,
|
|
1486
|
-
child: {
|
|
1487
|
-
subject_id: bound.subject_id,
|
|
1488
|
-
request_id: edge.request_id,
|
|
1489
|
-
...edge.location?.pane ? { pane_id: edge.location.pane } : {},
|
|
1490
|
-
state: edge.state,
|
|
1491
|
-
exited: edge.exit_observation != null && edge.exit_observation.state !== "unknown"
|
|
1492
|
-
}
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
if (!read.value.next) return { ok: true, value: newest };
|
|
1496
|
-
after = read.value.next;
|
|
1497
|
-
}
|
|
1498
|
-
return { ok: false, code: "protocol", message: "the caller's launches exceed the readable pages" };
|
|
1499
|
-
}
|
|
1500
|
-
async function resolve2(subject) {
|
|
1501
|
-
const all = await resolveAll();
|
|
1502
|
-
return all.ok ? all.value.get(subject) : void 0;
|
|
1503
|
-
}
|
|
1504
|
-
async function resolveAndFollow(subject) {
|
|
1505
|
-
const all = await resolveAll();
|
|
1506
|
-
if (!all.ok) return void 0;
|
|
1507
|
-
follow(all.value);
|
|
1508
|
-
return all.value.get(subject);
|
|
1509
|
-
}
|
|
1510
|
-
function follow(all) {
|
|
1511
|
-
for (const [subject, entry] of held) {
|
|
1512
|
-
const target = all.get(subject);
|
|
1513
|
-
if (target !== void 0 && entry.published !== target.registration_id)
|
|
1514
|
-
entry.publish?.(target);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
1402
|
return {
|
|
1518
|
-
async list() {
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1403
|
+
async list(selection) {
|
|
1404
|
+
const children = [];
|
|
1405
|
+
let after;
|
|
1406
|
+
do {
|
|
1407
|
+
const page = await client.subjectChildren({ selection, after, limit: 100 });
|
|
1408
|
+
if (!page.ok) return page;
|
|
1409
|
+
children.push(...page.value.subjects);
|
|
1410
|
+
if (page.value.next === after)
|
|
1411
|
+
return { ok: false, code: "protocol", message: "child page did not advance" };
|
|
1412
|
+
after = page.value.next ?? void 0;
|
|
1413
|
+
} while (after !== void 0);
|
|
1414
|
+
return { ok: true, value: children };
|
|
1523
1415
|
},
|
|
1524
|
-
|
|
1525
|
-
const
|
|
1526
|
-
if (
|
|
1527
|
-
|
|
1528
|
-
const
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
publication.assignment(target.registration_id, owner).update(latest);
|
|
1535
|
-
};
|
|
1536
|
-
entry.publish = send;
|
|
1537
|
-
entry.writer = {
|
|
1538
|
-
update(snapshot) {
|
|
1539
|
-
latest = snapshot;
|
|
1540
|
-
void resolve2(subject).then((target) => {
|
|
1541
|
-
if (target !== void 0) send(target);
|
|
1542
|
-
});
|
|
1543
|
-
},
|
|
1544
|
-
retire() {
|
|
1545
|
-
latest = void 0;
|
|
1546
|
-
if (entry.published !== void 0) publication.retireAssignment(entry.published);
|
|
1547
|
-
entry.published = void 0;
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
held.set(subject, entry);
|
|
1551
|
-
return entry.writer;
|
|
1552
|
-
},
|
|
1553
|
-
retire(subject) {
|
|
1554
|
-
held.get(subject)?.writer.retire();
|
|
1555
|
-
held.delete(subject);
|
|
1556
|
-
},
|
|
1557
|
-
async observe(subject, session_id) {
|
|
1558
|
-
const target = await resolveAndFollow(subject);
|
|
1559
|
-
if (target === void 0)
|
|
1560
|
-
return { kind: "unknown", diagnostic: "the daemon records no bound launch for this child" };
|
|
1561
|
-
return observeRadarChild(client, {
|
|
1562
|
-
request_id: target.child.request_id,
|
|
1563
|
-
pane_id: target.child.pane_id,
|
|
1564
|
-
registration_id: target.registration_id,
|
|
1565
|
-
session_id
|
|
1416
|
+
async restart(subject, program, options) {
|
|
1417
|
+
const parent = await publication.subject();
|
|
1418
|
+
if (parent?.subject_id === void 0)
|
|
1419
|
+
return { kind: "failed", message: "this process has no granted subject" };
|
|
1420
|
+
const request_id = randomUUID2();
|
|
1421
|
+
const requested = await client.restart({ request_id, parent: parent.registration_id, subject_id: subject, ...program });
|
|
1422
|
+
if (!requested.ok) return launchFailure(requested, request_id);
|
|
1423
|
+
const bound = await waitForChild(client, requested.value.launch_request_id, {
|
|
1424
|
+
pollMs: options?.pollMs ?? 250,
|
|
1425
|
+
...options?.signal !== void 0 ? { signal: options.signal } : {}
|
|
1566
1426
|
});
|
|
1427
|
+
if (bound.kind !== "bound") return bound;
|
|
1428
|
+
return { kind: "ready", value: { request_id: requested.value.launch_request_id, subject_id: bound.subject_id, pane_id: bound.pane_id } };
|
|
1567
1429
|
},
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
return
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1430
|
+
assignment: (subject, owner) => publication.assignment(subject, owner),
|
|
1431
|
+
retire: (subject) => publication.retireAssignment(subject),
|
|
1432
|
+
async observe(subject) {
|
|
1433
|
+
const read = await client.childGet(subject);
|
|
1434
|
+
if (!read.ok) return { kind: "unknown", diagnostic: `${read.code}: ${read.message}` };
|
|
1435
|
+
if (read.value.state === "live") return { kind: "live", subject: read.value };
|
|
1436
|
+
if (read.value.state === "detached") return { kind: "gone", subject: read.value };
|
|
1437
|
+
return { kind: "unknown", diagnostic: `child attachment is ${read.value.state}` };
|
|
1438
|
+
},
|
|
1439
|
+
async inspect(subject) {
|
|
1440
|
+
const read = await client.childInspect(subject);
|
|
1441
|
+
if (!read.ok) throw new Error(`Child inspection unavailable (${read.code}: ${read.message})`);
|
|
1442
|
+
return read.value;
|
|
1577
1443
|
}
|
|
1578
1444
|
};
|
|
1579
1445
|
}
|
|
@@ -1616,7 +1482,7 @@ function createRadarHandoffPort(client, publication, parentSubjectId = () => pro
|
|
|
1616
1482
|
}
|
|
1617
1483
|
|
|
1618
1484
|
// extension/radar-publication.ts
|
|
1619
|
-
import { createHash, randomUUID as
|
|
1485
|
+
import { createHash, randomUUID as randomUUID3 } from "node:crypto";
|
|
1620
1486
|
import {
|
|
1621
1487
|
closeSync,
|
|
1622
1488
|
constants,
|
|
@@ -1746,7 +1612,7 @@ function createPublicationStore(root) {
|
|
|
1746
1612
|
ensureDirectory(directory);
|
|
1747
1613
|
const temporary = join2(
|
|
1748
1614
|
directory,
|
|
1749
|
-
`.${name.slice(name.lastIndexOf("/") + 1)}.${
|
|
1615
|
+
`.${name.slice(name.lastIndexOf("/") + 1)}.${randomUUID3()}.tmp`
|
|
1750
1616
|
);
|
|
1751
1617
|
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
1752
1618
|
if (bytes.length > MAX_RECORD_BYTES)
|
|
@@ -1802,7 +1668,7 @@ function radarProcessSlot() {
|
|
|
1802
1668
|
return host[PROCESS_SLOT];
|
|
1803
1669
|
}
|
|
1804
1670
|
function radarProcessIncarnation(slot = radarProcessSlot()) {
|
|
1805
|
-
return slot.incarnation ??=
|
|
1671
|
+
return slot.incarnation ??= randomUUID3();
|
|
1806
1672
|
}
|
|
1807
1673
|
function recordAt2(value, key) {
|
|
1808
1674
|
if (typeof value !== "object" || value === null) return void 0;
|
|
@@ -1959,15 +1825,18 @@ function createChannelWriter(input) {
|
|
|
1959
1825
|
function blank(agentId) {
|
|
1960
1826
|
return {
|
|
1961
1827
|
version: RADAR_PUBLICATION_VERSION,
|
|
1962
|
-
|
|
1828
|
+
target_id: agentId,
|
|
1963
1829
|
channel,
|
|
1964
1830
|
publisher,
|
|
1965
1831
|
sequence: 0
|
|
1966
1832
|
};
|
|
1967
1833
|
}
|
|
1834
|
+
function addressed(target) {
|
|
1835
|
+
return channel === "assignment" ? { subject_id: target } : { registration_id: target };
|
|
1836
|
+
}
|
|
1968
1837
|
function loadRecord() {
|
|
1969
1838
|
const value = store.read(recordName);
|
|
1970
|
-
const agentId = textAt(value, "
|
|
1839
|
+
const agentId = textAt(value, "target_id");
|
|
1971
1840
|
const storedPublisher = recordAt2(value, "publisher");
|
|
1972
1841
|
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)
|
|
1973
1842
|
return blank("");
|
|
@@ -1982,7 +1851,7 @@ function createChannelWriter(input) {
|
|
|
1982
1851
|
const sentAt = numberAt2(acceptedValue, "sent_at");
|
|
1983
1852
|
return {
|
|
1984
1853
|
version: RADAR_PUBLICATION_VERSION,
|
|
1985
|
-
|
|
1854
|
+
target_id: agentId,
|
|
1986
1855
|
channel,
|
|
1987
1856
|
publisher,
|
|
1988
1857
|
...handle !== void 0 && generation !== void 0 ? { writer: { handle, generation } } : {},
|
|
@@ -2023,15 +1892,11 @@ function createChannelWriter(input) {
|
|
|
2023
1892
|
if (record.writer !== void 0) return true;
|
|
2024
1893
|
const agentId = await input.subject(recovering);
|
|
2025
1894
|
if (agentId === void 0) return false;
|
|
2026
|
-
if (record.
|
|
1895
|
+
if (record.target_id !== agentId) {
|
|
2027
1896
|
record = blank(agentId);
|
|
2028
1897
|
save();
|
|
2029
1898
|
}
|
|
2030
|
-
const answer = await client.acquire({
|
|
2031
|
-
registration_id: agentId,
|
|
2032
|
-
channel,
|
|
2033
|
-
publisher
|
|
2034
|
-
});
|
|
1899
|
+
const answer = await client.acquire({ ...addressed(agentId), channel, publisher });
|
|
2035
1900
|
if (answer.ok === false) {
|
|
2036
1901
|
if (answer.code === "not_found") {
|
|
2037
1902
|
recovering = true;
|
|
@@ -2058,7 +1923,7 @@ function createChannelWriter(input) {
|
|
|
2058
1923
|
const writer = record.writer;
|
|
2059
1924
|
if (writer === void 0) return;
|
|
2060
1925
|
const answer = await client.publish({
|
|
2061
|
-
|
|
1926
|
+
...addressed(record.target_id),
|
|
2062
1927
|
channel,
|
|
2063
1928
|
writer_handle: writer.handle,
|
|
2064
1929
|
sequence: pending.sequence,
|
|
@@ -2152,9 +2017,9 @@ function createChannelWriter(input) {
|
|
|
2152
2017
|
stopPump();
|
|
2153
2018
|
const writer = record.writer;
|
|
2154
2019
|
if (writer === void 0) return;
|
|
2155
|
-
const agentId = record.
|
|
2020
|
+
const agentId = record.target_id;
|
|
2156
2021
|
try {
|
|
2157
|
-
void client.retire({
|
|
2022
|
+
void client.retire({ ...addressed(agentId), channel, writer_handle: writer.handle }).then((answer) => {
|
|
2158
2023
|
if (answer.ok === true) {
|
|
2159
2024
|
try {
|
|
2160
2025
|
record.retired_at = new Date(scheduler.now()).toISOString();
|
|
@@ -2244,6 +2109,7 @@ function createPresenceReporter(input) {
|
|
|
2244
2109
|
}
|
|
2245
2110
|
claim.registration_id = answer.value.registration_id;
|
|
2246
2111
|
claim.subject_id = answer.value.standing === "granted" ? answer.value.subject_id ?? void 0 : void 0;
|
|
2112
|
+
if (claim.subject_id !== void 0) input.granted?.(claim.subject_id);
|
|
2247
2113
|
standing = answer.value.standing === "granted" ? { state: "managed", subject_id: claim.subject_id } : answer.value.standing === "duplicate" ? { state: "duplicate" } : answer.value.standing === "pending" ? { state: "pending" } : { state: "unmanaged" };
|
|
2248
2114
|
diagnostic = answer.value.standing === "duplicate" ? DUPLICATE_SESSION_DIAGNOSTIC : void 0;
|
|
2249
2115
|
save();
|
|
@@ -2267,7 +2133,7 @@ function createPresenceReporter(input) {
|
|
|
2267
2133
|
releases.push({ registration_id: state.registration_id, claim_id: state.claim_id });
|
|
2268
2134
|
}
|
|
2269
2135
|
standing = void 0;
|
|
2270
|
-
state = { version: 2, session, claim_id:
|
|
2136
|
+
state = { version: 2, session, claim_id: randomUUID3(), releases };
|
|
2271
2137
|
save();
|
|
2272
2138
|
stopped = false;
|
|
2273
2139
|
void ensure();
|
|
@@ -2304,7 +2170,7 @@ var STORE_IDENTITY = /* @__PURE__ */ new WeakMap();
|
|
|
2304
2170
|
function storeIdentity(store) {
|
|
2305
2171
|
const known = STORE_IDENTITY.get(store);
|
|
2306
2172
|
if (known !== void 0) return known;
|
|
2307
|
-
const created =
|
|
2173
|
+
const created = randomUUID3();
|
|
2308
2174
|
STORE_IDENTITY.set(store, created);
|
|
2309
2175
|
return created;
|
|
2310
2176
|
}
|
|
@@ -2425,7 +2291,7 @@ function createRadarPublicationInner(options) {
|
|
|
2425
2291
|
agentId = registered;
|
|
2426
2292
|
processRecord = { ...record, registration_id: agentId };
|
|
2427
2293
|
store.write(registrationName, processRecord);
|
|
2428
|
-
if (options.binding !== void 0) writeBinding(options.binding);
|
|
2294
|
+
if (options.binding !== void 0) writeBinding(options.binding, grantedSubject());
|
|
2429
2295
|
return agentId;
|
|
2430
2296
|
})();
|
|
2431
2297
|
try {
|
|
@@ -2434,11 +2300,16 @@ function createRadarPublicationInner(options) {
|
|
|
2434
2300
|
subjectReady = void 0;
|
|
2435
2301
|
}
|
|
2436
2302
|
}
|
|
2437
|
-
function
|
|
2303
|
+
function grantedSubject() {
|
|
2304
|
+
const standing = presence.management();
|
|
2305
|
+
return standing?.state === "managed" ? standing.subject_id : void 0;
|
|
2306
|
+
}
|
|
2307
|
+
function writeBinding(binding, subjectId) {
|
|
2438
2308
|
if (agentId === void 0) return;
|
|
2439
2309
|
store.write(`bindings/${binding.key}.json`, {
|
|
2440
2310
|
version: RADAR_PUBLICATION_VERSION,
|
|
2441
2311
|
registration_id: agentId,
|
|
2312
|
+
...subjectId !== void 0 ? { subject_id: subjectId } : {},
|
|
2442
2313
|
incarnation,
|
|
2443
2314
|
run: binding.run,
|
|
2444
2315
|
owner: binding.owner,
|
|
@@ -2478,6 +2349,9 @@ function createRadarPublicationInner(options) {
|
|
|
2478
2349
|
...launchRegistrationId ? { registration_id: launchRegistrationId } : {}
|
|
2479
2350
|
};
|
|
2480
2351
|
},
|
|
2352
|
+
granted: (subjectId) => {
|
|
2353
|
+
if (options.binding !== void 0) writeBinding(options.binding, subjectId);
|
|
2354
|
+
},
|
|
2481
2355
|
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
2482
2356
|
});
|
|
2483
2357
|
const facts = createChannelWriter({
|
|
@@ -2509,8 +2383,7 @@ function createRadarPublicationInner(options) {
|
|
|
2509
2383
|
channel: "assignment",
|
|
2510
2384
|
publisher: publisher("herdsman-owner", reportingOwner),
|
|
2511
2385
|
codec: assignmentCodec,
|
|
2512
|
-
//
|
|
2513
|
-
// registration_id from the child's sidecar is the whole correlation.
|
|
2386
|
+
// Radar owns this assignment across the child's process replacements.
|
|
2514
2387
|
subject: async () => target,
|
|
2515
2388
|
leaseMs: options.leaseMs ?? RADAR_LEASE_MS,
|
|
2516
2389
|
heartbeatMs: options.heartbeatMs ?? RADAR_HEARTBEAT_MS
|
|
@@ -2531,16 +2404,18 @@ function createRadarPublicationInner(options) {
|
|
|
2531
2404
|
return void 0;
|
|
2532
2405
|
}
|
|
2533
2406
|
const bound = textAt(value, "registration_id");
|
|
2407
|
+
const subjectId = textAt(value, "subject_id");
|
|
2534
2408
|
const boundIncarnation = textAt(value, "incarnation");
|
|
2535
2409
|
const run = textAt(value, "run");
|
|
2536
2410
|
const owner = textAt(value, "owner");
|
|
2537
2411
|
const label = textAt(value, "label");
|
|
2538
2412
|
const observedAt = textAt(value, "observed_at");
|
|
2539
|
-
if (!isUuid(bound) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
|
|
2413
|
+
if (!isUuid(bound) || subjectId !== void 0 && !isUuid(subjectId) || !isUuid(boundIncarnation) || run === void 0 || owner === void 0 || label === void 0 || observedAt === void 0)
|
|
2540
2414
|
return void 0;
|
|
2541
2415
|
return {
|
|
2542
2416
|
version: RADAR_PUBLICATION_VERSION,
|
|
2543
2417
|
registration_id: bound,
|
|
2418
|
+
...subjectId !== void 0 ? { subject_id: subjectId } : {},
|
|
2544
2419
|
incarnation: boundIncarnation,
|
|
2545
2420
|
run,
|
|
2546
2421
|
owner,
|
|
@@ -2825,9 +2700,116 @@ function createRadarExecutionAdapter(pi, options) {
|
|
|
2825
2700
|
};
|
|
2826
2701
|
}
|
|
2827
2702
|
|
|
2703
|
+
// extension/radar-location.ts
|
|
2704
|
+
import { basename } from "node:path";
|
|
2705
|
+
function radarLocalPane(env) {
|
|
2706
|
+
const pane = env.TMUX_PANE?.trim();
|
|
2707
|
+
return pane && /^%\d+$/.test(pane) ? pane : void 0;
|
|
2708
|
+
}
|
|
2709
|
+
function tmuxServerName(env) {
|
|
2710
|
+
const socket = env.TMUX?.trim().split(",")[0];
|
|
2711
|
+
return socket ? basename(socket) || void 0 : void 0;
|
|
2712
|
+
}
|
|
2713
|
+
function qualifiedOwnPane(env, backend, instance) {
|
|
2714
|
+
if (backend !== "tmux")
|
|
2715
|
+
return {
|
|
2716
|
+
absent: `the daemon serves the ${backend} backend, and Herdsman drives only tmux`
|
|
2717
|
+
};
|
|
2718
|
+
if (instance === void 0 || instance === "")
|
|
2719
|
+
return {
|
|
2720
|
+
pending: "the daemon did not name the tmux server it addresses"
|
|
2721
|
+
};
|
|
2722
|
+
const server = tmuxServerName(env);
|
|
2723
|
+
if (server === void 0)
|
|
2724
|
+
return {
|
|
2725
|
+
absent: "this process's own tmux client names no server, so it is not in the pane it would publish"
|
|
2726
|
+
};
|
|
2727
|
+
if (server !== instance)
|
|
2728
|
+
return {
|
|
2729
|
+
absent: `this process is attached to tmux server ${server}, not to ${instance}`
|
|
2730
|
+
};
|
|
2731
|
+
const pane = radarLocalPane(env);
|
|
2732
|
+
if (pane === void 0)
|
|
2733
|
+
return {
|
|
2734
|
+
absent: "this process's own environment names no tmux pane of that server"
|
|
2735
|
+
};
|
|
2736
|
+
return { pane: `tmux:${instance}:${pane}` };
|
|
2737
|
+
}
|
|
2738
|
+
function observedContainment(inventory, paneId) {
|
|
2739
|
+
const rows = inventory.panes.filter((pane) => pane.pane_id === paneId);
|
|
2740
|
+
if (rows.length === 0) return void 0;
|
|
2741
|
+
if (rows.length > 1) return { ambiguous: `topology reports pane ${paneId} twice` };
|
|
2742
|
+
const row = rows[0];
|
|
2743
|
+
const tab = inventory.tabs.find((candidate) => candidate.tab_id === row.tab_id);
|
|
2744
|
+
if (tab !== void 0 && tab.workspace_id !== row.workspace_id)
|
|
2745
|
+
return {
|
|
2746
|
+
ambiguous: `pane ${paneId} is reported in ${row.workspace_id}, but its tab belongs to ${tab.workspace_id}`
|
|
2747
|
+
};
|
|
2748
|
+
return { pane: row };
|
|
2749
|
+
}
|
|
2750
|
+
async function resolveRadarProcessLocation(client, env = process.env) {
|
|
2751
|
+
let ready;
|
|
2752
|
+
try {
|
|
2753
|
+
ready = await client.ping();
|
|
2754
|
+
} catch {
|
|
2755
|
+
return { state: "pending", reason: "the daemon did not answer this process" };
|
|
2756
|
+
}
|
|
2757
|
+
if (ready.ok === false)
|
|
2758
|
+
return {
|
|
2759
|
+
state: "pending",
|
|
2760
|
+
reason: `the daemon did not answer this process (${ready.code})`
|
|
2761
|
+
};
|
|
2762
|
+
const backend = ready.value.backend;
|
|
2763
|
+
if (backend === void 0 || backend === "none")
|
|
2764
|
+
return { state: "absent", reason: "the daemon serves no physical backend" };
|
|
2765
|
+
const instance = ready.value.instance ?? void 0;
|
|
2766
|
+
const own = qualifiedOwnPane(env, backend, instance);
|
|
2767
|
+
if ("absent" in own) return { state: "absent", reason: own.absent };
|
|
2768
|
+
if ("pending" in own) return { state: "pending", reason: own.pending };
|
|
2769
|
+
const paneId = own.pane;
|
|
2770
|
+
if (paneId === env.PI_HERDSMAN_PARENT_PANE_ID?.trim())
|
|
2771
|
+
return {
|
|
2772
|
+
state: "absent",
|
|
2773
|
+
reason: "this process is in the pane its launch was created beside, not in a pane of its own"
|
|
2774
|
+
};
|
|
2775
|
+
let inventory;
|
|
2776
|
+
try {
|
|
2777
|
+
inventory = await client.observe();
|
|
2778
|
+
} catch {
|
|
2779
|
+
return {
|
|
2780
|
+
state: "pending",
|
|
2781
|
+
reason: "the daemon's topology could not be read for this process's pane"
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
if (inventory.ok === false)
|
|
2785
|
+
return {
|
|
2786
|
+
state: "pending",
|
|
2787
|
+
reason: `the daemon's topology could not be read (${inventory.code})`
|
|
2788
|
+
};
|
|
2789
|
+
const containment = observedContainment(inventory.value, paneId);
|
|
2790
|
+
if (containment === void 0)
|
|
2791
|
+
return {
|
|
2792
|
+
state: "pending",
|
|
2793
|
+
reason: `the daemon's topology does not report pane ${paneId} yet`
|
|
2794
|
+
};
|
|
2795
|
+
if ("ambiguous" in containment)
|
|
2796
|
+
return { state: "pending", reason: containment.ambiguous };
|
|
2797
|
+
const row = containment.pane;
|
|
2798
|
+
return {
|
|
2799
|
+
state: "resolved",
|
|
2800
|
+
location: {
|
|
2801
|
+
backend,
|
|
2802
|
+
...instance !== void 0 ? { instance } : {},
|
|
2803
|
+
workspace: row.workspace_id,
|
|
2804
|
+
tab: row.tab_id,
|
|
2805
|
+
pane: paneId
|
|
2806
|
+
}
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2828
2810
|
// extension/radar-runtime.ts
|
|
2829
2811
|
var RADAR_RUNTIME_REQUEST = "pi-radar/runtime-request";
|
|
2830
|
-
var RADAR_RUNTIME_VERSION =
|
|
2812
|
+
var RADAR_RUNTIME_VERSION = 5;
|
|
2831
2813
|
function lazyRadarRuntime(start) {
|
|
2832
2814
|
let pending;
|
|
2833
2815
|
return {
|