@topolo/cli 0.13.19 → 0.13.22
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/agent-packs/README.md +421 -1
- package/agent-packs/docs/selected-artifacts.md +47 -0
- package/agent-packs/documentation/cloud-task-state.md +56 -0
- package/agent-packs/domain-contexts/README.md +25 -0
- package/agent-packs/domain-contexts/cicd.json +8 -0
- package/agent-packs/gui/feed-adapter.js +4 -0
- package/agent-packs/gui/index.html +9 -0
- package/agent-packs/gui/studio.css +14 -0
- package/agent-packs/gui/studio.js +121 -0
- package/agent-packs/gui/studio_server.py +355 -0
- package/agent-packs/roles/cicd/CLAUDE.md +24 -0
- package/agent-packs/specializations/cicd--cicd.json +5 -0
- package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
- package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
- package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
- package/agent-packs/specializations/cicd--release-verification.json +5 -0
- package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
- package/agent-packs/specializations/intakes--engineering-02.json +5 -0
- package/agent-packs/specializations/intakes--engineering-03.json +5 -0
- package/agent-packs/specializations/intakes--engineering.json +5 -0
- package/agent-packs/specializations/intakes--operator-01.json +5 -0
- package/agent-packs/specializations/intakes--operator-02.json +5 -0
- package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
- package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
- package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
- package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
- package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
- package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
- package/agent-packs/specializations/operators--agreement-operations.json +5 -0
- package/agent-packs/specializations/operators--books-operator.json +5 -0
- package/agent-packs/specializations/operators--brand-operator.json +5 -0
- package/agent-packs/specializations/operators--commerce-operations.json +5 -0
- package/agent-packs/specializations/operators--communications-operator.json +5 -0
- package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
- package/agent-packs/specializations/operators--customer-operations.json +5 -0
- package/agent-packs/specializations/operators--design-operator.json +5 -0
- package/agent-packs/specializations/operators--design-production-02.json +5 -0
- package/agent-packs/specializations/operators--design-production-03.json +5 -0
- package/agent-packs/specializations/operators--design-production-04.json +5 -0
- package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
- package/agent-packs/specializations/operators--director-operator.json +5 -0
- package/agent-packs/specializations/operators--experience-review.json +5 -0
- package/agent-packs/specializations/operators--growth-operations.json +5 -0
- package/agent-packs/specializations/operators--learn-operator.json +5 -0
- package/agent-packs/specializations/operators--localization-operator.json +5 -0
- package/agent-packs/specializations/operators--operators.json +5 -0
- package/agent-packs/specializations/operators--pay-operator.json +5 -0
- package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
- package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
- package/agent-packs/specializations/operators--social-content-operator.json +5 -0
- package/agent-packs/specializations/operators--talent-operations.json +5 -0
- package/agent-packs/specializations/operators--web-operator.json +5 -0
- package/agent-packs/specializations/operators--workflow-operator.json +5 -0
- package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
- package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
- package/agent-packs/specializations/specialists--build-01.json +5 -0
- package/agent-packs/specializations/specialists--build-02.json +5 -0
- package/agent-packs/specializations/specialists--build-03.json +5 -0
- package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
- package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
- package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
- package/agent-packs/specializations/specialists--incident-triage.json +5 -0
- package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
- package/agent-packs/specializations/specialists--release-recovery.json +5 -0
- package/agent-packs/specializations/specialists--release-watch.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
- package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
- package/agent-packs/specializations/specialists--translation.json +5 -0
- package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
- package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
- package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
- package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
- package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
- package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
- package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
- package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
- package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
- package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
- package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
- package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +181 -16
- package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
- package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
- package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
- package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
- package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
- package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
- package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
- package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
- package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
- package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
- package/agent-packs/src/topolo_agent_pack/execution.py +103 -6
- package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
- package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
- package/agent-packs/src/topolo_agent_pack/live.py +285 -18
- package/agent-packs/src/topolo_agent_pack/native.py +160 -4
- package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
- package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
- package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
- package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
- package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
- package/agent-packs/src/topolo_agent_pack/work.py +322 -0
- package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
- package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
- package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
- package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
- package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
- package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
- package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
- package/dist/chunk-NYDIAYVY.js +144 -0
- package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
- package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
- package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
- package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
- package/dist/index.js +152 -73
- package/package.json +5 -5
|
@@ -1,4 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
acknowledgeClaudeDesktopProfile,
|
|
4
|
+
readClaudeDesktopProfile,
|
|
5
|
+
verifyClaudeDesktopProfileNative
|
|
6
|
+
} from "./chunk-NYDIAYVY.js";
|
|
7
|
+
|
|
8
|
+
// src/lib/claude-frontdoor-state.ts
|
|
9
|
+
async function observeClaudeFrontdoors(manager, members, check, warm = false, checkpoint = () => {
|
|
10
|
+
}) {
|
|
11
|
+
if (!Array.isArray(members) || members.length !== 3 || new Set(members.map((m) => m.sessionId)).size !== 3) throw Error("Exact three front doors required.");
|
|
12
|
+
const result = [];
|
|
13
|
+
for (const m of members) {
|
|
14
|
+
check();
|
|
15
|
+
let raw = await manager.getSessionWhenLoaded(m.sessionId, "topolo-launch-frontdoors", { warnOnMiss: false });
|
|
16
|
+
let dto = await manager.getSession(m.sessionId);
|
|
17
|
+
check();
|
|
18
|
+
if (!raw || !dto || raw.sessionId !== m.sessionId || dto.sessionId !== m.sessionId || raw.cwd !== m.cwd || dto.cwd !== m.cwd || dto.title !== m.title || raw.isArchived || dto.isArchived || raw.isRunning || raw.starting || raw.pendingCycle || raw.pendingToolPermissions?.length || manager.hasLosableWork(m.sessionId) || !raw.cliSessionId || m.cliSessionId && m.cliSessionId !== raw.cliSessionId || dto.isStarred !== true || ["model", "effort", "permissionMode"].some((k) => dto[k] !== m[k])) throw Error("Installed coordinator identity, settings or custody differ.");
|
|
19
|
+
if (warm && !raw.query) {
|
|
20
|
+
if (typeof manager.warmSession !== "function") throw Error("Native connect-only warm capability unavailable.");
|
|
21
|
+
checkpoint({ kind: "frontdoor-connect-only-attempt", sessionId: m.sessionId, cliSessionId: raw.cliSessionId });
|
|
22
|
+
await manager.warmSession(m.sessionId, { connectOnly: true });
|
|
23
|
+
check();
|
|
24
|
+
raw = await manager.getSessionWhenLoaded(m.sessionId, "topolo-launch-frontdoors", { warnOnMiss: false });
|
|
25
|
+
dto = await manager.getSession(m.sessionId);
|
|
26
|
+
check();
|
|
27
|
+
}
|
|
28
|
+
if (!raw?.query || raw.bypassAvailableAtSpawn !== true || raw.isRunning || raw.pendingCycle || raw.pendingToolPermissions?.length || manager.hasLosableWork(m.sessionId) || raw.cliSessionId !== (m.cliSessionId ?? raw.cliSessionId) || ["model", "effort", "permissionMode"].some((k) => dto[k] !== m[k])) throw Error("Coordinator effective startup permission is unverified.");
|
|
29
|
+
result.push({ key: m.key, sessionId: m.sessionId, cliSessionId: raw.cliSessionId, cwd: m.cwd, title: m.title, model: dto.model, effort: dto.effort, permissionMode: dto.permissionMode, pinned: true });
|
|
30
|
+
}
|
|
31
|
+
return { state: "installed-frontdoors-verified", members: result, nativeInputsSent: false };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/lib/claude-readiness-restart.ts
|
|
35
|
+
async function adoptClaudeReadinessHistory(manager, members, created, operationId, contextDigest, previousPid, currentPid, check, checkpoint, crypto, kill) {
|
|
36
|
+
if (!Number.isSafeInteger(previousPid) || previousPid < 1 || previousPid === currentPid) throw Error("Distinct original Desktop process required.");
|
|
37
|
+
try {
|
|
38
|
+
kill(previousPid, 0);
|
|
39
|
+
throw Error("Original Desktop process is still alive.");
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code !== "ESRCH") throw Error("Original Desktop process is not independently proven dead.");
|
|
42
|
+
}
|
|
43
|
+
const key = /* @__PURE__ */ Symbol.for("topolo.agent-pack.coordinator-bootstrap.v1"), old = manager[key];
|
|
44
|
+
if (old !== void 0 && !(old instanceof Map)) throw Error("Unknown native bootstrap custody.");
|
|
45
|
+
if (old?.size) throw Error("Existing native reservations require ordinary same-process continuation.");
|
|
46
|
+
const starts = manager.inFlightStarts;
|
|
47
|
+
if (!starts || ["hasStartInFlight", "hasStartAbortController", "hasDeferredTeardown", "pendingStartModel"].some((k) => typeof starts[k] !== "function") || !(manager.pendingPopSpawnsInFlight instanceof Map) || !(manager.sideSessionStartsInFlight instanceof Map) || typeof manager.warmSession !== "function") throw Error("Native restart custody capabilities unavailable.");
|
|
48
|
+
const receipts = [];
|
|
49
|
+
for (const member of members) {
|
|
50
|
+
check();
|
|
51
|
+
const id = member.sessionId;
|
|
52
|
+
if (starts.hasStartInFlight(id) || starts.hasStartAbortController(id) || starts.hasDeferredTeardown(id) || starts.pendingStartModel(id) !== void 0 || manager.pendingPopSpawnsInFlight.has(id) || manager.sideSessionStartsInFlight.has(id) || manager.hasLosableWork(id)) throw Error("Readiness identity has pending native custody.");
|
|
53
|
+
const raw = await manager.getSessionWhenLoaded(id, "topolo-readiness-restart", { warnOnMiss: false }), dto = await manager.getSession(id), history = await manager.getTranscript(id);
|
|
54
|
+
check();
|
|
55
|
+
if (!created.includes(id)) {
|
|
56
|
+
if (raw || dto || !Array.isArray(history) || history.length) throw Error("Uncreated readiness identity has existing history.");
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!raw || !dto || raw.sessionId !== id || dto.sessionId !== id || raw.cwd !== member.cwd || dto.cwd !== member.cwd || raw.title !== member.title || dto.title !== member.title || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.query || raw.spawnedFrom || raw.worktreePath || raw.worktreeLazy || raw.isArchived || dto.isArchived || raw.isRunning || dto.isRunning || raw.pendingCycle || raw.pendingToolPermissions?.length || raw.pendingFirstStart || raw.pendingSystemReminder || raw.interruptedAwaitingPerson || raw.queuedSideSessionNotes?.length || raw.pendingRewindTo || raw.stoppedUntilPersonSends || raw.armedWorkAtQuit || raw.interruptedByQuitAt || raw.remoteControlLiveAtUpdateQuit || raw.scheduledTaskId || raw.keptResent?.size || raw.heldAcrossLoss?.length || raw.queuedMessages?.length || raw.messageQueue?.length || raw.unreadInputUuids?.length || raw.heldQueuedSendUuids?.length || !Array.isArray(history) || ["model", "effort", "permissionMode"].some((k) => raw[k] !== member[k] || dto[k] !== member[k]) || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(raw.cliSessionId ?? "")) throw Error("Cold readiness identity, settings or native work differs.");
|
|
60
|
+
const users = history.filter((r) => r.type === "user"), assistants = history.filter((r) => r.type === "assistant"), workerId = id.replace(/^local_/, ""), content = users[0]?.message?.content;
|
|
61
|
+
const text = typeof content === "string" ? content : Array.isArray(content) && content.every((r) => r.type === "text") ? content.map((r) => r.text).join("") : null;
|
|
62
|
+
const answer = assistants[0]?.message;
|
|
63
|
+
if (users.length !== 1 || users[0].uuid !== workerId || text !== "Installation readiness probe only. Do not use tools, read task files, dispatch work or perform any task. Reply exactly TOPOLO_WORKER_READY:" + workerId || assistants.length !== 1 || answer?.model !== member.model || answer.stop_reason !== "end_turn" || !Array.isArray(answer.content) || answer.content.length !== 1 || answer.content[0].type !== "text" || answer.content[0].text !== "TOPOLO_WORKER_READY:" + workerId || !assistants[0].uuid || history.some((r) => r.type === "result" || r.type === "command_lifecycle" || r.type === "assistant" && r.message?.content?.some((c) => c.type === "tool_use"))) throw Error("Only one persisted fixed tool-free readiness exchange can be adopted.");
|
|
64
|
+
receipts.push({ kind: "coordinator-readiness-history-adopted", operationId, sessionId: id, cliSessionId: raw.cliSessionId, userUuid: workerId, assistantUuid: assistants[0].uuid, contextDigest, historyDigest: crypto.createHash("sha256").update(JSON.stringify(history)).digest("hex"), previousPid, currentPid, originalTerminalUnknown: true, nativeInputsReplayed: false });
|
|
65
|
+
}
|
|
66
|
+
check();
|
|
67
|
+
for (const receipt of receipts) checkpoint(receipt);
|
|
68
|
+
const records = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const row of receipts) records.set(row.sessionId, { state: "restart-adopted", operationId, cliSessionId: row.cliSessionId, historyAdopted: true });
|
|
70
|
+
manager[key] = records;
|
|
71
|
+
return receipts;
|
|
72
|
+
}
|
|
2
73
|
|
|
3
74
|
// src/lib/claude-pack-backend.ts
|
|
4
75
|
import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
|
|
@@ -1154,7 +1225,857 @@ async function nativeClaudeLaneControl(net, fs, manager, descriptor, token, invo
|
|
|
1154
1225
|
return { state: "private-native-lane-control-ready", ...descriptor };
|
|
1155
1226
|
}
|
|
1156
1227
|
|
|
1228
|
+
// src/lib/claude-native-capabilities.ts
|
|
1229
|
+
function discoverClaudeNativeCapabilities(load, resourcesPath, bypassAllowed) {
|
|
1230
|
+
const prefix = resourcesPath + "/app.asar/";
|
|
1231
|
+
const candidates = Object.values(load.cache ?? {}).filter((entry) => typeof entry?.filename === "string" && entry.filename.startsWith(prefix) && entry.exports?.claudeCodeSessionManager);
|
|
1232
|
+
const managers = [...new Set(candidates.map((entry) => entry.exports.claudeCodeSessionManager))];
|
|
1233
|
+
if (managers.length !== 1) throw new Error("Native session-manager discovery is unavailable or ambiguous.");
|
|
1234
|
+
const manager = managers[0];
|
|
1235
|
+
const required = [
|
|
1236
|
+
"getSession",
|
|
1237
|
+
"getSessionWhenLoaded",
|
|
1238
|
+
"getAvailableCodeModels",
|
|
1239
|
+
"hasLosableWork",
|
|
1240
|
+
"setModel",
|
|
1241
|
+
"setEffort",
|
|
1242
|
+
"setPermissionMode"
|
|
1243
|
+
];
|
|
1244
|
+
for (const name of required) {
|
|
1245
|
+
if (typeof manager[name] !== "function") throw new Error("Native settings capability missing: " + name);
|
|
1246
|
+
}
|
|
1247
|
+
const methods = Object.fromEntries(required.map((name) => [name, manager[name]]));
|
|
1248
|
+
const assertCurrent = () => {
|
|
1249
|
+
const current = Object.values(load.cache ?? {}).filter((entry) => typeof entry?.filename === "string" && entry.filename.startsWith(prefix) && entry.exports?.claudeCodeSessionManager);
|
|
1250
|
+
if (!current.length || current.some((entry) => entry.exports.claudeCodeSessionManager !== manager) || required.some((name) => manager[name] !== methods[name]))
|
|
1251
|
+
throw new Error("Native settings capabilities changed during the operation.");
|
|
1252
|
+
};
|
|
1253
|
+
return { manager, assertCurrent, bypassAllowed, evidence: {
|
|
1254
|
+
contract: "topolo-claude-native-settings-v1",
|
|
1255
|
+
modulePaths: candidates.map((entry) => entry.filename),
|
|
1256
|
+
required
|
|
1257
|
+
} };
|
|
1258
|
+
}
|
|
1259
|
+
function discoverClaudeSidebar(load, resourcesPath) {
|
|
1260
|
+
const find = () => {
|
|
1261
|
+
const functions = [...new Set(Object.values(load.cache ?? {}).filter((entry) => entry?.filename?.startsWith(resourcesPath + "/app.asar/")).flatMap((entry) => Object.values(entry.exports ?? {}).filter((value) => typeof value === "function")))];
|
|
1262
|
+
const send = functions.filter((fn) => fn.length === 2 && String(fn).length < 2e3 && String(fn).includes(".dispatchSidebarCommand("));
|
|
1263
|
+
const acknowledge = functions.filter((fn) => {
|
|
1264
|
+
const source = String(fn);
|
|
1265
|
+
return source.length < 2e3 && source.includes(".randomUUID)()") && source.includes("new Promise") && source.includes("setTimeout") && source.includes(".set(") && source.includes("{ok:!1,error:");
|
|
1266
|
+
});
|
|
1267
|
+
if (send.length !== 1 || acknowledge.length !== 1)
|
|
1268
|
+
throw new Error("Native acknowledged sidebar capability is unavailable or ambiguous.");
|
|
1269
|
+
return { send: send[0], acknowledge: acknowledge[0] };
|
|
1270
|
+
};
|
|
1271
|
+
const { send: sendCommand, acknowledge: awaitAcknowledgement } = find();
|
|
1272
|
+
return async (command) => {
|
|
1273
|
+
const current = find();
|
|
1274
|
+
if (current.send !== sendCommand || current.acknowledge !== awaitAcknowledgement)
|
|
1275
|
+
throw new Error("Native sidebar capabilities changed during the operation.");
|
|
1276
|
+
const result = await awaitAcknowledgement(command, sendCommand, 1e4);
|
|
1277
|
+
if (result?.ok !== true) throw new Error(result?.error || "Native sidebar pin acknowledgement refused.");
|
|
1278
|
+
return result;
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
async function applyClaudeNativeSettings(capabilities, members, operationId, deadline, checkpoint, verifyContext, apply, sidebar) {
|
|
1282
|
+
const { manager, assertCurrent } = capabilities;
|
|
1283
|
+
const pinAcknowledgements = /* @__PURE__ */ new Map();
|
|
1284
|
+
const fence = /* @__PURE__ */ Symbol.for("topolo.agent-pack.native-operation.v1");
|
|
1285
|
+
if (!operationId || !Number.isFinite(deadline) || !members.length || members.length > 100 || new Set(members.map((m) => m.sessionId)).size !== members.length || new Set(members.map((m) => m.key)).size !== members.length)
|
|
1286
|
+
throw new Error("Unique owned settings roster and operation identity required.");
|
|
1287
|
+
if (manager[fence]) throw new Error("Unresolved native operation; reconcile before mutation.");
|
|
1288
|
+
const check = () => {
|
|
1289
|
+
assertCurrent();
|
|
1290
|
+
if (Date.now() >= deadline) throw new Error("Native settings deadline expired.");
|
|
1291
|
+
verifyContext();
|
|
1292
|
+
if (members.some((m) => m.permissionMode === "bypassPermissions") && capabilities.bypassAllowed?.() !== true)
|
|
1293
|
+
throw new Error("Native app-level bypass permission is disabled or unverified; enable the account opt-in in Claude Settings.");
|
|
1294
|
+
};
|
|
1295
|
+
const read = async (member) => {
|
|
1296
|
+
check();
|
|
1297
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-agent-pack", { warnOnMiss: false });
|
|
1298
|
+
const dto = await manager.getSession(member.sessionId);
|
|
1299
|
+
check();
|
|
1300
|
+
for (const row of [raw, dto]) {
|
|
1301
|
+
if (!row || row.sessionId !== member.sessionId || row.cwd !== member.cwd || row.title !== member.title || row.isArchived || row.importedUnconfirmed === true)
|
|
1302
|
+
throw new Error("Owned native session identity changed: " + member.key);
|
|
1303
|
+
if (row.isRunning || row.starting || row.pendingToolPermissions?.length)
|
|
1304
|
+
throw new Error("Native session has active work: " + member.key);
|
|
1305
|
+
}
|
|
1306
|
+
if (manager.hasLosableWork(member.sessionId) || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.worktreePath || raw.worktreeLazy || raw.spawnedFrom)
|
|
1307
|
+
throw new Error("Native session is not an idle owned local coordinator: " + member.key);
|
|
1308
|
+
if (raw.query != null && dto.permissionMode === "bypassPermissions" && raw.bypassAvailableAtSpawn !== true)
|
|
1309
|
+
throw new Error("Bypass permissions were not available when this native process started: " + member.key);
|
|
1310
|
+
return {
|
|
1311
|
+
key: member.key,
|
|
1312
|
+
sessionId: member.sessionId,
|
|
1313
|
+
model: dto.model ?? null,
|
|
1314
|
+
effort: dto.effort ?? null,
|
|
1315
|
+
permissionMode: dto.permissionMode ?? null,
|
|
1316
|
+
...pinAcknowledgements.has(member.sessionId) ? { pinned: pinAcknowledgements.get(member.sessionId) } : {},
|
|
1317
|
+
processAttached: raw.query != null,
|
|
1318
|
+
startupPermissionVerified: raw.query != null && (dto.permissionMode !== "bypassPermissions" || raw.bypassAvailableAtSpawn === true)
|
|
1319
|
+
};
|
|
1320
|
+
};
|
|
1321
|
+
for (const member of members) {
|
|
1322
|
+
if (member.pinned !== void 0 && (typeof member.pinned !== "boolean" || !sidebar))
|
|
1323
|
+
throw new Error("Requested startup pinning requires the acknowledged native sidebar capability.");
|
|
1324
|
+
if (!/^local_[0-9a-f-]{36}$/.test(member.sessionId) || !member.key || !member.cwd.startsWith("/") || !member.title || !["low", "medium", "high"].includes(member.effort) || !["default", "acceptEdits", "bypassPermissions"].includes(member.permissionMode) || member.permissionMode === "bypassPermissions" && (!member.permissionConsent?.contextKey.startsWith("org:") || !member.permissionConsent.intakeKey))
|
|
1325
|
+
throw new Error("Unqualified native settings request.");
|
|
1326
|
+
check();
|
|
1327
|
+
if (!manager.getAvailableCodeModels().includes(member.model))
|
|
1328
|
+
throw new Error("Requested model absent from the native account catalog: " + member.model);
|
|
1329
|
+
}
|
|
1330
|
+
const before = [];
|
|
1331
|
+
for (const member of members) before.push(await read(member));
|
|
1332
|
+
checkpoint({ kind: "settings-baseline", operationId, before, evidence: capabilities.evidence, at: Date.now() });
|
|
1333
|
+
if (!apply) return { state: "settings-preflight", before, applied: false };
|
|
1334
|
+
if (manager[fence]) throw new Error("A concurrent native operation acquired the session manager.");
|
|
1335
|
+
manager[fence] = { operationId, running: true, effectAttempted: false };
|
|
1336
|
+
try {
|
|
1337
|
+
for (const member of members) {
|
|
1338
|
+
if (member.pinned !== void 0) {
|
|
1339
|
+
await read(member);
|
|
1340
|
+
checkpoint({ kind: "settings-pin-attempt", operationId, sessionId: member.sessionId, pinned: member.pinned, at: Date.now() });
|
|
1341
|
+
manager[fence].effectAttempted = true;
|
|
1342
|
+
const result = await sidebar({ kind: "set_pinned", sessionId: member.sessionId, pinned: member.pinned });
|
|
1343
|
+
if (result?.ok !== true) throw new Error("Native sidebar pin acknowledgement refused: " + member.key);
|
|
1344
|
+
pinAcknowledgements.set(member.sessionId, member.pinned);
|
|
1345
|
+
check();
|
|
1346
|
+
checkpoint({ kind: "settings-pin-acknowledged", operationId, sessionId: member.sessionId, pinned: member.pinned, at: Date.now() });
|
|
1347
|
+
}
|
|
1348
|
+
for (const field of ["model", "effort", "permissionMode"]) {
|
|
1349
|
+
const actual = await read(member);
|
|
1350
|
+
if (actual[field] === member[field]) continue;
|
|
1351
|
+
if (!manager.getAvailableCodeModels().includes(member.model)) throw new Error("Native model catalog changed.");
|
|
1352
|
+
checkpoint({ kind: "settings-attempt", operationId, sessionId: member.sessionId, field, value: member[field], at: Date.now() });
|
|
1353
|
+
manager[fence].effectAttempted = true;
|
|
1354
|
+
if (field === "model") await manager.setModel(member.sessionId, member.model);
|
|
1355
|
+
else if (field === "effort") await manager.setEffort(member.sessionId, member.effort);
|
|
1356
|
+
else if (!await manager.setPermissionMode(member.sessionId, member.permissionMode, "picker"))
|
|
1357
|
+
throw new Error("Native permission change refused: " + member.key);
|
|
1358
|
+
const after2 = await read(member);
|
|
1359
|
+
if (after2[field] !== member[field]) throw new Error("Native settings read-back mismatch: " + member.key + " / " + field);
|
|
1360
|
+
checkpoint({ kind: "settings-verified-field", operationId, after: after2, field, at: Date.now() });
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const after = [];
|
|
1364
|
+
for (const member of members) {
|
|
1365
|
+
const row = await read(member);
|
|
1366
|
+
if (row.model !== member.model || row.effort !== member.effort || row.permissionMode !== member.permissionMode)
|
|
1367
|
+
throw new Error("Final native settings read-back mismatch: " + member.key);
|
|
1368
|
+
after.push(row);
|
|
1369
|
+
}
|
|
1370
|
+
checkpoint({ kind: "settings-verified", operationId, after, at: Date.now() });
|
|
1371
|
+
delete manager[fence];
|
|
1372
|
+
return {
|
|
1373
|
+
state: "native-settings-verified",
|
|
1374
|
+
before,
|
|
1375
|
+
after,
|
|
1376
|
+
applied: true,
|
|
1377
|
+
allStartupPermissionsVerified: after.every((row) => row.startupPermissionVerified),
|
|
1378
|
+
pinsVerified: members.some((member) => member.pinned !== void 0) && members.every((member) => member.pinned === void 0 || pinAcknowledgements.get(member.sessionId) === member.pinned),
|
|
1379
|
+
dispatchVerified: false,
|
|
1380
|
+
sidebarVerified: false
|
|
1381
|
+
};
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
manager[fence].running = false;
|
|
1384
|
+
checkpoint({ kind: "settings-unresolved", operationId, error: String(error), at: Date.now() });
|
|
1385
|
+
throw error;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/lib/claude-desktop-control.ts
|
|
1390
|
+
async function installClaudeDesktopControl(manager, descriptor, token, modules, actions, authorize, qualify, checkpoint, options = {}) {
|
|
1391
|
+
const { net, fs, crypto, path } = modules;
|
|
1392
|
+
const key = /* @__PURE__ */ Symbol.for("topolo.agent-pack.desktop-task-control.v1");
|
|
1393
|
+
qualify();
|
|
1394
|
+
if (manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.desktop-coordinator-transfer.v1")]) throw new Error("Unresolved coordinator transfer; preserve custody.");
|
|
1395
|
+
if (descriptor.pid !== process.pid || !/^[a-f0-9]{64}$/.test(token) || !/^[a-f0-9]{64}$/.test(descriptor.identity) || !path.isAbsolute(descriptor.runtime)) throw new Error("Native task control ownership is unavailable.");
|
|
1396
|
+
const fence = /* @__PURE__ */ Symbol.for("topolo.agent-pack.desktop-task-control-replacement.v1");
|
|
1397
|
+
if (manager[fence]) throw new Error("Unresolved native task-control replacement; reconcile without replay.");
|
|
1398
|
+
const previous = manager[key];
|
|
1399
|
+
if (previous) {
|
|
1400
|
+
const existing = options.existing;
|
|
1401
|
+
if (!/^[a-f0-9]{64}$/.test(options.codeIdentity ?? "") || !options.existing || Object.keys(existing).sort().join() !== Object.keys(previous.descriptor ?? {}).sort().join() || Object.keys(existing).some((name) => existing[name] !== previous.descriptor[name]) || existing.pid !== process.pid || !/^[a-f0-9]{64}$/.test(options.existingToken ?? token) || previous.server?.address() !== existing.socket || previous.failed || previous.active !== 0 || descriptor.socket !== existing.socket && fs.existsSync(descriptor.socket))
|
|
1402
|
+
throw new Error("Existing native task control is not the exact owned idle connection.");
|
|
1403
|
+
if (!Array.isArray(options.coordinators) || !options.coordinators.length || options.coordinators.length > 6 || new Set(options.coordinators.map((row) => row.sessionId)).size !== options.coordinators.length)
|
|
1404
|
+
throw new Error("Exact idle coordinator roster required before native control replacement.");
|
|
1405
|
+
for (const member of options.coordinators) {
|
|
1406
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-control-upgrade", { warnOnMiss: false });
|
|
1407
|
+
const dto = await manager.getSession(member.sessionId);
|
|
1408
|
+
for (const row of [raw, dto]) if (!row || row.sessionId !== member.sessionId || row.cwd !== member.cwd || row.isRunning || row.starting || row.isArchived || row.pendingToolPermissions?.length)
|
|
1409
|
+
throw new Error("Coordinator has active or changed native custody.");
|
|
1410
|
+
if (manager.hasLosableWork(member.sessionId)) throw new Error("Coordinator has losable native work.");
|
|
1411
|
+
}
|
|
1412
|
+
let probeSocket;
|
|
1413
|
+
let connectionCount = 0;
|
|
1414
|
+
const observeConnection = (socket) => {
|
|
1415
|
+
connectionCount++;
|
|
1416
|
+
probeSocket ??= socket;
|
|
1417
|
+
};
|
|
1418
|
+
previous.server.on("connection", observeConnection);
|
|
1419
|
+
try {
|
|
1420
|
+
await new Promise((done, fail) => previous.server.getConnections((error, count) => error || count !== 0 || previous.active !== 0 || connectionCount !== 0 ? fail(error ?? new Error("Native task connection is active.")) : done()));
|
|
1421
|
+
const observed = await new Promise((done, fail) => {
|
|
1422
|
+
const client = net.createConnection(existing.socket);
|
|
1423
|
+
let bytes = "";
|
|
1424
|
+
let ended = false;
|
|
1425
|
+
const finish = (error, result) => {
|
|
1426
|
+
if (ended) return;
|
|
1427
|
+
ended = true;
|
|
1428
|
+
client.once("close", () => error ? fail(error) : done(result));
|
|
1429
|
+
client.destroy();
|
|
1430
|
+
};
|
|
1431
|
+
client.setEncoding("utf8");
|
|
1432
|
+
client.setTimeout(5e3, () => finish(new Error("Owned native status timed out.")));
|
|
1433
|
+
client.on("error", (error) => finish(error));
|
|
1434
|
+
client.on("connect", () => client.write(JSON.stringify({ pid: existing.pid, identity: existing.identity, token: options.existingToken ?? token, operation: { kind: "status" } }) + "\n"));
|
|
1435
|
+
client.on("data", (chunk) => {
|
|
1436
|
+
bytes += chunk;
|
|
1437
|
+
if (Buffer.byteLength(bytes) > 1048576) return finish(new Error("Owned native status exceeded bounds."));
|
|
1438
|
+
if (bytes.includes("\n")) {
|
|
1439
|
+
try {
|
|
1440
|
+
const value = JSON.parse(bytes);
|
|
1441
|
+
finish(value.error ? new Error(value.error) : null, value.result);
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
finish(error);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
if (observed.harness !== "claude-desktop" || observed.pid !== existing.pid || observed.identity !== existing.identity || observed.workers !== 0)
|
|
1449
|
+
throw new Error("Existing native workers retain their connection; replacement refused.");
|
|
1450
|
+
await new Promise((done, fail) => {
|
|
1451
|
+
if (connectionCount !== 1 || !probeSocket) return fail(new Error("Native task connection changed during status."));
|
|
1452
|
+
if (probeSocket.closed) return done();
|
|
1453
|
+
const timer = setTimeout(() => {
|
|
1454
|
+
probeSocket.removeListener("close", closed);
|
|
1455
|
+
fail(new Error("Native status socket retirement timed out."));
|
|
1456
|
+
}, 5e3);
|
|
1457
|
+
const closed = () => {
|
|
1458
|
+
clearTimeout(timer);
|
|
1459
|
+
done();
|
|
1460
|
+
};
|
|
1461
|
+
probeSocket.once("close", closed);
|
|
1462
|
+
});
|
|
1463
|
+
if (connectionCount !== 1 || previous.active !== 0) throw new Error("Native task connection changed during status.");
|
|
1464
|
+
qualify();
|
|
1465
|
+
if (previous.codeIdentity === options.codeIdentity && Object.keys(descriptor).every((name) => descriptor[name] === existing[name])) {
|
|
1466
|
+
if (observed.qualified === false) throw new Error("Existing native guard qualification changed.");
|
|
1467
|
+
checkpoint({ kind: "native-desktop-task-control-reused", ...descriptor, codeIdentity: options.codeIdentity });
|
|
1468
|
+
return { state: "native-desktop-task-control-reused", ...descriptor, codeIdentity: options.codeIdentity };
|
|
1469
|
+
}
|
|
1470
|
+
await new Promise((done, fail) => previous.server.getConnections((error, count) => {
|
|
1471
|
+
if (error || count !== 0 || previous.active !== 0 || connectionCount !== 1) return fail(error ?? new Error("Native task connection is active."));
|
|
1472
|
+
manager[fence] = { descriptor, previous: existing, codeIdentity: options.codeIdentity };
|
|
1473
|
+
try {
|
|
1474
|
+
checkpoint({ kind: "native-desktop-task-control-retiring", ...descriptor, codeIdentity: options.codeIdentity });
|
|
1475
|
+
} catch (error2) {
|
|
1476
|
+
delete manager[fence];
|
|
1477
|
+
return fail(error2);
|
|
1478
|
+
}
|
|
1479
|
+
previous.server.close((error2) => error2 ? fail(error2) : done());
|
|
1480
|
+
}));
|
|
1481
|
+
if (manager[key] !== previous || previous.active !== 0) throw new Error("Native control changed during retirement.");
|
|
1482
|
+
checkpoint({ kind: "native-desktop-task-control-retired", ...descriptor });
|
|
1483
|
+
delete manager[key];
|
|
1484
|
+
} finally {
|
|
1485
|
+
previous.server.removeListener("connection", observeConnection);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
if (fs.existsSync(descriptor.socket)) throw new Error("Native task control socket remains occupied.");
|
|
1489
|
+
const workers = /* @__PURE__ */ new Map();
|
|
1490
|
+
const control = { active: 0, server: null, descriptor, codeIdentity: options.codeIdentity ?? null };
|
|
1491
|
+
const invoke = async (op) => {
|
|
1492
|
+
if (control.failed) throw new Error("Native task control failed; preserve worker custody.");
|
|
1493
|
+
if (!op || typeof op.kind !== "string") throw new Error("Exact native worker operation required.");
|
|
1494
|
+
if (op.kind === "status" && Object.keys(op).join() === "kind") {
|
|
1495
|
+
let qualified = true;
|
|
1496
|
+
try {
|
|
1497
|
+
qualify();
|
|
1498
|
+
} catch {
|
|
1499
|
+
qualified = false;
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
harness: "claude-desktop",
|
|
1503
|
+
pid: descriptor.pid,
|
|
1504
|
+
identity: descriptor.identity,
|
|
1505
|
+
models: manager.getAvailableCodeModels(),
|
|
1506
|
+
workers: workers.size,
|
|
1507
|
+
codeIdentity: control.codeIdentity,
|
|
1508
|
+
qualified
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
qualify();
|
|
1512
|
+
if (op.kind === "frontdoors" && Object.keys(op).join() === "kind") {
|
|
1513
|
+
if (typeof actions.frontdoors !== "function" || !options.frontdoors) throw Error("Installed front-door proof unavailable.");
|
|
1514
|
+
return actions.frontdoors(manager, options.frontdoors, qualify, true, checkpoint);
|
|
1515
|
+
}
|
|
1516
|
+
const fields = op.kind === "create" ? ["kind", "plan", "files"] : op.kind === "send" ? ["kind", "workerId", "assignment"] : ["kind", "workerId", "messageId"];
|
|
1517
|
+
if (!["create", "read", "send", "archive"].includes(op.kind) || Object.keys(op).sort().join() !== fields.sort().join()) throw new Error("Unsupported native worker operation.");
|
|
1518
|
+
if (op.kind === "create") {
|
|
1519
|
+
const p2 = op.plan;
|
|
1520
|
+
if (!p2 || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(p2.workerId) || workers.has(p2.workerId) || p2.cwd !== path.join(descriptor.runtime, "task-workspaces", p2.workerId) || fs.realpathSync(p2.cwd) !== p2.cwd || !op.files || !Object.keys(op.files).length) throw new Error("Exact owned fresh worker context required.");
|
|
1521
|
+
const files = op.files;
|
|
1522
|
+
let outputsMayExist = false;
|
|
1523
|
+
const initial = /* @__PURE__ */ new Map();
|
|
1524
|
+
const identity = (file, directory) => {
|
|
1525
|
+
const st = fs.lstatSync(file);
|
|
1526
|
+
if (st.isSymbolicLink() || (directory ? !st.isDirectory() : !st.isFile()) || fs.realpathSync(file) !== file || st.uid !== process.getuid?.() || st.mode & 18 || !directory && st.nlink !== 1)
|
|
1527
|
+
throw new Error("Selected native worker context ownership or real path changed.");
|
|
1528
|
+
return { dev: st.dev, ino: st.ino, uid: st.uid, gid: st.gid, mode: st.mode, directory };
|
|
1529
|
+
};
|
|
1530
|
+
const remember = (file, directory) => {
|
|
1531
|
+
const now = identity(file, directory), before = initial.get(file);
|
|
1532
|
+
if (before && ["dev", "ino", "uid", "gid", "mode", "directory"].some((k) => now[k] !== before[k]))
|
|
1533
|
+
throw new Error("Selected native worker context ownership or identity changed.");
|
|
1534
|
+
if (!before) initial.set(file, now);
|
|
1535
|
+
};
|
|
1536
|
+
const verify = () => {
|
|
1537
|
+
qualify();
|
|
1538
|
+
remember(p2.cwd, true);
|
|
1539
|
+
if (!outputsMayExist) {
|
|
1540
|
+
const actual = [];
|
|
1541
|
+
const walk = (directory) => {
|
|
1542
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
1543
|
+
const item = path.join(directory, entry.name);
|
|
1544
|
+
if (directory === p2.cwd && entry.name === ".topolo-worker") continue;
|
|
1545
|
+
if (entry.isSymbolicLink()) throw new Error("Selected native worker context changed.");
|
|
1546
|
+
if (entry.isDirectory()) walk(item);
|
|
1547
|
+
else actual.push(item);
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
walk(p2.cwd);
|
|
1551
|
+
if (actual.sort().join("\n") !== Object.keys(files).sort().join("\n"))
|
|
1552
|
+
throw new Error("Selected native worker context inventory changed.");
|
|
1553
|
+
}
|
|
1554
|
+
for (const [file, hash] of Object.entries(files)) {
|
|
1555
|
+
if (!file.startsWith(p2.cwd + path.sep) || path.normalize(file) !== file || !/^[a-f0-9]{64}$/.test(String(hash)))
|
|
1556
|
+
throw new Error("Selected native worker context changed.");
|
|
1557
|
+
let directory = path.dirname(file);
|
|
1558
|
+
while (directory !== p2.cwd) {
|
|
1559
|
+
remember(directory, true);
|
|
1560
|
+
directory = path.dirname(directory);
|
|
1561
|
+
}
|
|
1562
|
+
remember(file, false);
|
|
1563
|
+
if (crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== hash)
|
|
1564
|
+
throw new Error("Selected native worker context changed.");
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
verify();
|
|
1568
|
+
workers.set(p2.workerId, { plan: p2, verify, assignment: null, allowOutputs: () => {
|
|
1569
|
+
outputsMayExist = true;
|
|
1570
|
+
} });
|
|
1571
|
+
try {
|
|
1572
|
+
if (await authorize(op) !== true) throw new Error("Cloud worker creation reservation was not verified.");
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
workers.delete(p2.workerId);
|
|
1575
|
+
throw error;
|
|
1576
|
+
}
|
|
1577
|
+
return actions.create(manager, p2, verify, checkpoint);
|
|
1578
|
+
}
|
|
1579
|
+
const worker = workers.get(op.workerId);
|
|
1580
|
+
if (!worker) throw new Error("This connection does not own the native worker.");
|
|
1581
|
+
const p = worker.plan;
|
|
1582
|
+
if (op.kind === "send") {
|
|
1583
|
+
if (worker.assignment !== null) throw new Error("Task input already reserved; reconcile without resend.");
|
|
1584
|
+
worker.assignment = op.assignment?.messageId;
|
|
1585
|
+
return actions.send(manager, p, op.assignment, () => authorize({ ...op, plan: p }), worker.verify, (row) => {
|
|
1586
|
+
checkpoint(row);
|
|
1587
|
+
if (row.kind === "worker-task-send-attempt") {
|
|
1588
|
+
if (row.workerId !== p.workerId || row.messageId !== worker.assignment || row.nativeId !== p.nativeId)
|
|
1589
|
+
throw new Error("Exact native send boundary checkpoint differs.");
|
|
1590
|
+
worker.allowOutputs();
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
const message = worker.assignment ?? p.workerId;
|
|
1595
|
+
if (op.messageId !== message) throw new Error("Exact currently owned input identity required.");
|
|
1596
|
+
if (op.kind === "archive") {
|
|
1597
|
+
const result2 = await actions.archive(
|
|
1598
|
+
manager,
|
|
1599
|
+
p,
|
|
1600
|
+
message,
|
|
1601
|
+
(result3) => authorize({ ...op, plan: p, terminal: result3 }),
|
|
1602
|
+
worker.verify,
|
|
1603
|
+
checkpoint
|
|
1604
|
+
);
|
|
1605
|
+
workers.delete(p.workerId);
|
|
1606
|
+
return result2;
|
|
1607
|
+
}
|
|
1608
|
+
const result = await actions.read(manager, p, message, worker.verify);
|
|
1609
|
+
const history = await manager.getTranscript(p.nativeId);
|
|
1610
|
+
const at = history.findIndex((r) => r.type === "user" && r.uuid === message);
|
|
1611
|
+
if (at < 0) return { ...result, messages: [], toolItems: 0 };
|
|
1612
|
+
const own = history.slice(at + 1).filter((r) => r.type === "assistant");
|
|
1613
|
+
const messages = own.flatMap((r) => (r.message?.content ?? []).filter((c) => c.type === "text").map((c) => ({ id: r.uuid, text: c.text })));
|
|
1614
|
+
const toolItems = own.reduce((count, r) => count + (r.message?.content ?? []).filter((c) => c.type === "tool_use").length, 0);
|
|
1615
|
+
return { ...result, messages, toolItems };
|
|
1616
|
+
};
|
|
1617
|
+
const server = net.createServer((connection) => {
|
|
1618
|
+
let buffer = "";
|
|
1619
|
+
let dispatched = false;
|
|
1620
|
+
connection.setEncoding("utf8");
|
|
1621
|
+
connection.setTimeout(5e3, () => connection.destroy());
|
|
1622
|
+
connection.on("error", () => connection.destroy());
|
|
1623
|
+
connection.on("data", async (chunk) => {
|
|
1624
|
+
if (dispatched) return;
|
|
1625
|
+
buffer += chunk;
|
|
1626
|
+
if (Buffer.byteLength(buffer) > 262144) {
|
|
1627
|
+
dispatched = true;
|
|
1628
|
+
connection.destroy();
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
if (!buffer.includes("\n")) return;
|
|
1632
|
+
dispatched = true;
|
|
1633
|
+
control.active++;
|
|
1634
|
+
try {
|
|
1635
|
+
const request = JSON.parse(buffer);
|
|
1636
|
+
if (Object.keys(request).sort().join() !== "identity,operation,pid,token" || request.token !== token || request.pid !== descriptor.pid || request.identity !== descriptor.identity)
|
|
1637
|
+
throw new Error("Native task connection authentication differs.");
|
|
1638
|
+
connection.setTimeout(6e4, () => connection.destroy());
|
|
1639
|
+
const result = await invoke(request.operation);
|
|
1640
|
+
const reply = JSON.stringify({ result }) + "\n";
|
|
1641
|
+
if (Buffer.byteLength(reply) > 1048576) throw new Error("Selected native result exceeds bounded transport.");
|
|
1642
|
+
connection.end(reply);
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
connection.end(JSON.stringify({ error: error instanceof Error ? error.message : "Native task refused." }) + "\n");
|
|
1645
|
+
} finally {
|
|
1646
|
+
control.active--;
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
1650
|
+
control.server = server;
|
|
1651
|
+
await new Promise((done, fail) => {
|
|
1652
|
+
server.once("error", fail);
|
|
1653
|
+
server.listen(descriptor.socket, done);
|
|
1654
|
+
});
|
|
1655
|
+
try {
|
|
1656
|
+
fs.chmodSync(descriptor.socket, 384);
|
|
1657
|
+
checkpoint({ kind: "native-desktop-task-control-installed", ...descriptor });
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
await new Promise((done) => server.close(() => done()));
|
|
1660
|
+
throw error;
|
|
1661
|
+
}
|
|
1662
|
+
manager[key] = control;
|
|
1663
|
+
delete manager[fence];
|
|
1664
|
+
server.on("error", () => {
|
|
1665
|
+
control.failed = true;
|
|
1666
|
+
});
|
|
1667
|
+
return { state: "native-desktop-task-control-installed", ...descriptor, codeIdentity: control.codeIdentity };
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/lib/claude-desktop-worker.ts
|
|
1671
|
+
async function createClaudeTaskSession(manager, plan, check, checkpoint) {
|
|
1672
|
+
check();
|
|
1673
|
+
const uuid = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/;
|
|
1674
|
+
if (!uuid.test(plan.taskId) || !uuid.test(plan.workerId) || plan.nativeId !== "local_" + plan.workerId || !plan.cwd.startsWith("/") || !plan.title || !/^[a-f0-9]{64}$/.test(plan.contextDigest) || !["low", "medium", "high"].includes(plan.effort) || plan.permissionMode !== "bypassPermissions")
|
|
1675
|
+
throw new Error("Exact preallocated task/session identity and context required.");
|
|
1676
|
+
for (const name of ["startSession", "getSession", "getSessionWhenLoaded", "getAvailableCodeModels", "getTranscript"])
|
|
1677
|
+
if (typeof manager[name] !== "function") throw new Error("Native worker capability missing: " + name);
|
|
1678
|
+
if (!manager.getAvailableCodeModels().includes(plan.model)) throw new Error("Requested worker model unavailable.");
|
|
1679
|
+
if (await manager.getSession(plan.nativeId)) throw new Error("Native task identity already exists; reconcile without creation replay.");
|
|
1680
|
+
const trust = manager.workspaceTrustChecks;
|
|
1681
|
+
if (typeof manager.checkWorkspaceTrust !== "function" || typeof manager.saveWorkspaceTrust !== "function" || typeof trust?.invalidateWorkspaceTrust !== "function" || typeof trust?.saveWorkspaceTrust !== "function" || !String(trust.saveWorkspaceTrust).includes("literalKeyOnly") || !String(trust.saveWorkspaceTrust).includes("onlyIf"))
|
|
1682
|
+
throw new Error("Native exact-directory workspace trust capability unavailable.");
|
|
1683
|
+
const methods = [manager.checkWorkspaceTrust, manager.saveWorkspaceTrust, trust.invalidateWorkspaceTrust, trust.saveWorkspaceTrust];
|
|
1684
|
+
const verifyTrust = () => {
|
|
1685
|
+
check();
|
|
1686
|
+
if (manager.workspaceTrustChecks !== trust || methods.some((method, i) => method !== [manager.checkWorkspaceTrust, manager.saveWorkspaceTrust, trust.invalidateWorkspaceTrust, trust.saveWorkspaceTrust][i]))
|
|
1687
|
+
throw new Error("Native workspace trust capability changed.");
|
|
1688
|
+
};
|
|
1689
|
+
verifyTrust();
|
|
1690
|
+
trust.invalidateWorkspaceTrust(plan.cwd);
|
|
1691
|
+
const beforeTrust = await manager.checkWorkspaceTrust(plan.cwd);
|
|
1692
|
+
verifyTrust();
|
|
1693
|
+
if (beforeTrust?.trusted !== true) {
|
|
1694
|
+
checkpoint({
|
|
1695
|
+
kind: "worker-workspace-trust-attempt",
|
|
1696
|
+
nativeId: plan.nativeId,
|
|
1697
|
+
cwd: plan.cwd,
|
|
1698
|
+
contextDigest: plan.contextDigest,
|
|
1699
|
+
literalKeyOnly: true,
|
|
1700
|
+
at: Date.now()
|
|
1701
|
+
});
|
|
1702
|
+
await manager.saveWorkspaceTrust(plan.cwd, {
|
|
1703
|
+
literalKeyOnly: true,
|
|
1704
|
+
keepCliImportScan: true,
|
|
1705
|
+
onlyIf: () => {
|
|
1706
|
+
verifyTrust();
|
|
1707
|
+
return true;
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
verifyTrust();
|
|
1711
|
+
trust.invalidateWorkspaceTrust(plan.cwd);
|
|
1712
|
+
if ((await manager.checkWorkspaceTrust(plan.cwd))?.trusted !== true)
|
|
1713
|
+
throw new Error("Native exact-directory workspace trust was not acknowledged.");
|
|
1714
|
+
verifyTrust();
|
|
1715
|
+
checkpoint({
|
|
1716
|
+
kind: "worker-workspace-trust-verified",
|
|
1717
|
+
nativeId: plan.nativeId,
|
|
1718
|
+
cwd: plan.cwd,
|
|
1719
|
+
contextDigest: plan.contextDigest,
|
|
1720
|
+
literalKeyOnly: true,
|
|
1721
|
+
at: Date.now()
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
check();
|
|
1725
|
+
checkpoint({ kind: "worker-create-attempt", ...plan, at: Date.now() });
|
|
1726
|
+
const nativeId = await manager.startSession(
|
|
1727
|
+
{
|
|
1728
|
+
sessionId: plan.nativeId,
|
|
1729
|
+
cwd: plan.cwd,
|
|
1730
|
+
title: plan.title,
|
|
1731
|
+
model: plan.model,
|
|
1732
|
+
effort: plan.effort,
|
|
1733
|
+
permissionMode: plan.permissionMode,
|
|
1734
|
+
useWorktree: false,
|
|
1735
|
+
messageUuid: plan.workerId,
|
|
1736
|
+
message: "Installation readiness probe only. Do not use tools, read task files, dispatch work or perform any task. Reply exactly TOPOLO_WORKER_READY:" + plan.workerId
|
|
1737
|
+
},
|
|
1738
|
+
{ initiator: "agent" }
|
|
1739
|
+
);
|
|
1740
|
+
if (nativeId !== plan.nativeId) throw new Error("Native worker returned another identity; preserve creation uncertainty.");
|
|
1741
|
+
check();
|
|
1742
|
+
const raw = await manager.getSessionWhenLoaded(nativeId, "topolo-task-startup", { warnOnMiss: false });
|
|
1743
|
+
const dto = await manager.getSession(nativeId);
|
|
1744
|
+
const transcript = await manager.getTranscript(nativeId);
|
|
1745
|
+
check();
|
|
1746
|
+
if (!raw || !dto || raw.sessionId !== nativeId || dto.sessionId !== nativeId || raw.cwd !== plan.cwd || dto.cwd !== plan.cwd || raw.title !== plan.title || dto.title !== plan.title || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.spawnedFrom || raw.worktreePath || raw.worktreeLazy || raw.isArchived || dto.isArchived || raw.pendingToolPermissions?.length || !Array.isArray(transcript) || transcript.some((entry) => entry.type === "user" && entry.uuid !== plan.workerId))
|
|
1747
|
+
throw new Error("Fresh native worker identity, placement or readiness history differs.");
|
|
1748
|
+
if (!raw.query || raw.bypassAvailableAtSpawn !== true || dto.permissionMode !== plan.permissionMode || dto.model !== plan.model || dto.effort !== plan.effort)
|
|
1749
|
+
throw new Error("Actual native worker startup settings were not acknowledged.");
|
|
1750
|
+
const result = {
|
|
1751
|
+
state: "native-worker-readiness-pending",
|
|
1752
|
+
nativeId,
|
|
1753
|
+
taskId: plan.taskId,
|
|
1754
|
+
workerId: plan.workerId,
|
|
1755
|
+
model: dto.model,
|
|
1756
|
+
effort: dto.effort,
|
|
1757
|
+
permissionMode: dto.permissionMode,
|
|
1758
|
+
contextDigest: plan.contextDigest,
|
|
1759
|
+
nativeHistoryEntries: transcript.length,
|
|
1760
|
+
readinessProbeSent: true,
|
|
1761
|
+
taskPromptSent: false
|
|
1762
|
+
};
|
|
1763
|
+
checkpoint({ kind: "worker-create-verified", ...result, at: Date.now() });
|
|
1764
|
+
return result;
|
|
1765
|
+
}
|
|
1766
|
+
async function readClaudeTaskResult(manager, plan, messageId, check) {
|
|
1767
|
+
check();
|
|
1768
|
+
const raw = await manager.getSessionWhenLoaded(plan.nativeId, "topolo-task-result", { warnOnMiss: false });
|
|
1769
|
+
const dto = await manager.getSession(plan.nativeId);
|
|
1770
|
+
const transcript = await manager.getTranscript(plan.nativeId);
|
|
1771
|
+
check();
|
|
1772
|
+
if (!raw || !dto || raw.sessionId !== plan.nativeId || dto.sessionId !== plan.nativeId || raw.cwd !== plan.cwd || dto.cwd !== plan.cwd || dto.title !== plan.title || raw.isArchived || dto.isArchived || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.spawnedFrom || dto.model !== plan.model || dto.effort !== plan.effort || dto.permissionMode !== plan.permissionMode || raw.bypassAvailableAtSpawn !== true || !Array.isArray(transcript))
|
|
1773
|
+
throw new Error("Native task result identity or startup settings differ.");
|
|
1774
|
+
if (raw.isRunning || raw.pendingCycle || raw.pendingToolPermissions?.length || manager.hasLosableWork(plan.nativeId))
|
|
1775
|
+
return { state: "native-task-pending" };
|
|
1776
|
+
const results = transcript.filter((r) => r.type === "result" && r.user_message_uuid === messageId);
|
|
1777
|
+
if (!raw.cliSessionId) {
|
|
1778
|
+
if (results.length) throw new Error("Native terminal SDK identity is missing; preserve result uncertainty.");
|
|
1779
|
+
if (!raw.query || !transcript.length || transcript.some((r) => r.type === "user" && r.uuid !== messageId))
|
|
1780
|
+
throw new Error("Native SDK startup is not an acknowledged pending input.");
|
|
1781
|
+
return { state: "native-task-pending" };
|
|
1782
|
+
}
|
|
1783
|
+
const completed = transcript.filter((r) => r.type === "command_lifecycle" && r.command_uuid === messageId && r.state === "completed");
|
|
1784
|
+
const users = transcript.filter((r) => r.type === "user" && r.uuid === messageId);
|
|
1785
|
+
const inputs = transcript.filter((r) => r.type === "user" && !(Array.isArray(r.message?.content) && r.message.content.length > 0 && r.message.content.every((c) => c.type === "tool_result" && typeof c.tool_use_id === "string")));
|
|
1786
|
+
if (inputs.length && inputs.at(-1)?.uuid !== messageId)
|
|
1787
|
+
throw new Error("Another input superseded this task receipt; preserve native custody.");
|
|
1788
|
+
if (results.length === 0) {
|
|
1789
|
+
if (!inputs.length && !transcript.some((r) => r.type === "command_lifecycle" && r.command_uuid === messageId && ["queued", "started"].includes(r.state)))
|
|
1790
|
+
throw new Error("Native startup input is not acknowledged.");
|
|
1791
|
+
return { state: "native-task-pending" };
|
|
1792
|
+
}
|
|
1793
|
+
if (inputs.at(-1)?.uuid !== messageId) throw new Error("Native terminal input is missing.");
|
|
1794
|
+
if (results.length !== 1 || completed.length !== 1 || users.length !== 1)
|
|
1795
|
+
throw new Error("Native task terminal evidence is missing or ambiguous.");
|
|
1796
|
+
const terminal = results[0];
|
|
1797
|
+
if (terminal.session_id !== raw.cliSessionId || completed[0].session_id !== raw.cliSessionId || terminal.user_message_uuids?.length !== 1 || terminal.user_message_uuids[0] !== messageId || terminal.terminal_reason !== "completed" || terminal.subtype !== "success" || terminal.is_error !== false || terminal.stop_reason !== "end_turn" || typeof terminal.result !== "string" || !terminal.uuid || terminal.permission_denials?.length || terminal.queued_turn_count !== 0 || terminal.heldQueuedSendUuids?.length || terminal.unreadInputUuids?.length || !terminal.modelUsage || Object.keys(terminal.modelUsage).length !== 1 || terminal.modelUsage[plan.model]?.canonicalModel !== plan.model)
|
|
1798
|
+
throw new Error("Native task terminal receipt does not prove the selected successful turn.");
|
|
1799
|
+
return {
|
|
1800
|
+
state: "native-task-completed",
|
|
1801
|
+
taskId: plan.taskId,
|
|
1802
|
+
workerId: plan.workerId,
|
|
1803
|
+
nativeId: plan.nativeId,
|
|
1804
|
+
messageId,
|
|
1805
|
+
cliSessionId: raw.cliSessionId,
|
|
1806
|
+
result: terminal.result,
|
|
1807
|
+
resultUuid: terminal.uuid,
|
|
1808
|
+
lifecycleUuid: completed[0].uuid,
|
|
1809
|
+
model: plan.model,
|
|
1810
|
+
effort: plan.effort,
|
|
1811
|
+
contextDigest: plan.contextDigest
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
async function sendClaudeTaskAssignment(manager, plan, assignment, claimDelivery, check, checkpoint) {
|
|
1815
|
+
check();
|
|
1816
|
+
if (!/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(assignment.messageId) || assignment.messageId === plan.workerId || !assignment.prompt.trim() || assignment.prompt.length > 1e5 || typeof manager.sendMessage !== "function")
|
|
1817
|
+
throw new Error("One bounded task assignment with a distinct exact input identity is required.");
|
|
1818
|
+
const ready = await readClaudeTaskResult(manager, plan, plan.workerId, check);
|
|
1819
|
+
if (ready.state !== "native-task-completed" || ready.result.trim() !== "TOPOLO_WORKER_READY:" + plan.workerId)
|
|
1820
|
+
throw new Error("The exact native readiness probe must complete before task delivery.");
|
|
1821
|
+
const transcript = await manager.getTranscript(plan.nativeId);
|
|
1822
|
+
if (transcript.some((entry) => entry.type === "assistant" && Array.isArray(entry.message?.content) && entry.message.content.some((item) => item.type === "tool_use")))
|
|
1823
|
+
throw new Error("Readiness used tools; preserve this worker for reconciliation.");
|
|
1824
|
+
check();
|
|
1825
|
+
const record = {
|
|
1826
|
+
taskId: plan.taskId,
|
|
1827
|
+
workerId: plan.workerId,
|
|
1828
|
+
nativeId: plan.nativeId,
|
|
1829
|
+
messageId: assignment.messageId,
|
|
1830
|
+
contextDigest: plan.contextDigest,
|
|
1831
|
+
model: plan.model,
|
|
1832
|
+
effort: plan.effort,
|
|
1833
|
+
prompt: assignment.prompt
|
|
1834
|
+
};
|
|
1835
|
+
if (await claimDelivery(record) !== true)
|
|
1836
|
+
throw new Error("Cloud delivery reservation was not acknowledged; no task was sent.");
|
|
1837
|
+
check();
|
|
1838
|
+
checkpoint({
|
|
1839
|
+
kind: "worker-task-send-attempt",
|
|
1840
|
+
taskId: plan.taskId,
|
|
1841
|
+
workerId: plan.workerId,
|
|
1842
|
+
nativeId: plan.nativeId,
|
|
1843
|
+
messageId: assignment.messageId,
|
|
1844
|
+
contextDigest: plan.contextDigest,
|
|
1845
|
+
at: Date.now()
|
|
1846
|
+
});
|
|
1847
|
+
await manager.sendMessage(
|
|
1848
|
+
plan.nativeId,
|
|
1849
|
+
assignment.prompt,
|
|
1850
|
+
void 0,
|
|
1851
|
+
{ messageUuid: assignment.messageId, initiator: "agent", origin: { kind: "task-notification" } }
|
|
1852
|
+
);
|
|
1853
|
+
check();
|
|
1854
|
+
const events = await manager.getTranscript(plan.nativeId);
|
|
1855
|
+
const inputs = events.filter((entry) => entry.type === "user" && entry.uuid === assignment.messageId);
|
|
1856
|
+
if (inputs.length !== 1)
|
|
1857
|
+
throw new Error("Native delivery is uncertain; reconcile the reserved input without replay.");
|
|
1858
|
+
checkpoint({
|
|
1859
|
+
kind: "worker-task-send-acknowledged",
|
|
1860
|
+
taskId: plan.taskId,
|
|
1861
|
+
workerId: plan.workerId,
|
|
1862
|
+
nativeId: plan.nativeId,
|
|
1863
|
+
messageId: assignment.messageId,
|
|
1864
|
+
at: Date.now()
|
|
1865
|
+
});
|
|
1866
|
+
return {
|
|
1867
|
+
state: "native-task-input-acknowledged",
|
|
1868
|
+
taskId: plan.taskId,
|
|
1869
|
+
workerId: plan.workerId,
|
|
1870
|
+
nativeId: plan.nativeId,
|
|
1871
|
+
messageId: assignment.messageId,
|
|
1872
|
+
completionVerified: false
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
async function archiveClaudeTaskSession(manager, plan, messageId, verifyCloudCheckpoint, check, checkpoint) {
|
|
1876
|
+
for (const key of ["archiveSession", "archiveCascadeClosureOf", "ensureArchivedSessionsLoaded"])
|
|
1877
|
+
if (typeof manager[key] !== "function") throw new Error("Native archive capability missing: " + key);
|
|
1878
|
+
const result = await readClaudeTaskResult(manager, plan, messageId, check);
|
|
1879
|
+
if (result.state !== "native-task-completed" || await verifyCloudCheckpoint(result) !== true)
|
|
1880
|
+
throw new Error("A verified cloud checkpoint is required before worker archival.");
|
|
1881
|
+
check();
|
|
1882
|
+
const raw = await manager.getSessionWhenLoaded(plan.nativeId, "topolo-worker-archive", { warnOnMiss: false });
|
|
1883
|
+
if (!raw || raw.isStarred || raw.isRunning || raw.pendingCycle || raw.pendingToolPermissions?.length || manager.hasLosableWork(plan.nativeId) || manager.archiveCascadeClosureOf(raw).length !== 0)
|
|
1884
|
+
throw new Error("Worker archival would affect pinned, active or child work.");
|
|
1885
|
+
checkpoint({ kind: "worker-archive-attempt", ...result, at: Date.now() });
|
|
1886
|
+
check();
|
|
1887
|
+
await manager.archiveSession(plan.nativeId, { isAutomatic: true });
|
|
1888
|
+
await manager.ensureArchivedSessionsLoaded("topolo-worker-archive");
|
|
1889
|
+
const archived = await manager.getSession(plan.nativeId);
|
|
1890
|
+
check();
|
|
1891
|
+
if (!archived || archived.sessionId !== plan.nativeId || archived.cwd !== plan.cwd || archived.title !== plan.title || archived.isArchived !== true)
|
|
1892
|
+
throw new Error("Native worker archival read-back failed; reconcile without replay.");
|
|
1893
|
+
const receipt = {
|
|
1894
|
+
state: "native-worker-archived",
|
|
1895
|
+
taskId: plan.taskId,
|
|
1896
|
+
workerId: plan.workerId,
|
|
1897
|
+
nativeId: plan.nativeId,
|
|
1898
|
+
messageId,
|
|
1899
|
+
resultUuid: result.resultUuid,
|
|
1900
|
+
contextDigest: plan.contextDigest
|
|
1901
|
+
};
|
|
1902
|
+
checkpoint({ kind: "worker-archive-verified", ...receipt, at: Date.now() });
|
|
1903
|
+
return receipt;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1157
1906
|
// src/lib/claude-pack-backend.ts
|
|
1907
|
+
function snapshotClaudeWorkerSource(root, fs, crypto, path, strict = false) {
|
|
1908
|
+
const files = {};
|
|
1909
|
+
const walk = (directory) => {
|
|
1910
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1911
|
+
const file = path.join(directory, entry.name);
|
|
1912
|
+
if (entry.isSymbolicLink()) throw new Error("Bundled worker source must not follow symlinks.");
|
|
1913
|
+
if (entry.isDirectory()) walk(file);
|
|
1914
|
+
else if (entry.isFile() && entry.name.endsWith(".py")) files[file] = crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
1915
|
+
else if (strict) throw new Error("Immutable worker guards may contain only verified Python source.");
|
|
1916
|
+
}
|
|
1917
|
+
};
|
|
1918
|
+
walk(path.join(root, "topolo_agent_pack"));
|
|
1919
|
+
return files;
|
|
1920
|
+
}
|
|
1921
|
+
function materializeClaudeWorkerGuard(root, source) {
|
|
1922
|
+
const inventory = snapshotClaudeWorkerSource(source, filesystem, cryptography, paths);
|
|
1923
|
+
if (!Object.keys(inventory).length) throw new Error("Bundled worker guard inventory is empty.");
|
|
1924
|
+
const relative2 = Object.fromEntries(Object.entries(inventory).map(([file, hash]) => [paths.relative(source, file), hash]));
|
|
1925
|
+
const generation = createHash2("sha256").update(JSON.stringify(relative2)).digest("hex");
|
|
1926
|
+
const destination = resolve(root, "claude/guard-runtimes", generation);
|
|
1927
|
+
for (const directory of [resolve(root, "claude"), resolve(root, "claude/guard-runtimes"), destination]) {
|
|
1928
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
1929
|
+
if (lstatSync(directory).isSymbolicLink() || realpathSync(directory) !== directory) throw new Error("Owned immutable guard directory required.");
|
|
1930
|
+
}
|
|
1931
|
+
for (const [file, hash] of Object.entries(relative2)) {
|
|
1932
|
+
const target = resolve(destination, file);
|
|
1933
|
+
mkdirSync(dirname(target), { recursive: true, mode: 448 });
|
|
1934
|
+
if (realpathSync(dirname(target)) !== dirname(target)) throw new Error("Guard namespace must not follow aliases.");
|
|
1935
|
+
if (existsSync(target)) {
|
|
1936
|
+
if (lstatSync(target).isSymbolicLink() || !lstatSync(target).isFile() || createHash2("sha256").update(readFileSync(target)).digest("hex") !== hash) throw new Error("Existing immutable worker guard changed.");
|
|
1937
|
+
} else {
|
|
1938
|
+
const fd = openSync(target, "wx", 384);
|
|
1939
|
+
try {
|
|
1940
|
+
writeFileSync(fd, readFileSync(resolve(source, file)));
|
|
1941
|
+
fsyncSync(fd);
|
|
1942
|
+
} finally {
|
|
1943
|
+
closeSync(fd);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
const expected = Object.fromEntries(Object.entries(relative2).map(([file, hash]) => [resolve(destination, file), hash]));
|
|
1948
|
+
if (JSON.stringify(snapshotClaudeWorkerSource(destination, filesystem, cryptography, paths, true)) !== JSON.stringify(expected)) throw new Error("Immutable worker guard inventory differs.");
|
|
1949
|
+
syncDirectory(destination);
|
|
1950
|
+
return destination;
|
|
1951
|
+
}
|
|
1952
|
+
function qualifyClaudePython(run = spawnSync) {
|
|
1953
|
+
const result = run("python3", ["-I", "-B", "-c", 'import sys,json;print(json.dumps({"executable":sys.executable,"version":list(sys.version_info[:3])}))'], { encoding: "utf8", timeout: 5e3 });
|
|
1954
|
+
if (result.error || result.status !== 0) throw new Error("Qualified Python interpreter unavailable.");
|
|
1955
|
+
const value = JSON.parse(String(result.stdout));
|
|
1956
|
+
if (!Array.isArray(value.version) || value.version.length !== 3 || value.version.some((v) => !Number.isInteger(v) || v < 0) || value.version[0] !== 3 || value.version[1] < 11 || typeof value.executable !== "string" || !paths.isAbsolute(value.executable))
|
|
1957
|
+
throw new Error("Python 3.11 or later interpreter capability required.");
|
|
1958
|
+
const executable = realpathSync(value.executable), stat = lstatSync(executable);
|
|
1959
|
+
if (!stat.isFile() || stat.isSymbolicLink() || ![0, process.getuid?.()].includes(stat.uid) || stat.mode & 18 || !(stat.mode & 73))
|
|
1960
|
+
throw new Error("Approved owned regular Python executable required.");
|
|
1961
|
+
return { executable, version: value.version, uid: stat.uid, sha256: createHash2("sha256").update(readFileSync(executable)).digest("hex") };
|
|
1962
|
+
}
|
|
1963
|
+
function nativeClaudeDesktopControlExpression(pid, root, descriptor, secretPath, journal, lifecycle = {}) {
|
|
1964
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1965
|
+
const bundled = [resolve(here, "../../agent-packs/src"), resolve(here, "../agent-packs/src")].find(existsSync);
|
|
1966
|
+
if (!bundled) throw new Error("Bundled native cloud authority helper unavailable.");
|
|
1967
|
+
const python = qualifyClaudePython();
|
|
1968
|
+
const source = materializeClaudeWorkerGuard(root, bundled);
|
|
1969
|
+
const inventory = snapshotClaudeWorkerSource(source, filesystem, cryptography, paths, true);
|
|
1970
|
+
const codeIdentity = createHash2("sha256").update(JSON.stringify({
|
|
1971
|
+
inventory,
|
|
1972
|
+
source,
|
|
1973
|
+
root,
|
|
1974
|
+
python,
|
|
1975
|
+
native: [installClaudeDesktopControl, observeClaudeFrontdoors, createClaudeTaskSession, readClaudeTaskResult, sendClaudeTaskAssignment, archiveClaudeTaskSession, discoverClaudeNativeCapabilities, snapshotClaudeWorkerSource].map((fn) => fn.toString())
|
|
1976
|
+
})).digest("hex");
|
|
1977
|
+
return `(async()=>{
|
|
1978
|
+
if(process.pid!==${pid})throw new Error('Desktop process changed.');
|
|
1979
|
+
const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');
|
|
1980
|
+
const fs=load('node:fs'),crypto=load('node:crypto'),path=load('node:path');
|
|
1981
|
+
const caps=(${discoverClaudeNativeCapabilities.toString()})(load,process.resourcesPath);
|
|
1982
|
+
const python=${JSON.stringify(python)};
|
|
1983
|
+
const qualify=()=>{caps.assertCurrent();const current=(${snapshotClaudeWorkerSource.toString()})(${JSON.stringify(source)},fs,crypto,path,true);
|
|
1984
|
+
const interpreter=fs.lstatSync(python.executable);
|
|
1985
|
+
if(!interpreter.isFile()||interpreter.isSymbolicLink()||interpreter.uid!==python.uid||(interpreter.mode&18)||fs.realpathSync(python.executable)!==python.executable||crypto.createHash('sha256').update(fs.readFileSync(python.executable)).digest('hex')!==python.sha256)throw new Error('Qualified Python executable changed.');
|
|
1986
|
+
if(JSON.stringify(current)!==${JSON.stringify(JSON.stringify(inventory))})throw new Error('Bundled worker guard source changed; native upgrade qualification required.');};
|
|
1987
|
+
const stat=fs.lstatSync(${JSON.stringify(secretPath)});
|
|
1988
|
+
if(!stat.isFile()||stat.isSymbolicLink()||stat.uid!==process.getuid()||(stat.mode&63))throw new Error('Unowned native control secret.');
|
|
1989
|
+
const token=JSON.parse(fs.readFileSync(${JSON.stringify(secretPath)},'utf8')).token;
|
|
1990
|
+
const lifecycle=${JSON.stringify({ ...lifecycle, codeIdentity })};
|
|
1991
|
+
if(lifecycle.existing){const previous=fs.lstatSync(lifecycle.existing.secretPath);
|
|
1992
|
+
if(!previous.isFile()||previous.isSymbolicLink()||previous.uid!==process.getuid()||(previous.mode&63))throw new Error('Previous native connection secret changed.');
|
|
1993
|
+
lifecycle.existingToken=JSON.parse(fs.readFileSync(lifecycle.existing.secretPath,'utf8')).token;}
|
|
1994
|
+
const authorize=operation=>new Promise((done,fail)=>{
|
|
1995
|
+
qualify();
|
|
1996
|
+
const script='import sys;assert list(sys.version_info[:3])=='+JSON.stringify(python.version)+';sys.path.insert(0,sys.argv[1]);from topolo_agent_pack.claude_desktop import main;sys.argv=[sys.argv[0],sys.argv[2]];sys.exit(main())';
|
|
1997
|
+
const child=load('node:child_process').spawn(python.executable,['-I','-B','-c',script,${JSON.stringify(source)},${JSON.stringify(root)}],
|
|
1998
|
+
{env:{...process.env,PYTHONDONTWRITEBYTECODE:'1',TOPOLO_AGENT_PACK_PUBLIC_CLI:'1'},stdio:['pipe','pipe','pipe']});
|
|
1999
|
+
let output='',ended=false;
|
|
2000
|
+
const finish=(error,value)=>{if(ended)return;ended=true;clearTimeout(timer);error?fail(error):done(value);};
|
|
2001
|
+
const timer=setTimeout(()=>{child.kill();finish(new Error('Native cloud reservation read timed out; no replay.'));},50000);
|
|
2002
|
+
child.on('error',error=>finish(error));
|
|
2003
|
+
child.stdin.on('error',error=>finish(error));
|
|
2004
|
+
child.stdout.on('data',chunk=>{output+=String(chunk);if(output.length>16384){child.kill();finish(new Error('Native authority response exceeded boundary.'));}});
|
|
2005
|
+
child.stderr.resume();
|
|
2006
|
+
child.on('close',code=>{try{finish(code===0?undefined:new Error('Cloud reservation was not verified.'),code===0&&JSON.parse(output).authorized===true);}catch(error){finish(error);}});
|
|
2007
|
+
child.stdin.end(JSON.stringify(operation));
|
|
2008
|
+
});
|
|
2009
|
+
const checkpoint=row=>{const fd=fs.openSync(${JSON.stringify(journal)},'a',384);try{fs.writeSync(fd,JSON.stringify(row)+'\\n');fs.fsyncSync(fd);}finally{fs.closeSync(fd);}};
|
|
2010
|
+
const readClaudeTaskResult=(${readClaudeTaskResult.toString()});
|
|
2011
|
+
qualify();checkpoint({kind:'native-python-capability-qualified',...python});
|
|
2012
|
+
return (${installClaudeDesktopControl.toString()})(caps.manager,${JSON.stringify(descriptor)},token,
|
|
2013
|
+
{net:load('node:net'),fs,crypto,path},
|
|
2014
|
+
{frontdoors:(${observeClaudeFrontdoors.toString()}),create:(${createClaudeTaskSession.toString()}),read:readClaudeTaskResult,
|
|
2015
|
+
send:(${sendClaudeTaskAssignment.toString()}),archive:(${archiveClaudeTaskSession.toString()})},
|
|
2016
|
+
authorize,qualify,checkpoint,lifecycle);
|
|
2017
|
+
})()`;
|
|
2018
|
+
}
|
|
2019
|
+
function nativeClaudeSettingsExpression(pid, plan, journal, apply) {
|
|
2020
|
+
if (!Number.isSafeInteger(pid) || pid < 1) throw new Error("Explicit Claude main-process PID required.");
|
|
2021
|
+
const startupPlan = { ...plan, members: plan.members.map((member) => ({
|
|
2022
|
+
...member,
|
|
2023
|
+
.../\/(intakes|coordinators)\//.test(member.key) ? { pinned: true } : {}
|
|
2024
|
+
})) };
|
|
2025
|
+
return `(async () => {
|
|
2026
|
+
if (process.pid !== ${pid} || typeof process.getBuiltinModule !== 'function') throw new Error('Claude process identity changed.');
|
|
2027
|
+
const load = process.getBuiltinModule('module').createRequire(process.resourcesPath + '/app.asar/package.json');
|
|
2028
|
+
const app = load('electron').app;
|
|
2029
|
+
if (app.getName() !== 'Claude') throw new Error('The supplied process is not Claude Desktop.');
|
|
2030
|
+
const fs = load('node:fs'), crypto = load('node:crypto'), path = load('node:path');
|
|
2031
|
+
// Locate the loaded native eligibility predicate by its preference contract,
|
|
2032
|
+
// not by a generated bundle filename, export alias, version or hash.
|
|
2033
|
+
const gates = [...new Set(Object.values(load.cache ?? {}).filter(entry =>
|
|
2034
|
+
entry.filename?.startsWith(process.resourcesPath + '/app.asar/')).flatMap(entry =>
|
|
2035
|
+
Object.keys(entry.exports ?? {}).flatMap(key => {
|
|
2036
|
+
let value; try {value = entry.exports[key];} catch {return [];}
|
|
2037
|
+
return typeof value === 'function' && value.length === 0 &&
|
|
2038
|
+
String(value).includes('bypassPermissionsGateByAccount') ? [value] : [];
|
|
2039
|
+
})))];
|
|
2040
|
+
const bypassAllowed = () => {
|
|
2041
|
+
if (gates.length !== 1) throw new Error('Native app-level bypass eligibility capability is unavailable or ambiguous.');
|
|
2042
|
+
return gates[0]() === true;
|
|
2043
|
+
};
|
|
2044
|
+
const caps = (${discoverClaudeNativeCapabilities.toString()})(load, process.resourcesPath, bypassAllowed);
|
|
2045
|
+
if (${JSON.stringify(plan.desktopProfile ?? null)}) (${verifyClaudeDesktopProfileNative.toString()})(${JSON.stringify(plan.desktopProfile ?? null)},process.pid,app,caps.manager,process.env);
|
|
2046
|
+
caps.evidence.appVersion = app.getVersion();
|
|
2047
|
+
caps.evidence.methodHashes = Object.fromEntries(caps.evidence.required.map(name =>
|
|
2048
|
+
[name, crypto.createHash('sha256').update(String(caps.manager[name])).digest('hex')]));
|
|
2049
|
+
const plan = ${JSON.stringify(startupPlan)};
|
|
2050
|
+
const sidebar = plan.members.some(member => member.pinned !== undefined) ?
|
|
2051
|
+
(${discoverClaudeSidebar.toString()})(load, process.resourcesPath) : undefined;
|
|
2052
|
+
const snapshotMarkdown = (${snapshotClaudeMarkdown.toString()});
|
|
2053
|
+
const verify = () => {if(plan.desktopProfile){(${verifyClaudeDesktopProfileNative.toString()})(plan.desktopProfile,process.pid,app,caps.manager,process.env);if(!plan.desktopProfileReceipt||crypto.createHash('sha256').update(fs.readFileSync(plan.desktopProfileReceipt.file)).digest('hex')!==plan.desktopProfileReceipt.digest)throw Error('Managed profile receipt changed.');}(${verifyClaudePlanContext.toString()})(plan, plan.members, fs, crypto, path, snapshotMarkdown);};
|
|
2054
|
+
const checkpoint = value => {
|
|
2055
|
+
const fd = fs.openSync(${JSON.stringify(journal)}, 'a', 384);
|
|
2056
|
+
try {fs.writeSync(fd, JSON.stringify(value) + '\\n'); fs.fsyncSync(fd);} finally {fs.closeSync(fd);}
|
|
2057
|
+
};
|
|
2058
|
+
if (${apply} && plan.previousCoordinators?.length)await (${verifyClaudePreviousCoordinatorCustody.toString()})(caps.manager,plan.previousCoordinators,()=>{caps.assertCurrent();verify();});
|
|
2059
|
+
if (${apply} && plan.restartReadiness && !caps.manager[Symbol.for('topolo.agent-pack.coordinator-bootstrap.v1')])await (${adoptClaudeReadinessHistory.toString()})(caps.manager,plan.members,plan.readinessContinuation,plan.operationId,crypto.createHash('sha256').update(JSON.stringify(plan.context)).digest('hex'),plan.restartReadiness.previousPid,process.pid,()=>{caps.assertCurrent();verify();},checkpoint,crypto,(pid,signal)=>process.kill(pid,signal));
|
|
2060
|
+
if (${apply} && plan.readinessContinuation)await (${verifyClaudeReadinessContinuation.toString()})(caps.manager,plan.members,plan.readinessContinuation,plan.operationId,()=>{caps.assertCurrent();verify();});
|
|
2061
|
+
if (${apply})await (${ensureClaudeCoordinators.toString()})(caps.manager,plan.members,plan.operationId,
|
|
2062
|
+
crypto.createHash('sha256').update(JSON.stringify(plan.context)).digest('hex'),
|
|
2063
|
+
{create:(${createClaudeTaskSession.toString()}),read:(${readClaudeTaskResult.toString()})},
|
|
2064
|
+
()=>{caps.assertCurrent();verify();if(plan.members.some(m=>m.permissionMode==='bypassPermissions')&&!bypassAllowed())throw new Error('Native bypass eligibility changed.');},checkpoint,Date.now()+45000,plan.readinessContinuation,plan.readinessAcknowledged);
|
|
2065
|
+
const result = await (${applyClaudeNativeSettings.toString()})(caps, plan.members, plan.operationId,
|
|
2066
|
+
Date.now() + 45000, checkpoint, verify, ${apply}, sidebar);
|
|
2067
|
+
if (${apply} && result.state === 'native-settings-verified') {
|
|
2068
|
+
result.frontdoorSessions = [];
|
|
2069
|
+
for (const member of plan.members) {
|
|
2070
|
+
const raw = await caps.manager.getSessionWhenLoaded(member.sessionId, 'topolo-frontdoor-hook-binding', {warnOnMiss:false});
|
|
2071
|
+
if (!raw?.query || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(raw.cliSessionId ?? '') || raw.cwd !== member.cwd)
|
|
2072
|
+
throw new Error('Actual coordinator SDK session binding is unavailable; preserve startup custody.');
|
|
2073
|
+
result.frontdoorSessions.push({key:member.key,sessionId:raw.cliSessionId,cwd:raw.cwd});
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return result;
|
|
2077
|
+
})()`;
|
|
2078
|
+
}
|
|
1158
2079
|
var CLAUDE_NATIVE_QUALIFIER = {
|
|
1159
2080
|
version: "2.37337.1",
|
|
1160
2081
|
manager: "index.chunk-BolytlRP.js",
|
|
@@ -1190,7 +2111,7 @@ function snapshotClaudeMarkdown(root, fs, crypto, path) {
|
|
|
1190
2111
|
visit(root);
|
|
1191
2112
|
return inventory;
|
|
1192
2113
|
}
|
|
1193
|
-
function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
|
|
2114
|
+
function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure", nativeAdoption = false) {
|
|
1194
2115
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(pack)) throw new Error("Invalid pack name.");
|
|
1195
2116
|
const root = realpathSync(store);
|
|
1196
2117
|
const state = JSON.parse(readFileSync(resolve(root, "claude/state.json"), "utf8"));
|
|
@@ -1209,7 +2130,7 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
|
|
|
1209
2130
|
};
|
|
1210
2131
|
for (const record of records) {
|
|
1211
2132
|
const matches = policy.agents.filter((a) => a.key === record.key && a.title === record.title);
|
|
1212
|
-
if (matches.length !== 1 || record.sourceRevision !== policy.sourceRevision || record.importState !== "desktop-adopted")
|
|
2133
|
+
if (matches.length !== 1 || record.sourceRevision !== policy.sourceRevision || record.importState !== "desktop-adopted" && !(nativeAdoption && record.importState === "prepared"))
|
|
1213
2134
|
throw new Error("Reviewed policy does not match adopted role: " + record.key);
|
|
1214
2135
|
const role = matches[0];
|
|
1215
2136
|
const settings = role.harnesses.claude.nativeSettings;
|
|
@@ -1217,10 +2138,13 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
|
|
|
1217
2138
|
if (!cwd.startsWith(resolve(root, "claude/workspaces", pack) + sep)) throw new Error("Role workspace is outside the owned pack.");
|
|
1218
2139
|
captureMarkdown(cwd);
|
|
1219
2140
|
const charter = resolve(cwd, "CLAUDE.md");
|
|
1220
|
-
const transcript = realpathSync(record.transcript);
|
|
1221
2141
|
context[charter] = createHash2("sha256").update(readFileSync(charter)).digest("hex");
|
|
1222
|
-
|
|
1223
|
-
|
|
2142
|
+
roleContextFiles[record.desktopSessionId] = [charter];
|
|
2143
|
+
if (!nativeAdoption || record.importState !== "prepared") {
|
|
2144
|
+
const transcript = realpathSync(record.transcript);
|
|
2145
|
+
context[transcript] = createHash2("sha256").update(readFileSync(transcript)).digest("hex");
|
|
2146
|
+
roleContextFiles[record.desktopSessionId].push(transcript);
|
|
2147
|
+
}
|
|
1224
2148
|
members.push({
|
|
1225
2149
|
key: record.key,
|
|
1226
2150
|
sessionId: record.desktopSessionId,
|
|
@@ -1461,9 +2385,10 @@ function evaluateClaudeMain(endpoint, expression, socketFactory = (url) => new W
|
|
|
1461
2385
|
socket.addEventListener("close", () => finish(new Error("Claude backend disconnected; reconcile the journal.")));
|
|
1462
2386
|
});
|
|
1463
2387
|
}
|
|
1464
|
-
async function lockClaudeLibrary(root) {
|
|
2388
|
+
async function lockClaudeLibrary(root, stoppedClaudeService = false) {
|
|
1465
2389
|
const script = 'import os,sys,fcntl\nf=os.open(sys.argv[1],os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)\nfcntl.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB)\nprint("locked",flush=True)\nsys.stdin.buffer.read()\nos.close(f)';
|
|
1466
|
-
const
|
|
2390
|
+
const serviceScript = 'import os,sys,fcntl\nfiles=[]\nfor name in sys.argv[1:]:\n f=os.open(name,os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)\n fcntl.flock(f,fcntl.LOCK_EX|fcntl.LOCK_NB)\n files.append(f)\nprint("locked",flush=True)\nsys.stdin.buffer.read()\nfor f in files: os.close(f)';
|
|
2391
|
+
const child = spawn("python3", ["-c", stoppedClaudeService ? serviceScript : script, resolve(root, ".lock"), ...stoppedClaudeService ? [resolve(root, "work/claude-service-start.lock"), resolve(root, "work/claude-service-owner.lock")] : []], { stdio: ["pipe", "pipe", "pipe"] });
|
|
1467
2392
|
await new Promise((done, fail) => {
|
|
1468
2393
|
let ready = false;
|
|
1469
2394
|
const timer = setTimeout(() => {
|
|
@@ -1535,11 +2460,15 @@ function verifyClaudeOrganizationPolicy(root, pack, consent, run = spawnSync, lo
|
|
|
1535
2460
|
return proof;
|
|
1536
2461
|
}
|
|
1537
2462
|
async function configureClaudePack(options, evaluate = evaluateClaudeMain, qualify = verifyClaudeOrganizationPolicy) {
|
|
2463
|
+
if (!options.legacy && (!options.mode || options.mode === "configure"))
|
|
2464
|
+
return configureClaudeStartup(options, evaluate, qualify);
|
|
1538
2465
|
validateInspectorEndpoint(options.endpoint);
|
|
1539
2466
|
const root = realpathSync(options.store);
|
|
1540
2467
|
if (lstatSync(resolve(root, "claude")).isSymbolicLink()) throw new Error("Claude library must not be a symlink.");
|
|
1541
2468
|
const release = await lockClaudeLibrary(root);
|
|
1542
2469
|
try {
|
|
2470
|
+
if (existsSync(resolve(root, "claude/startup-operation.json")))
|
|
2471
|
+
throw new Error("Unresolved native startup operation requires its own readback reconciliation.");
|
|
1543
2472
|
const markerPath = resolve(root, "claude/native-operation.json");
|
|
1544
2473
|
const recovering = options.mode === "recover";
|
|
1545
2474
|
if (recovering && options.apply) throw new Error("Recovery is read-only; --apply is not permitted.");
|
|
@@ -1738,6 +2667,692 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
|
|
|
1738
2667
|
await release();
|
|
1739
2668
|
}
|
|
1740
2669
|
}
|
|
2670
|
+
function materializeClaudeHookAdapter(root, adapter) {
|
|
2671
|
+
const source = resolve(dirname(adapter), "src"), inventory = snapshotClaudeWorkerSource(source, filesystem, cryptography, paths);
|
|
2672
|
+
const bytes = { "topolo-agent-pack": readFileSync(adapter) };
|
|
2673
|
+
for (const file of Object.keys(inventory)) bytes["src/" + paths.relative(source, file)] = readFileSync(file);
|
|
2674
|
+
const hashes = Object.fromEntries(Object.entries(bytes).map(([name, data]) => [name, createHash2("sha256").update(data).digest("hex")]));
|
|
2675
|
+
const digest = createHash2("sha256").update(JSON.stringify(hashes)).digest("hex"), destination = resolve(root, "claude/hook-runtimes", digest);
|
|
2676
|
+
for (const directory of [resolve(root, "claude"), resolve(root, "claude/hook-runtimes"), destination]) {
|
|
2677
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2678
|
+
const st = lstatSync(directory);
|
|
2679
|
+
if (st.isSymbolicLink() || !st.isDirectory() || st.uid !== process.getuid?.() || st.mode & 63 || realpathSync(directory) !== directory) throw Error("Private owned hook runtime required.");
|
|
2680
|
+
}
|
|
2681
|
+
const expected = {};
|
|
2682
|
+
for (const [name, data] of Object.entries(bytes)) {
|
|
2683
|
+
const target = resolve(destination, name);
|
|
2684
|
+
mkdirSync(dirname(target), { recursive: true, mode: 448 });
|
|
2685
|
+
if (realpathSync(dirname(target)) !== dirname(target)) throw Error("Hook runtime namespace follows an alias.");
|
|
2686
|
+
if (existsSync(target)) {
|
|
2687
|
+
const st = lstatSync(target);
|
|
2688
|
+
if (!st.isFile() || st.isSymbolicLink() || st.uid !== process.getuid?.() || st.mode & 18 || createHash2("sha256").update(readFileSync(target)).digest("hex") !== hashes[name]) throw Error("Immutable hook adapter changed.");
|
|
2689
|
+
} else {
|
|
2690
|
+
const fd = openSync(target, "wx", 384);
|
|
2691
|
+
try {
|
|
2692
|
+
writeFileSync(fd, data);
|
|
2693
|
+
fsyncSync(fd);
|
|
2694
|
+
} finally {
|
|
2695
|
+
closeSync(fd);
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
expected[target] = hashes[name];
|
|
2699
|
+
}
|
|
2700
|
+
syncDirectory(destination);
|
|
2701
|
+
return { adapter: resolve(destination, "topolo-agent-pack"), source: resolve(destination, "src"), files: expected, digest };
|
|
2702
|
+
}
|
|
2703
|
+
function bindClaudeFrontdoorHooks(root, pid, roles, input, launch = spawnSync, nativeConnection, reuseInput = false) {
|
|
2704
|
+
if (!Array.isArray(roles) || roles.length !== 3) throw new Error("The native three-coordinator SDK bindings are unavailable.");
|
|
2705
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
2706
|
+
const adapter = [resolve(here, "../agent-packs/topolo-agent-pack"), resolve(here, "../../agent-packs/topolo-agent-pack")].find(existsSync);
|
|
2707
|
+
if (!adapter) throw new Error("Installed bundled pack adapter unavailable.");
|
|
2708
|
+
if (!nativeConnection) throw new Error("The signed-in Desktop task control must be installed before front-door hooks.");
|
|
2709
|
+
const payload = { desktopPid: pid, roles, nativeConnection };
|
|
2710
|
+
if (reuseInput) {
|
|
2711
|
+
const info = lstatSync(input);
|
|
2712
|
+
if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || info.mode & 63 || JSON.stringify(JSON.parse(readFileSync(input, "utf8"))) !== JSON.stringify(payload)) throw Error("Original front-door attempt differs; preserve late startup custody.");
|
|
2713
|
+
} else durableRecord(input, payload, "wx");
|
|
2714
|
+
const frozen = materializeClaudeHookAdapter(root, adapter), python = qualifyClaudePython(), owned = lstatSync(root);
|
|
2715
|
+
const before = { dev: owned.dev, ino: owned.ino, uid: owned.uid }, inputDigest = createHash2("sha256").update(readFileSync(input)).digest("hex");
|
|
2716
|
+
const script = `import sys,json,pathlib,hashlib,os,stat
|
|
2717
|
+
p=json.loads(sys.stdin.read(1048577))
|
|
2718
|
+
def verify():
|
|
2719
|
+
assert sys.version_info[:3]==tuple(p['python']['version']), 'Qualified hook interpreter changed'
|
|
2720
|
+
assert hashlib.sha256(pathlib.Path(sys.executable).read_bytes()).hexdigest()==p['python']['sha256'], 'Hook interpreter bytes changed'
|
|
2721
|
+
root=pathlib.Path(p['root']);s=root.stat()
|
|
2722
|
+
assert root.resolve()==root and {'dev':s.st_dev,'ino':s.st_ino,'uid':s.st_uid}==p['rootIdentity'], 'Hook installation root changed'
|
|
2723
|
+
for name,digest in p['files'].items():
|
|
2724
|
+
file=pathlib.Path(name);st=file.lstat()
|
|
2725
|
+
assert stat.S_ISREG(st.st_mode) and file.resolve()==file and st.st_uid==os.getuid() and not st.st_mode&0o022 and hashlib.sha256(file.read_bytes()).hexdigest()==digest, 'Frozen hook source changed'
|
|
2726
|
+
file=pathlib.Path(p['input']);st=file.lstat()
|
|
2727
|
+
assert stat.S_ISREG(st.st_mode) and file.resolve()==file and st.st_uid==os.getuid() and not st.st_mode&0o077 and hashlib.sha256(file.read_bytes()).hexdigest()==p['inputDigest'], 'Original hook input changed'
|
|
2728
|
+
try:
|
|
2729
|
+
verify();sys.path.insert(0,p['source'])
|
|
2730
|
+
from topolo_agent_pack.claude_service import install
|
|
2731
|
+
result=install(p['root'],json.loads(pathlib.Path(p['input']).read_text()))
|
|
2732
|
+
verify();print(json.dumps(result))
|
|
2733
|
+
except Exception as error:
|
|
2734
|
+
print('topolo-agent-pack: '+str(error),file=sys.stderr);raise SystemExit(2)
|
|
2735
|
+
`;
|
|
2736
|
+
const bound = launch(
|
|
2737
|
+
python.executable,
|
|
2738
|
+
["-I", "-B", "-c", script],
|
|
2739
|
+
{ encoding: "utf8", input: JSON.stringify({ root, input, inputDigest, source: frozen.source, files: frozen.files, rootIdentity: before, python }), env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1" }, timeout: 6e5, maxBuffer: 1048576 }
|
|
2740
|
+
);
|
|
2741
|
+
const current = lstatSync(root);
|
|
2742
|
+
if (current.dev !== before.dev || current.ino !== before.ino || current.uid !== before.uid || realpathSync(root) !== root) throw Error("Hook installation root identity changed.");
|
|
2743
|
+
for (const [file, hash] of Object.entries(frozen.files)) if (createHash2("sha256").update(readFileSync(file)).digest("hex") !== hash) throw Error("Frozen hook source changed during installation.");
|
|
2744
|
+
if (bound.error || bound.status !== 0) {
|
|
2745
|
+
const diagnostic = input + ".failure-" + randomUUID() + ".json", errorCode = bound.error?.code ?? null;
|
|
2746
|
+
const publicError = String(bound.stderr ?? "").split("\n").find((line) => /^topolo-agent-pack: [^\r\n]{1,1000}$/.test(line));
|
|
2747
|
+
durableRecord(diagnostic, { kind: "frontdoor-hook-installation-refused", status: bound.status, signal: bound.signal, errorCode, stderrSHA256: createHash2("sha256").update(String(bound.stderr ?? "")).digest("hex"), ...publicError ? { publicError: publicError.replace(/(?:Bearer\s+|sk-|pk_|token[=:])[A-Za-z0-9._-]+/gi, "[redacted]") } : {} }, "wx");
|
|
2748
|
+
throw new Error("Claude front-door hook installation failed (" + (errorCode ?? bound.status) + "); preserve startup custody. Diagnostic: " + diagnostic);
|
|
2749
|
+
}
|
|
2750
|
+
const binding = JSON.parse(String(bound.stdout));
|
|
2751
|
+
if (binding.state !== "claude-frontdoors-prepared" || binding.coordinators !== 3 || binding.cloudDispatchVerified !== false)
|
|
2752
|
+
throw new Error("Claude front-door preparation readback differs.");
|
|
2753
|
+
return binding;
|
|
2754
|
+
}
|
|
2755
|
+
function readClaudeTaskConnection(file, pid, allowDead = false) {
|
|
2756
|
+
const target = realpathSync(file), parent = dirname(target), stat = lstatSync(file);
|
|
2757
|
+
if (target !== resolve(file) || paths.basename(parent) !== "claude" || !["desktop-task-connection.json", "desktop-task-connection-prepared.json"].includes(paths.basename(file)) || !stat.isFile() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || stat.mode & 63) throw new Error("Owned native connection descriptor required.");
|
|
2758
|
+
const root = dirname(parent), value = JSON.parse(readFileSync(file, "utf8"));
|
|
2759
|
+
if (Object.keys(value).sort().join() !== "identity,pid,runtime,secretPath,socket" || !Number.isSafeInteger(value.pid) || value.pid < 2 || value.runtime !== resolve(root, "claude/task-runtime") || !/^[a-f0-9]{64}$/.test(value.identity) || !paths.isAbsolute(value.socket) || !value.secretPath.startsWith(resolve(root, "claude/journals") + sep))
|
|
2760
|
+
throw new Error("Previous native connection identity differs.");
|
|
2761
|
+
if (value.pid !== pid) {
|
|
2762
|
+
if (!allowDead) throw new Error("Previous native connection identity differs.");
|
|
2763
|
+
try {
|
|
2764
|
+
process.kill(value.pid, 0);
|
|
2765
|
+
throw new Error("Previous native Desktop is alive; preserve its connection.");
|
|
2766
|
+
} catch (error) {
|
|
2767
|
+
if (error.code !== "ESRCH") throw new Error("Previous native Desktop absence is unverified.");
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
const secret = lstatSync(value.secretPath);
|
|
2771
|
+
if (!secret.isFile() || secret.isSymbolicLink() || realpathSync(value.secretPath) !== value.secretPath || secret.uid !== process.getuid?.() || secret.mode & 63 || !/^[a-f0-9]{64}$/.test(JSON.parse(readFileSync(value.secretPath, "utf8")).token)) throw new Error("Owned native connection secret required.");
|
|
2772
|
+
return value;
|
|
2773
|
+
}
|
|
2774
|
+
function claudeTaskConnectionCoordinators(connection) {
|
|
2775
|
+
const root = dirname(dirname(connection.runtime)), bindingPath = resolve(root, "work/claude-frontdoors.json");
|
|
2776
|
+
const read = (file) => {
|
|
2777
|
+
const st = lstatSync(file);
|
|
2778
|
+
if (!st.isFile() || st.isSymbolicLink() || st.uid !== process.getuid?.() || st.mode & 63 || realpathSync(file) !== file) throw new Error("Owned previous coordinator metadata required.");
|
|
2779
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
2780
|
+
};
|
|
2781
|
+
if (!existsSync(bindingPath)) return [];
|
|
2782
|
+
const binding = read(bindingPath);
|
|
2783
|
+
if (Object.keys(connection).some((key) => binding.nativeConnection?.[key] !== connection[key]) || !Array.isArray(binding.roles) || binding.roles.length !== 3)
|
|
2784
|
+
throw new Error("Previous coordinator connection binding differs.");
|
|
2785
|
+
const state = read(resolve(root, "claude/state.json"));
|
|
2786
|
+
if (state.harness !== "claude" || !Array.isArray(state.agents)) throw new Error("Previous native coordinator identities are unavailable.");
|
|
2787
|
+
return binding.roles.map((role) => {
|
|
2788
|
+
const records = state.agents.filter((row) => row.cliSessionId === role.sessionId && row.workspace === role.cwd);
|
|
2789
|
+
if (records.length !== 1 || typeof records[0].desktopSessionId !== "string") throw new Error("Previous coordinator has no exact native identity.");
|
|
2790
|
+
return { sessionId: records[0].desktopSessionId, cwd: role.cwd };
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
function readClaudePreviousCoordinatorLibrary(library, currentRoot) {
|
|
2794
|
+
const root = realpathSync(library);
|
|
2795
|
+
if (root !== resolve(library) || root === currentRoot || paths.basename(root) !== "library") throw new Error("Distinct exact previous Claude library required.");
|
|
2796
|
+
const statePath = resolve(root, "claude/state.json"), proofPath = resolve(root, "../coordinator-adoption.json");
|
|
2797
|
+
const context = {};
|
|
2798
|
+
const read = (file) => {
|
|
2799
|
+
const st = lstatSync(file);
|
|
2800
|
+
if (!st.isFile() || st.isSymbolicLink() || st.uid !== process.getuid?.() || st.mode & 63 || st.size > 1048576 || realpathSync(file) !== file) throw new Error("Private bounded previous coordinator proof required.");
|
|
2801
|
+
const bytes = readFileSync(file);
|
|
2802
|
+
context[file] = createHash2("sha256").update(bytes).digest("hex");
|
|
2803
|
+
return JSON.parse(bytes.toString());
|
|
2804
|
+
};
|
|
2805
|
+
const state = read(statePath), adoption = read(proofPath);
|
|
2806
|
+
if (state.harness !== "claude" || !Array.isArray(state.agents) || state.agents.length !== 3 || !Array.isArray(adoption.proof) || adoption.proof.length !== 3)
|
|
2807
|
+
throw new Error("Exactly three original coordinator adoption records required.");
|
|
2808
|
+
const expected = ["engineering", "operator-01", "cicd"];
|
|
2809
|
+
const members = state.agents.map((row) => {
|
|
2810
|
+
const role = row.key?.match(/\/(?:intakes|coordinators)\/(engineering|operator-01|cicd)$/)?.[1];
|
|
2811
|
+
const proof = adoption.proof.filter((p) => p.key === row.key && p.desktopSessionId === row.desktopSessionId && p.verified === true);
|
|
2812
|
+
if (!expected.includes(role) || proof.length !== 1 || !/^local_[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(row.desktopSessionId ?? "") || row.cliSessionId !== row.desktopSessionId.slice(6) || !row.workspace?.startsWith(resolve(root, "claude/workspaces") + sep) || realpathSync(row.workspace) !== row.workspace)
|
|
2813
|
+
throw new Error("Previous coordinator adoption identity or placement differs.");
|
|
2814
|
+
return { sessionId: row.desktopSessionId, cwd: row.workspace, role };
|
|
2815
|
+
});
|
|
2816
|
+
if (new Set(members.map((m) => m.sessionId)).size !== 3 || new Set(members.map((m) => m.role)).size !== 3)
|
|
2817
|
+
throw new Error("Previous coordinator roster is not unique.");
|
|
2818
|
+
return { members: members.map(({ sessionId, cwd }) => ({ sessionId, cwd })), context };
|
|
2819
|
+
}
|
|
2820
|
+
async function verifyClaudePreviousCoordinatorCustody(manager, members, check) {
|
|
2821
|
+
if (members.length !== 3 || typeof manager.archiveCascadeClosureOf !== "function") throw new Error("Exact previous coordinator custody capability required.");
|
|
2822
|
+
for (const member of members) {
|
|
2823
|
+
check();
|
|
2824
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-previous-coordinator-preflight", { warnOnMiss: false });
|
|
2825
|
+
check();
|
|
2826
|
+
const dto = await manager.getSession(member.sessionId);
|
|
2827
|
+
check();
|
|
2828
|
+
if (!raw || !dto || [raw, dto].some((row) => row.sessionId !== member.sessionId || row.cwd !== member.cwd || row.isArchived || row.isRunning || row.starting || row.pendingCycle || row.pendingToolPermissions?.length) || manager.hasLosableWork(member.sessionId) || manager.archiveCascadeClosureOf(raw).length !== 0)
|
|
2829
|
+
throw new Error("Previous coordinator retains active, changed or child custody.");
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
async function retireClaudeCoordinators(manager, sidebar, previous, successors, descriptor, checkpoint, check) {
|
|
2833
|
+
const key = /* @__PURE__ */ Symbol.for("topolo.agent-pack.desktop-task-control.v1"), fence = /* @__PURE__ */ Symbol.for("topolo.agent-pack.desktop-coordinator-transfer.v1");
|
|
2834
|
+
if (manager[fence]) throw new Error("Unresolved coordinator transfer; reconcile without replay.");
|
|
2835
|
+
const control = manager[key];
|
|
2836
|
+
const verify = () => {
|
|
2837
|
+
check();
|
|
2838
|
+
if (manager[key] !== control || control.active !== 0 || control.failed) throw new Error("Successor control ownership changed during retirement.");
|
|
2839
|
+
};
|
|
2840
|
+
if (!control || Object.keys(descriptor).some((key2) => control.descriptor?.[key2] !== descriptor[key2]) || control.active !== 0 || control.failed)
|
|
2841
|
+
throw new Error("Successor native control is not the acknowledged idle owner.");
|
|
2842
|
+
for (const name of ["archiveSession", "archiveCascadeClosureOf", "ensureArchivedSessionsLoaded"])
|
|
2843
|
+
if (typeof manager[name] !== "function") throw new Error("Native coordinator retirement capability unavailable.");
|
|
2844
|
+
if (previous.length !== 3 || successors.length !== 3 || new Set([...previous, ...successors].map((row) => row.sessionId)).size !== 6)
|
|
2845
|
+
throw new Error("Exact disjoint previous and successor coordinator rosters required.");
|
|
2846
|
+
const idle = async (member, successor = false) => {
|
|
2847
|
+
verify();
|
|
2848
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-coordinator-transfer", { warnOnMiss: false });
|
|
2849
|
+
const dto = await manager.getSession(member.sessionId);
|
|
2850
|
+
for (const row of [raw, dto]) if (!row || row.sessionId !== member.sessionId || row.cwd !== member.cwd || row.isArchived || row.isRunning || row.starting || row.pendingCycle || row.pendingToolPermissions?.length)
|
|
2851
|
+
throw new Error("Coordinator custody changed or has active work.");
|
|
2852
|
+
if (manager.hasLosableWork(member.sessionId) || manager.archiveCascadeClosureOf(raw).length !== 0)
|
|
2853
|
+
throw new Error("Coordinator retirement could affect losable or child work.");
|
|
2854
|
+
if (successor && (dto.model !== member.model || dto.effort !== member.effort || dto.permissionMode !== member.permissionMode || dto.permissionMode === "bypassPermissions" && raw.bypassAvailableAtSpawn !== true))
|
|
2855
|
+
throw new Error("Successor effective settings changed before retirement.");
|
|
2856
|
+
return raw;
|
|
2857
|
+
};
|
|
2858
|
+
for (const row of successors) await idle(row, true);
|
|
2859
|
+
for (const row of previous) await idle(row);
|
|
2860
|
+
manager[fence] = { descriptor, previous, successors };
|
|
2861
|
+
const retired = [];
|
|
2862
|
+
for (const row of previous) {
|
|
2863
|
+
for (const member of successors) await idle(member, true);
|
|
2864
|
+
await idle(row);
|
|
2865
|
+
checkpoint({ kind: "coordinator-unpin-attempt", sessionId: row.sessionId });
|
|
2866
|
+
const result = await sidebar({ kind: "set_pinned", sessionId: row.sessionId, pinned: false });
|
|
2867
|
+
if (result?.ok !== true) throw new Error("Previous coordinator native unpin was not acknowledged.");
|
|
2868
|
+
checkpoint({ kind: "coordinator-unpin-acknowledged", sessionId: row.sessionId });
|
|
2869
|
+
await idle(row);
|
|
2870
|
+
checkpoint({ kind: "coordinator-archive-attempt", sessionId: row.sessionId });
|
|
2871
|
+
verify();
|
|
2872
|
+
await manager.archiveSession(row.sessionId, { isAutomatic: true });
|
|
2873
|
+
await manager.ensureArchivedSessionsLoaded("topolo-coordinator-transfer");
|
|
2874
|
+
const actual = await manager.getSession(row.sessionId);
|
|
2875
|
+
verify();
|
|
2876
|
+
if (!actual || actual.sessionId !== row.sessionId || actual.cwd !== row.cwd || actual.isArchived !== true)
|
|
2877
|
+
throw new Error("Previous coordinator archive readback failed; reconcile without replay.");
|
|
2878
|
+
checkpoint({ kind: "coordinator-archive-verified", sessionId: row.sessionId });
|
|
2879
|
+
retired.push(row.sessionId);
|
|
2880
|
+
}
|
|
2881
|
+
delete manager[fence];
|
|
2882
|
+
return { state: "previous-coordinators-retired", retired };
|
|
2883
|
+
}
|
|
2884
|
+
function nativeClaudeCoordinatorRetirementExpression(pid, previous, successors, descriptor, journal) {
|
|
2885
|
+
return `(async()=>{if(process.pid!==${pid})throw new Error('Desktop process changed.');
|
|
2886
|
+
const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');
|
|
2887
|
+
const fs=load('node:fs'),caps=(${discoverClaudeNativeCapabilities.toString()})(load,process.resourcesPath);
|
|
2888
|
+
const sidebar=(${discoverClaudeSidebar.toString()})(load,process.resourcesPath);
|
|
2889
|
+
const checkpoint=row=>{const fd=fs.openSync(${JSON.stringify(journal)},'a',384);try{fs.writeSync(fd,JSON.stringify(row)+'\\n');fs.fsyncSync(fd);}finally{fs.closeSync(fd);}};
|
|
2890
|
+
return (${retireClaudeCoordinators.toString()})(caps.manager,sidebar,${JSON.stringify(previous)},${JSON.stringify(successors)},${JSON.stringify(descriptor)},checkpoint,caps.assertCurrent);})()`;
|
|
2891
|
+
}
|
|
2892
|
+
async function ensureClaudeCoordinators(manager, members, operationId, contextDigest, actions, check, checkpoint, deadline, continuation, acknowledged) {
|
|
2893
|
+
const key = /* @__PURE__ */ Symbol.for("topolo.agent-pack.coordinator-bootstrap.v1");
|
|
2894
|
+
const records = manager[key] ?? (manager[key] = /* @__PURE__ */ new Map());
|
|
2895
|
+
for (const member of members) {
|
|
2896
|
+
check();
|
|
2897
|
+
const resume = continuation?.includes(member.sessionId) === true, record = records.get(member.sessionId);
|
|
2898
|
+
if (record?.state === "attempted" && !resume) throw new Error("Unresolved coordinator creation; reconcile without replay.");
|
|
2899
|
+
const existing = await manager.getSession(member.sessionId);
|
|
2900
|
+
if (existing && !resume) continue;
|
|
2901
|
+
if (resume && (!existing || record?.operationId !== operationId || !["attempted", "verified", "restart-adopted"].includes(record.state))) throw new Error("Readiness continuation no longer owns the existing probe.");
|
|
2902
|
+
if (member.permissionMode !== "bypassPermissions" || !member.permissionConsent) throw new Error("Fresh coordinator bootstrap requires approved full access.");
|
|
2903
|
+
const workerId = member.sessionId.replace(/^local_/, "");
|
|
2904
|
+
const plan = {
|
|
2905
|
+
taskId: operationId,
|
|
2906
|
+
workerId,
|
|
2907
|
+
nativeId: member.sessionId,
|
|
2908
|
+
cwd: member.cwd,
|
|
2909
|
+
title: member.title,
|
|
2910
|
+
model: member.model,
|
|
2911
|
+
effort: member.effort,
|
|
2912
|
+
permissionMode: member.permissionMode,
|
|
2913
|
+
contextDigest
|
|
2914
|
+
};
|
|
2915
|
+
if (Date.now() >= deadline) throw new Error("Coordinator bootstrap deadline expired before creation.");
|
|
2916
|
+
if (!resume) {
|
|
2917
|
+
records.set(member.sessionId, { state: "attempted", operationId });
|
|
2918
|
+
await actions.create(manager, plan, check, checkpoint);
|
|
2919
|
+
}
|
|
2920
|
+
if (resume && (record?.state === "restart-adopted" || record?.historyAdopted === true)) {
|
|
2921
|
+
check();
|
|
2922
|
+
let raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-readiness-restart-warm", { warnOnMiss: false });
|
|
2923
|
+
if (!raw?.query) {
|
|
2924
|
+
checkpoint({ kind: "coordinator-readiness-warm-attempt", sessionId: member.sessionId, operationId, originalTerminalUnknown: true });
|
|
2925
|
+
await manager.warmSession(member.sessionId, { connectOnly: true });
|
|
2926
|
+
}
|
|
2927
|
+
check();
|
|
2928
|
+
raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-readiness-restart-warm", { warnOnMiss: false });
|
|
2929
|
+
const dto = await manager.getSession(member.sessionId);
|
|
2930
|
+
const history = await manager.getTranscript(member.sessionId);
|
|
2931
|
+
check();
|
|
2932
|
+
if (!raw?.query || raw.cliSessionId !== record.cliSessionId || raw.bypassAvailableAtSpawn !== true || raw.isRunning || raw.pendingCycle || manager.hasLosableWork(member.sessionId) || !dto || ["model", "effort", "permissionMode"].some((k) => dto[k] !== member[k]) || history.filter((r) => r.type === "user").length !== 1 || history.some((r) => r.type === "assistant" && r.message?.content?.some((c) => c.type === "tool_use"))) throw Error("Restarted readiness query was not acknowledged without new work.");
|
|
2933
|
+
if (!acknowledged?.includes(member.sessionId)) checkpoint({ kind: "coordinator-bootstrap-history-ready", sessionId: member.sessionId, operationId, originalTerminalUnknown: true, nativeInputsReplayed: false });
|
|
2934
|
+
records.set(member.sessionId, { ...record, state: "verified" });
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2937
|
+
for (; ; ) {
|
|
2938
|
+
check();
|
|
2939
|
+
const result = await actions.read(manager, plan, workerId, check);
|
|
2940
|
+
if (result.state === "native-task-completed") {
|
|
2941
|
+
if (result.result.trim() !== "TOPOLO_WORKER_READY:" + workerId) throw new Error("Coordinator readiness did not match its native input.");
|
|
2942
|
+
const history = await manager.getTranscript(member.sessionId);
|
|
2943
|
+
if (resume) {
|
|
2944
|
+
const users = history.filter((row) => row.type === "user"), content = users[0]?.message?.content;
|
|
2945
|
+
const text = typeof content === "string" ? content : Array.isArray(content) && content.every((item) => item.type === "text") ? content.map((item) => item.text).join("") : null;
|
|
2946
|
+
if (users.length !== 1 || users[0].uuid !== workerId || text !== "Installation readiness probe only. Do not use tools, read task files, dispatch work or perform any task. Reply exactly TOPOLO_WORKER_READY:" + workerId)
|
|
2947
|
+
throw new Error("Original native readiness input differs; no continuation.");
|
|
2948
|
+
}
|
|
2949
|
+
if (history.some((row) => row.type === "assistant" && row.message?.content?.some((item) => item.type === "tool_use")))
|
|
2950
|
+
throw new Error("Coordinator bootstrap used tools; preserve creation uncertainty.");
|
|
2951
|
+
if (!acknowledged?.includes(member.sessionId)) checkpoint({ kind: "coordinator-bootstrap-verified", sessionId: member.sessionId });
|
|
2952
|
+
records.set(member.sessionId, { state: "verified", operationId });
|
|
2953
|
+
break;
|
|
2954
|
+
}
|
|
2955
|
+
if (Date.now() >= deadline) throw new Error("Native coordinator readiness is pending; reconcile without replay.");
|
|
2956
|
+
await new Promise((done) => setTimeout(done, 100));
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
return { state: "coordinators-native-ready" };
|
|
2960
|
+
}
|
|
2961
|
+
function prepareClaudeStartup(root, pack, policy, run = spawnSync) {
|
|
2962
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
2963
|
+
const source = [resolve(here, "../../agent-packs/src"), resolve(here, "../agent-packs/src")].find(existsSync);
|
|
2964
|
+
if (!source) throw new Error("Bundled startup preparation adapter unavailable.");
|
|
2965
|
+
const script = `import sys,json,io,contextlib
|
|
2966
|
+
sys.path.insert(0,sys.argv[4])
|
|
2967
|
+
from topolo_agent_pack import cli,permissions,topolo
|
|
2968
|
+
from topolo_agent_pack.storage import Library,PackError,read_json
|
|
2969
|
+
from topolo_agent_pack.role_context import materialize
|
|
2970
|
+
from pathlib import Path
|
|
2971
|
+
root,pack,output=sys.argv[1:4]
|
|
2972
|
+
lib=Library(root);manifest,revision=lib.load(pack)
|
|
2973
|
+
consent=permissions.load(lib,pack,manifest['agents'])
|
|
2974
|
+
if not consent or consent.get('sourceRevision')!=revision:raise PackError('Current approved installation consent source revision required')
|
|
2975
|
+
remote,proof=topolo.organization_execution_policy(topolo.Client(consent['environment'],consent['contextKey']),consent,manifest['agents'],consent.get('agentWorkspaceId'))
|
|
2976
|
+
permissions.authorize_native_scope(proof)
|
|
2977
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
2978
|
+
if cli.main(['--store',root,'add',pack,'--harness','claude','--group','intakes','--no-open'])!=0:raise PackError('Coordinator preparation refused')
|
|
2979
|
+
records=[row for row in read_json(Path(root)/'claude/state.json')['agents'] if row['pack']==pack and row['set']=='intakes']
|
|
2980
|
+
if len(records)!=3:raise PackError('Exactly three prepared coordinator records required')
|
|
2981
|
+
for row in records:materialize(Path(root)/'work',row['key'],Path(row['workspace']),harness='claude')
|
|
2982
|
+
if cli.main(['--store',root,'policy',pack,'--harness','claude','--set','intakes','--verify-organization','--output',output])!=0:raise PackError('Fresh startup policy refused')
|
|
2983
|
+
print(json.dumps({'state':'claude-startup-prepared','policy':output,'nativeEffects':False}))`;
|
|
2984
|
+
const result = run("python3", ["-I", "-B", "-c", script, root, pack, policy, source], {
|
|
2985
|
+
encoding: "utf8",
|
|
2986
|
+
timeout: 18e4,
|
|
2987
|
+
maxBuffer: 1048576,
|
|
2988
|
+
env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1", TOPOLO_AGENT_PACK_PUBLIC_CLI: "1" }
|
|
2989
|
+
});
|
|
2990
|
+
if (result.error || result.status !== 0) throw new Error("Storage-only Claude startup preparation refused; inspect owned consent and preparation files. No native login/resume was used.");
|
|
2991
|
+
let value;
|
|
2992
|
+
try {
|
|
2993
|
+
value = JSON.parse(String(result.stdout));
|
|
2994
|
+
} catch {
|
|
2995
|
+
throw new Error("Startup preparation acknowledgement is invalid.");
|
|
2996
|
+
}
|
|
2997
|
+
if (value.state !== "claude-startup-prepared" || value.policy !== policy || value.nativeEffects !== false) throw new Error("Startup preparation acknowledgement differs.");
|
|
2998
|
+
return value;
|
|
2999
|
+
}
|
|
3000
|
+
async function reconcileClaudeStartupAbsence(manager, plan, attempt, check, checkpoint) {
|
|
3001
|
+
const key = /* @__PURE__ */ Symbol.for("topolo.agent-pack.coordinator-bootstrap.v1"), records = manager[key];
|
|
3002
|
+
const starts = manager.inFlightStarts;
|
|
3003
|
+
if (!(records instanceof Map) || !starts || ["hasStartInFlight", "hasStartAbortController", "hasDeferredTeardown", "pendingStartModel"].some((k) => typeof starts[k] !== "function") || !(manager.pendingPopSpawnsInFlight instanceof Map) || !(manager.sideSessionStartsInFlight instanceof Map))
|
|
3004
|
+
throw new Error("Native startup absence capabilities unavailable.");
|
|
3005
|
+
const tracked = ["hasStartInFlight", "hasStartAbortController", "hasDeferredTeardown", "pendingStartModel"];
|
|
3006
|
+
const startMethods = tracked.map((name) => starts[name]), transcriptMethod = manager.getTranscript;
|
|
3007
|
+
const pops = manager.pendingPopSpawnsInFlight, sides = manager.sideSessionStartsInFlight;
|
|
3008
|
+
const pending = () => {
|
|
3009
|
+
check();
|
|
3010
|
+
if (manager[key] !== records || manager.inFlightStarts !== starts || manager.getTranscript !== transcriptMethod || tracked.some((name, i) => starts[name] !== startMethods[i]) || manager.pendingPopSpawnsInFlight !== pops || manager.sideSessionStartsInFlight !== sides || manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.native-operation.v1")]) throw new Error("Native startup custody changed.");
|
|
3011
|
+
for (const member of plan.members) {
|
|
3012
|
+
const row = records.get(member.sessionId);
|
|
3013
|
+
if (member.sessionId === attempt.nativeId) {
|
|
3014
|
+
if (!row || Object.keys(row).sort().join() !== "operationId,state" || row.operationId !== plan.operationId || !["attempted", "reconciled-absent"].includes(row.state)) throw new Error("Original coordinator creation reservation differs.");
|
|
3015
|
+
} else if (row) throw new Error("Another coordinator creation was attempted.");
|
|
3016
|
+
if (starts.hasStartInFlight(member.sessionId) || starts.hasStartAbortController(member.sessionId) || starts.hasDeferredTeardown(member.sessionId) || starts.pendingStartModel(member.sessionId) !== void 0 || manager.pendingPopSpawnsInFlight.has(member.sessionId) || manager.sideSessionStartsInFlight.has(member.sessionId) || manager.hasLosableWork(member.sessionId))
|
|
3017
|
+
throw new Error("Native coordinator startup remains pending.");
|
|
3018
|
+
}
|
|
3019
|
+
};
|
|
3020
|
+
pending();
|
|
3021
|
+
for (const member of plan.members) {
|
|
3022
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-readiness-absence-reconciliation", { warnOnMiss: false });
|
|
3023
|
+
pending();
|
|
3024
|
+
const dto = await manager.getSession(member.sessionId);
|
|
3025
|
+
pending();
|
|
3026
|
+
const history = await manager.getTranscript(member.sessionId);
|
|
3027
|
+
pending();
|
|
3028
|
+
if (raw || dto || !Array.isArray(history) || history.length) throw new Error("Native coordinator exists or retains history; no creation replay.");
|
|
3029
|
+
}
|
|
3030
|
+
pending();
|
|
3031
|
+
const receipt = {
|
|
3032
|
+
kind: "startup-creation-absence-verified",
|
|
3033
|
+
operationId: plan.operationId,
|
|
3034
|
+
sessionIds: plan.members.map((m) => m.sessionId),
|
|
3035
|
+
attemptedNativeId: attempt.nativeId,
|
|
3036
|
+
nativeSessionsAbsent: true,
|
|
3037
|
+
nativeInputsReplayed: false,
|
|
3038
|
+
preservedUnknown: true
|
|
3039
|
+
};
|
|
3040
|
+
checkpoint(receipt);
|
|
3041
|
+
records.set(attempt.nativeId, { state: "reconciled-absent", operationId: plan.operationId });
|
|
3042
|
+
return receipt;
|
|
3043
|
+
}
|
|
3044
|
+
async function recoverClaudeStartup(options, evaluate = evaluateClaudeMain) {
|
|
3045
|
+
validateInspectorEndpoint(options.endpoint);
|
|
3046
|
+
const root = realpathSync(options.store), release = await lockClaudeLibrary(root);
|
|
3047
|
+
try {
|
|
3048
|
+
const markerPath = resolve(root, "claude/startup-operation.json"), directory = realpathSync(resolve(root, "claude/journals"));
|
|
3049
|
+
for (const item of [resolve(root, "claude"), directory]) {
|
|
3050
|
+
const stat = lstatSync(item);
|
|
3051
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || stat.uid !== process.getuid?.() || stat.mode & 63 || realpathSync(item) !== item)
|
|
3052
|
+
throw new Error("Private owned startup directories required.");
|
|
3053
|
+
}
|
|
3054
|
+
if (directory !== resolve(root, "claude/journals")) throw new Error("Startup journal directory changed.");
|
|
3055
|
+
const privateFile = (file) => {
|
|
3056
|
+
const stat = lstatSync(file);
|
|
3057
|
+
if (stat.isSymbolicLink() || !stat.isFile() || stat.uid !== process.getuid?.() || stat.mode & 63 || stat.size > 1048576) throw new Error("Private bounded startup evidence required.");
|
|
3058
|
+
};
|
|
3059
|
+
privateFile(markerPath);
|
|
3060
|
+
const marker = JSON.parse(readFileSync(markerPath, "utf8"));
|
|
3061
|
+
if (Object.keys(marker).sort().join() !== "journal,operationId,state" || marker.state !== "may-have-effects" || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(marker.operationId) || marker.journal !== resolve(directory, marker.operationId + ".jsonl")) throw new Error("Exact original startup marker required.");
|
|
3062
|
+
privateFile(marker.journal);
|
|
3063
|
+
const original = readFileSync(marker.journal, "utf8");
|
|
3064
|
+
const lines = original.trimEnd().split("\n").map((line) => JSON.parse(line)), plan = lines[0], attempt = lines[1];
|
|
3065
|
+
if (!original.endsWith("\n") || ![2, 3].includes(lines.length) || plan?.kind !== "startup-plan" || plan.apply !== true || plan.libraryRoot !== root || plan.operationId !== marker.operationId || plan.mode !== "configure" || !Array.isArray(plan.members) || plan.members.length !== 3 || plan.members.map((m) => m.key).sort().join() !== ["engineering", "operator-01", "cicd"].map((role) => options.pack + "/intakes/" + role).sort().join() || new Set(plan.members.map((m) => m.sessionId)).size !== 3 || attempt?.kind !== "worker-create-attempt" || Object.keys(attempt).sort().join() !== ["kind", "taskId", "workerId", "nativeId", "cwd", "title", "model", "effort", "permissionMode", "contextDigest", "at"].sort().join() || attempt.taskId !== plan.operationId || attempt.nativeId !== plan.members[0].sessionId || attempt.workerId !== attempt.nativeId.replace(/^local_/, "") || attempt.contextDigest !== createHash2("sha256").update(JSON.stringify(plan.context)).digest("hex") || ["cwd", "title", "model", "effort", "permissionMode"].some((key) => attempt[key] !== plan.members[0][key]) || plan.members.some((m) => !/^local_[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(m.sessionId) || m.permissionMode !== "bypassPermissions" || !m.permissionConsent) || lines.length === 3 && lines[2].kind !== "startup-creation-absence-verified")
|
|
3066
|
+
throw new Error("Only the exact interrupted three-coordinator readiness bootstrap can be reconciled.");
|
|
3067
|
+
verifyPlanContext(plan);
|
|
3068
|
+
const expected = {
|
|
3069
|
+
kind: "startup-creation-absence-verified",
|
|
3070
|
+
operationId: plan.operationId,
|
|
3071
|
+
sessionIds: plan.members.map((m) => m.sessionId),
|
|
3072
|
+
attemptedNativeId: attempt.nativeId,
|
|
3073
|
+
nativeSessionsAbsent: true,
|
|
3074
|
+
nativeInputsReplayed: false,
|
|
3075
|
+
preservedUnknown: true
|
|
3076
|
+
};
|
|
3077
|
+
if (lines.length === 3 && JSON.stringify(lines[2]) !== JSON.stringify(expected)) throw new Error("Prior startup reconciliation receipt differs.");
|
|
3078
|
+
const expression = `(async()=>{if(process.pid!==${options.pid})throw new Error('Desktop process changed.');
|
|
3079
|
+
const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');
|
|
3080
|
+
const fs=load('node:fs'),crypto=load('node:crypto'),path=load('node:path');
|
|
3081
|
+
const caps=(${discoverClaudeNativeCapabilities.toString()})(load,process.resourcesPath),plan=${JSON.stringify(plan)};
|
|
3082
|
+
const check=()=>{caps.assertCurrent();(${verifyClaudePlanContext.toString()})(plan,undefined,fs,crypto,path,(${snapshotClaudeMarkdown.toString()}));};
|
|
3083
|
+
const checkpoint=row=>{check();const old=fs.readFileSync(${JSON.stringify(marker.journal)},'utf8');
|
|
3084
|
+
if(old!==${JSON.stringify(original)})throw new Error('Original startup journal changed.');
|
|
3085
|
+
${lines.length === 2 ? `const fd=fs.openSync(${JSON.stringify(marker.journal)},'a',384);try{fs.writeSync(fd,JSON.stringify(row)+'\\n');fs.fsyncSync(fd);}finally{fs.closeSync(fd);}` : ""}};
|
|
3086
|
+
return (${reconcileClaudeStartupAbsence.toString()})(caps.manager,plan,${JSON.stringify(attempt)},check,checkpoint);})()`;
|
|
3087
|
+
const receipt = await evaluate(options.endpoint, expression);
|
|
3088
|
+
verifyPlanContext(plan);
|
|
3089
|
+
if (JSON.stringify(receipt) !== JSON.stringify(expected) || readFileSync(marker.journal, "utf8") !== (lines.length === 2 ? original + JSON.stringify(expected) + "\n" : original) || readFileSync(markerPath, "utf8") !== JSON.stringify(marker) + "\n")
|
|
3090
|
+
throw new Error("Startup absence reconciliation audit was not acknowledged.");
|
|
3091
|
+
const retained = resolve(directory, plan.operationId + ".reconciled-marker.json");
|
|
3092
|
+
if (existsSync(retained)) throw new Error("Startup reconciliation marker already retained.");
|
|
3093
|
+
renameSync(markerPath, retained);
|
|
3094
|
+
syncDirectory(directory);
|
|
3095
|
+
syncDirectory(resolve(root, "claude"));
|
|
3096
|
+
return { ...receipt, journal: marker.journal, retainedMarker: retained, state: "startup-readiness-absence-reconciled", packActivated: false };
|
|
3097
|
+
} finally {
|
|
3098
|
+
release();
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
function readClaudeReadinessContinuation(root, pack, completion = false) {
|
|
3102
|
+
const markerPath = resolve(root, "claude/startup-operation.json");
|
|
3103
|
+
for (const directory2 of [resolve(root, "claude"), resolve(root, "claude/journals")]) {
|
|
3104
|
+
const st = lstatSync(directory2);
|
|
3105
|
+
if (!st.isDirectory() || st.isSymbolicLink() || st.uid !== process.getuid?.() || st.mode & 63 || realpathSync(directory2) !== directory2) throw new Error("Private owned readiness directories required.");
|
|
3106
|
+
}
|
|
3107
|
+
const exact = (row, fields) => row && Object.keys(row).sort().join() === fields.sort().join();
|
|
3108
|
+
const read = (file) => {
|
|
3109
|
+
const st = lstatSync(file);
|
|
3110
|
+
if (st.isSymbolicLink() || !st.isFile() || st.uid !== process.getuid?.() || st.mode & 63 || st.size > 1048576 || realpathSync(file) !== file) throw new Error("Private bounded readiness continuation evidence required.");
|
|
3111
|
+
return readFileSync(file, "utf8");
|
|
3112
|
+
};
|
|
3113
|
+
const markerText = read(markerPath), marker = JSON.parse(markerText), directory = resolve(root, "claude/journals");
|
|
3114
|
+
if (Object.keys(marker).sort().join() !== "journal,operationId,state" || marker.state !== "may-have-effects" || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(marker.operationId) || marker.journal !== resolve(directory, marker.operationId + ".jsonl")) throw new Error("Exact original readiness marker required.");
|
|
3115
|
+
const bytes = read(marker.journal);
|
|
3116
|
+
if (!bytes.endsWith("\n")) throw new Error("Readiness journal is incomplete.");
|
|
3117
|
+
const rows = bytes.trimEnd().split("\n").map((line) => JSON.parse(line)), original = rows.shift();
|
|
3118
|
+
if (original?.kind !== "startup-plan" || original.apply !== true || original.libraryRoot !== root || original.operationId !== marker.operationId || original.mode !== "configure" || !Array.isArray(original.members) || original.members.length !== 3 || original.members.some((m) => m.permissionMode !== "bypassPermissions" || !m.permissionConsent) || new Set(original.members.map((m) => m.sessionId)).size !== 3 || original.members.map((m) => m.key).sort().join() !== ["engineering", "operator-01", "cicd"].map((role) => pack + "/intakes/" + role).sort().join()) throw new Error("Exact three-coordinator readiness plan required.");
|
|
3119
|
+
const digest = createHash2("sha256").update(JSON.stringify(original.context)).digest("hex"), created = [], acknowledged = [];
|
|
3120
|
+
let index = 0;
|
|
3121
|
+
for (const member of original.members) {
|
|
3122
|
+
if (index === rows.length) break;
|
|
3123
|
+
if (rows[index]?.kind === "worker-workspace-trust-attempt") {
|
|
3124
|
+
for (const kind of ["worker-workspace-trust-attempt", "worker-workspace-trust-verified"]) {
|
|
3125
|
+
const row = rows[index++];
|
|
3126
|
+
if (!exact(row, ["kind", "nativeId", "cwd", "contextDigest", "literalKeyOnly", "at"]) || !Number.isFinite(row.at) || row.kind !== kind || row.nativeId !== member.sessionId || row.cwd !== member.cwd || row.contextDigest !== digest || row.literalKeyOnly !== true)
|
|
3127
|
+
throw new Error("Readiness trust audit differs or is unresolved.");
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
const attempt = rows[index++], verified = rows[index++];
|
|
3131
|
+
if (!exact(attempt, ["kind", "taskId", "workerId", "nativeId", "cwd", "title", "model", "effort", "permissionMode", "contextDigest", "at"]) || !Number.isFinite(attempt.at) || attempt.kind !== "worker-create-attempt" || attempt.taskId !== original.operationId || attempt.workerId !== member.sessionId.replace(/^local_/, "") || attempt.nativeId !== member.sessionId || attempt.contextDigest !== digest || ["cwd", "title", "model", "effort", "permissionMode"].some((k) => attempt[k] !== member[k]) || !exact(verified, ["kind", "state", "nativeId", "taskId", "workerId", "model", "effort", "permissionMode", "contextDigest", "nativeHistoryEntries", "readinessProbeSent", "taskPromptSent", "at"]) || !Number.isFinite(verified.at) || verified.kind !== "worker-create-verified" || verified.state !== "native-worker-readiness-pending" || verified.nativeId !== member.sessionId || verified.workerId !== attempt.workerId || verified.taskId !== original.operationId || verified.contextDigest !== digest || verified.readinessProbeSent !== true || verified.taskPromptSent !== false || !Number.isInteger(verified.nativeHistoryEntries) || verified.nativeHistoryEntries < 0 || ["model", "effort", "permissionMode"].some((k) => verified[k] !== member[k])) throw new Error("Only acknowledged readiness creation can continue; never replay an uncertain start.");
|
|
3132
|
+
created.push(member.sessionId);
|
|
3133
|
+
while (["coordinator-readiness-history-adopted", "coordinator-readiness-warm-attempt", "coordinator-bootstrap-history-ready"].includes(rows[index]?.kind)) {
|
|
3134
|
+
const row = rows[index++];
|
|
3135
|
+
if (row.sessionId !== member.sessionId || row.operationId !== original.operationId || row.originalTerminalUnknown !== true) throw Error("Readiness restart audit identity differs.");
|
|
3136
|
+
if (row.kind === "coordinator-readiness-history-adopted") {
|
|
3137
|
+
if (!exact(row, ["kind", "operationId", "sessionId", "cliSessionId", "userUuid", "assistantUuid", "contextDigest", "historyDigest", "previousPid", "currentPid", "originalTerminalUnknown", "nativeInputsReplayed"]) || row.userUuid !== attempt.workerId || row.contextDigest !== digest || !/^local_/.test(row.sessionId) || !/^[a-f0-9-]{36}$/.test(row.cliSessionId) || !/^[a-f0-9-]{36}$/.test(row.assistantUuid) || !/^[a-f0-9]{64}$/.test(row.historyDigest) || !Number.isSafeInteger(row.previousPid) || row.previousPid < 1 || !Number.isSafeInteger(row.currentPid) || row.currentPid < 1 || row.currentPid === row.previousPid || row.nativeInputsReplayed !== false) throw Error("Readiness restart history audit differs.");
|
|
3138
|
+
} else if (row.kind === "coordinator-readiness-warm-attempt") {
|
|
3139
|
+
if (!exact(row, ["kind", "sessionId", "operationId", "originalTerminalUnknown"])) throw Error("Readiness warm audit differs.");
|
|
3140
|
+
} else {
|
|
3141
|
+
if (!exact(row, ["kind", "sessionId", "operationId", "originalTerminalUnknown", "nativeInputsReplayed"]) || row.nativeInputsReplayed !== false) throw Error("Readiness history acknowledgement differs.");
|
|
3142
|
+
if (!acknowledged.includes(member.sessionId)) acknowledged.push(member.sessionId);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (rows[index]?.kind === "coordinator-bootstrap-verified") {
|
|
3146
|
+
if (!exact(rows[index], ["kind", "sessionId"]) || rows[index++].sessionId !== member.sessionId) throw new Error("Readiness terminal acknowledgement differs.");
|
|
3147
|
+
acknowledged.push(member.sessionId);
|
|
3148
|
+
} else if (index !== rows.length && !acknowledged.includes(member.sessionId)) throw new Error("Readiness creation order is ambiguous.");
|
|
3149
|
+
}
|
|
3150
|
+
if (!created.length || index !== rows.length && !(completion && created.length === 3 && acknowledged.length === 3 && rows[index]?.kind === "settings-baseline")) throw new Error("Readiness journal contains later or unrelated effects.");
|
|
3151
|
+
const { kind: _, apply: __, ...plan } = original;
|
|
3152
|
+
verifyPlanContext(plan);
|
|
3153
|
+
return { plan, created, acknowledged, journal: marker.journal, markerText, journalText: bytes, ...completion ? { completionRows: rows.slice(index) } : {} };
|
|
3154
|
+
}
|
|
3155
|
+
async function verifyClaudeReadinessContinuation(manager, members, created, operationId, check) {
|
|
3156
|
+
const records = manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.coordinator-bootstrap.v1")], starts = manager.inFlightStarts;
|
|
3157
|
+
if (!(records instanceof Map) || typeof starts?.hasStartInFlight !== "function" || typeof starts?.hasStartAbortController !== "function" || typeof starts?.hasDeferredTeardown !== "function" || typeof starts?.pendingStartModel !== "function" || !(manager.pendingPopSpawnsInFlight instanceof Map) || !(manager.sideSessionStartsInFlight instanceof Map)) throw new Error("Native readiness continuation capabilities unavailable.");
|
|
3158
|
+
for (const member of members) {
|
|
3159
|
+
check();
|
|
3160
|
+
const record = records.get(member.sessionId);
|
|
3161
|
+
if (created.includes(member.sessionId)) {
|
|
3162
|
+
if (record?.operationId !== operationId || !["attempted", "verified", "restart-adopted"].includes(record.state)) throw new Error("Original native readiness reservation changed.");
|
|
3163
|
+
} else {
|
|
3164
|
+
if (record || starts.hasStartInFlight(member.sessionId) || starts.hasStartAbortController(member.sessionId) || starts.hasDeferredTeardown(member.sessionId) || starts.pendingStartModel(member.sessionId) !== void 0 || manager.pendingPopSpawnsInFlight.has(member.sessionId) || manager.sideSessionStartsInFlight.has(member.sessionId) || manager.hasLosableWork(member.sessionId)) throw new Error("Uncreated coordinator has unresolved native custody.");
|
|
3165
|
+
const raw = await manager.getSessionWhenLoaded(member.sessionId, "topolo-readiness-continuation", { warnOnMiss: false });
|
|
3166
|
+
check();
|
|
3167
|
+
const dto = await manager.getSession(member.sessionId);
|
|
3168
|
+
check();
|
|
3169
|
+
const history = await manager.getTranscript(member.sessionId);
|
|
3170
|
+
check();
|
|
3171
|
+
if (raw || dto || !Array.isArray(history) || history.length) throw new Error("Uncreated coordinator identity already has native history.");
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
async function configureClaudeStartup(options, evaluate = evaluateClaudeMain, qualify = verifyClaudeOrganizationPolicy, bind = bindClaudeFrontdoorHooks, prepare = prepareClaudeStartup) {
|
|
3176
|
+
validateInspectorEndpoint(options.endpoint);
|
|
3177
|
+
const root = realpathSync(options.store);
|
|
3178
|
+
if (["activation-operation.json", "launch-operation.json"].some((n) => existsSync(resolve(root, "claude", n)))) throw Error("Unresolved pack activation/launch retains custody.");
|
|
3179
|
+
if (options.continueReadiness && (!options.apply || options.prepare || !options.policy)) throw new Error("Readiness continuation requires explicit original policy/apply and no preparation.");
|
|
3180
|
+
if (!options.continueReadiness && existsSync(resolve(root, "claude/startup-operation.json")) || existsSync(resolve(root, "claude/native-operation.json"))) throw new Error("Unresolved native operation requires reconciliation before preparation.");
|
|
3181
|
+
if (!options.prepare && !options.policy) throw new Error("Reviewed startup policy or explicit --prepare required.");
|
|
3182
|
+
const policy = options.policy ?? resolve(root, "claude/journals", randomUUID() + ".startup-policy.json");
|
|
3183
|
+
if (options.prepare) prepare(root, options.pack, policy);
|
|
3184
|
+
const release = await lockClaudeLibrary(root);
|
|
3185
|
+
try {
|
|
3186
|
+
const marker = resolve(root, "claude/startup-operation.json");
|
|
3187
|
+
if (!options.continueReadiness && existsSync(marker) || existsSync(resolve(root, "claude/native-operation.json")))
|
|
3188
|
+
throw new Error("Unresolved native operation requires reconciliation before startup.");
|
|
3189
|
+
const prior = options.previousLibrary ? readClaudePreviousCoordinatorLibrary(options.previousLibrary, root) : null;
|
|
3190
|
+
let plan = {
|
|
3191
|
+
...prepareClaudeNativePlan(root, options.pack, policy, "configure", true),
|
|
3192
|
+
operationId: randomUUID(),
|
|
3193
|
+
...prior ? { previousCoordinators: prior.members } : {}
|
|
3194
|
+
};
|
|
3195
|
+
if (prior) {
|
|
3196
|
+
Object.assign(plan.context, prior.context);
|
|
3197
|
+
plan.globalContextFiles.push(...Object.keys(prior.context));
|
|
3198
|
+
}
|
|
3199
|
+
const continuation = options.continueReadiness ? readClaudeReadinessContinuation(root, options.pack) : null;
|
|
3200
|
+
if (continuation) {
|
|
3201
|
+
for (const key of Object.keys(plan).filter((k) => k !== "operationId")) if (JSON.stringify(plan[key]) !== JSON.stringify(continuation.plan[key])) throw new Error("Original readiness policy, ownership or context changed.");
|
|
3202
|
+
plan = { ...continuation.plan, readinessContinuation: continuation.created, readinessAcknowledged: continuation.acknowledged };
|
|
3203
|
+
}
|
|
3204
|
+
if (plan.members.some((m) => !/^.+\/intakes\/(engineering|operator-01|cicd)$/.test(m.key)))
|
|
3205
|
+
throw new Error("Startup settings apply only to the three coordinator roles.");
|
|
3206
|
+
verifyPlanContext(plan);
|
|
3207
|
+
if (options.apply && plan.members.some((m) => m.permissionMode === "bypassPermissions"))
|
|
3208
|
+
qualify(root, options.pack, plan.installationConsent);
|
|
3209
|
+
const directory = resolve(root, "claude/journals");
|
|
3210
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
3211
|
+
const journal = resolve(directory, plan.operationId + ".jsonl");
|
|
3212
|
+
if (continuation) {
|
|
3213
|
+
if (readFileSync(marker, "utf8") !== continuation.markerText || readFileSync(journal, "utf8") !== continuation.journalText) throw new Error("Readiness continuation journal changed before native entry.");
|
|
3214
|
+
} else {
|
|
3215
|
+
durableRecord(journal, { kind: "startup-plan", ...plan, apply: options.apply }, "wx");
|
|
3216
|
+
syncDirectory(directory);
|
|
3217
|
+
if (options.apply) {
|
|
3218
|
+
durableRecord(marker, { operationId: plan.operationId, journal, state: "may-have-effects" }, "wx");
|
|
3219
|
+
syncDirectory(resolve(root, "claude"));
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
let profile = readClaudeDesktopProfile(root);
|
|
3223
|
+
if (profile) {
|
|
3224
|
+
if (profile.profile.pack !== options.pack || profile.profile.consentDigest !== createHash2("sha256").update(JSON.stringify(plan.installationConsent)).digest("hex")) throw Error("Managed profile consent differs from current startup.");
|
|
3225
|
+
const proof = await evaluate(options.endpoint, `(async()=>{if(process.pid!==${options.pid})throw Error('Desktop PID changed');const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');const managers=[...new Set(Object.values(load.cache).filter(r=>r.filename?.startsWith(process.resourcesPath+'/app.asar/')).map(r=>r.exports?.claudeCodeSessionManager).filter(Boolean))];if(managers.length!==1)throw Error('Native manager ambiguous');return (${verifyClaudeDesktopProfileNative.toString()})(${JSON.stringify(profile.profile)},process.pid,load('electron').app,managers[0],process.env);})()`);
|
|
3226
|
+
verifyPlanContext(plan);
|
|
3227
|
+
profile = acknowledgeClaudeDesktopProfile(root, profile, proof);
|
|
3228
|
+
}
|
|
3229
|
+
let restartReadiness;
|
|
3230
|
+
if (continuation && profile && options.previousConnection) {
|
|
3231
|
+
const previous = readClaudeTaskConnection(options.previousConnection, options.pid, true);
|
|
3232
|
+
if (previous.pid !== options.pid) restartReadiness = { previousPid: previous.pid };
|
|
3233
|
+
}
|
|
3234
|
+
const profileReceipt = profile ? { file: resolve(root, "claude/desktop-profile.json"), digest: createHash2("sha256").update(readFileSync(resolve(root, "claude/desktop-profile.json"))).digest("hex") } : null;
|
|
3235
|
+
const result = await evaluate(options.endpoint, nativeClaudeSettingsExpression(options.pid, { ...plan, ...profile ? { desktopProfile: profile.profile, desktopProfileReceipt: profileReceipt } : {}, ...restartReadiness ? { restartReadiness } : {} }, journal, options.apply));
|
|
3236
|
+
verifyPlanContext(plan);
|
|
3237
|
+
if (profileReceipt && createHash2("sha256").update(readFileSync(profileReceipt.file)).digest("hex") !== profileReceipt.digest) throw Error("Managed profile receipt changed during native startup.");
|
|
3238
|
+
if (result.state !== (options.apply ? "native-settings-verified" : "settings-preflight") || result.applied !== options.apply)
|
|
3239
|
+
throw new Error("Native startup result is not a qualified settings acknowledgement.");
|
|
3240
|
+
if (options.apply) {
|
|
3241
|
+
if (result.pinsVerified !== true || result.after?.length !== plan.members.length || result.after.some((r, i) => r.sessionId !== plan.members[i].sessionId || r.model !== plan.members[i].model || r.effort !== plan.members[i].effort || r.permissionMode !== plan.members[i].permissionMode || r.pinned !== true)) throw new Error("Native startup roster readback differs.");
|
|
3242
|
+
durableRecord(journal, { kind: "startup-settings-verified", after: result.after });
|
|
3243
|
+
const workConfig = resolve(root, "work/config.json");
|
|
3244
|
+
if (existsSync(workConfig) && JSON.parse(readFileSync(workConfig, "utf8")).format === "topolo-task-dispatch-v2") {
|
|
3245
|
+
const runtime = resolve(root, "claude/task-runtime");
|
|
3246
|
+
mkdirSync(runtime, { recursive: true, mode: 448 });
|
|
3247
|
+
const local = [resolve(root, "claude/desktop-task-connection.json"), resolve(root, "claude/desktop-task-connection-prepared.json")].filter(existsSync);
|
|
3248
|
+
const known = (options.previousConnection ? [options.previousConnection] : local).map((file) => readClaudeTaskConnection(file, options.pid, true));
|
|
3249
|
+
const current = known.filter((row) => row.pid === options.pid);
|
|
3250
|
+
if (current.some((row) => Object.keys(row).some((key) => row[key] !== current[0][key]))) throw new Error("Multiple current native connection owners differ; preserve custody.");
|
|
3251
|
+
const retained = current[0] ?? known[0] ?? null;
|
|
3252
|
+
const previous = retained?.pid === options.pid ? retained : null;
|
|
3253
|
+
const sameStore = previous?.runtime === runtime;
|
|
3254
|
+
const secretPath = sameStore ? previous.secretPath : resolve(directory, plan.operationId + ".desktop-secret.json");
|
|
3255
|
+
if (!sameStore) durableRecord(secretPath, { token: randomBytes(32).toString("hex") }, "wx");
|
|
3256
|
+
const descriptor = sameStore ? previous : {
|
|
3257
|
+
pid: options.pid,
|
|
3258
|
+
runtime,
|
|
3259
|
+
identity: createHash2("sha256").update(JSON.stringify({ root, pack: options.pack, operationId: plan.operationId })).digest("hex"),
|
|
3260
|
+
socket: resolve("/tmp", "topolo-claude-task-" + plan.operationId + ".sock"),
|
|
3261
|
+
secretPath
|
|
3262
|
+
};
|
|
3263
|
+
const boundCoordinators = previous ? claudeTaskConnectionCoordinators(previous) : [];
|
|
3264
|
+
if (prior && boundCoordinators.length && JSON.stringify(prior.members) !== JSON.stringify(boundCoordinators)) throw new Error("Previous coordinator ownership proofs differ.");
|
|
3265
|
+
const previousCoordinators = prior?.members ?? boundCoordinators;
|
|
3266
|
+
const outgoing = previousCoordinators.filter((row) => !plan.members.some((member) => member.sessionId === row.sessionId));
|
|
3267
|
+
const allCoordinators = [...plan.members.map((m) => ({ sessionId: m.sessionId, cwd: m.cwd })), ...previousCoordinators];
|
|
3268
|
+
const coordinators = [...new Map(allCoordinators.map((row) => [row.sessionId, row])).values()];
|
|
3269
|
+
const native = await evaluate(options.endpoint, nativeClaudeDesktopControlExpression(
|
|
3270
|
+
options.pid,
|
|
3271
|
+
root,
|
|
3272
|
+
descriptor,
|
|
3273
|
+
secretPath,
|
|
3274
|
+
journal,
|
|
3275
|
+
{ existing: previous, coordinators }
|
|
3276
|
+
));
|
|
3277
|
+
if (!["native-desktop-task-control-installed", "native-desktop-task-control-reused"].includes(native.state) || native.pid !== options.pid || native.identity !== descriptor.identity)
|
|
3278
|
+
throw new Error("Native Desktop task control was not acknowledged.");
|
|
3279
|
+
const connectionFile = resolve(root, "claude/desktop-task-connection.json"), temporary = resolve(directory, plan.operationId + ".connection.json");
|
|
3280
|
+
durableRecord(temporary, descriptor, "wx");
|
|
3281
|
+
renameSync(temporary, connectionFile);
|
|
3282
|
+
syncDirectory(resolve(root, "claude"));
|
|
3283
|
+
const input = resolve(directory, plan.operationId + ".frontdoors.json");
|
|
3284
|
+
const binding = bind(root, options.pid, result.frontdoorSessions, input, void 0, descriptor);
|
|
3285
|
+
verifyPlanContext(plan);
|
|
3286
|
+
durableRecord(journal, { kind: "frontdoor-hooks-prepared", ...binding });
|
|
3287
|
+
result.frontdoorHooksPrepared = true;
|
|
3288
|
+
if (outgoing.length) {
|
|
3289
|
+
const retirement = await evaluate(options.endpoint, nativeClaudeCoordinatorRetirementExpression(options.pid, outgoing, plan.members, descriptor, journal));
|
|
3290
|
+
if (retirement.state !== "previous-coordinators-retired" || JSON.stringify(retirement.retired) !== JSON.stringify(outgoing.map((row) => row.sessionId)))
|
|
3291
|
+
throw new Error("Previous coordinator retirement was not acknowledged; reconcile without replay.");
|
|
3292
|
+
durableRecord(journal, { kind: "previous-coordinators-retired", retired: retirement.retired });
|
|
3293
|
+
result.previousCoordinatorsRetired = retirement.retired;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
unlinkSync(marker);
|
|
3297
|
+
syncDirectory(resolve(root, "claude"));
|
|
3298
|
+
}
|
|
3299
|
+
return { ...result, journal, packActivated: false, cloudDispatchVerified: false };
|
|
3300
|
+
} finally {
|
|
3301
|
+
release();
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
async function completeClaudeStartup(options, evaluate = evaluateClaudeMain, bind = bindClaudeFrontdoorHooks) {
|
|
3305
|
+
validateInspectorEndpoint(options.endpoint);
|
|
3306
|
+
const root = realpathSync(options.store), release = await lockClaudeLibrary(root);
|
|
3307
|
+
try {
|
|
3308
|
+
const original = readClaudeReadinessContinuation(root, options.pack, true), plan = original.plan;
|
|
3309
|
+
const prior = options.previousLibrary ? readClaudePreviousCoordinatorLibrary(options.previousLibrary, root) : null;
|
|
3310
|
+
const current = { ...prepareClaudeNativePlan(root, options.pack, options.policy, "configure", true), ...prior ? { previousCoordinators: prior.members } : {} };
|
|
3311
|
+
if (prior) {
|
|
3312
|
+
Object.assign(current.context, prior.context);
|
|
3313
|
+
current.globalContextFiles.push(...Object.keys(prior.context));
|
|
3314
|
+
}
|
|
3315
|
+
for (const key of Object.keys(current)) if (JSON.stringify(current[key]) !== JSON.stringify(plan[key])) throw Error("Late startup policy or original context differs.");
|
|
3316
|
+
const tail = original.completionRows, settings = tail.filter((r) => r.kind === "settings-verified"), ack = tail.filter((r) => r.kind === "startup-settings-verified");
|
|
3317
|
+
const allow = /* @__PURE__ */ new Set(["settings-baseline", "settings-pin-attempt", "settings-pin-acknowledged", "settings-verified", "startup-settings-verified", "native-python-capability-qualified", "native-desktop-task-control-installed", "native-desktop-task-control-reused", "frontdoor-hooks-prepared"]);
|
|
3318
|
+
if (tail.some((r) => !allow.has(r.kind)) || settings.length !== 1 || ack.length !== 1 || tail.filter((r) => r.kind === "settings-baseline").length !== 1 || JSON.stringify(settings[0].after) !== JSON.stringify(ack[0].after)) throw Error("Only acknowledged settings/control startup can complete.");
|
|
3319
|
+
const after = ack[0].after;
|
|
3320
|
+
if (!Array.isArray(after) || after.length !== 3 || after.some((r, i) => r.sessionId !== plan.members[i].sessionId || r.key !== plan.members[i].key || r.model !== plan.members[i].model || r.effort !== plan.members[i].effort || r.permissionMode !== plan.members[i].permissionMode || r.pinned !== true || r.processAttached !== true || r.startupPermissionVerified !== true)) throw Error("Original effective startup settings ACK differs.");
|
|
3321
|
+
for (const row of tail) {
|
|
3322
|
+
if (row.operationId !== void 0 && row.operationId !== plan.operationId) throw Error("Startup audit belongs to another operation.");
|
|
3323
|
+
if (row.kind.startsWith("settings-pin-") && (!plan.members.some((m) => m.sessionId === row.sessionId) || row.pinned !== true)) throw Error("Startup pin audit differs.");
|
|
3324
|
+
}
|
|
3325
|
+
const descriptor = readClaudeTaskConnection(resolve(root, "claude/desktop-task-connection.json"), options.pid);
|
|
3326
|
+
const control = tail.filter((r) => ["native-desktop-task-control-installed", "native-desktop-task-control-reused"].includes(r.kind));
|
|
3327
|
+
if (control.length !== 1 || Object.keys(descriptor).some((k) => control[0][k] !== descriptor[k])) throw Error("Original native control ACK differs.");
|
|
3328
|
+
const profile = readClaudeDesktopProfile(root);
|
|
3329
|
+
if (!profile || profile.state !== "native-profile-verified" || profile.pid !== options.pid) throw Error("Current native profile acknowledgement required.");
|
|
3330
|
+
const profileFile = resolve(root, "claude/desktop-profile.json"), profileDigest = createHash2("sha256").update(readFileSync(profileFile)).digest("hex");
|
|
3331
|
+
const input = resolve(root, "claude/journals", plan.operationId + ".frontdoors.json"), payload = JSON.parse(readFileSync(input, "utf8"));
|
|
3332
|
+
if (payload.desktopPid !== options.pid || JSON.stringify(payload.nativeConnection) !== JSON.stringify(descriptor) || payload.roles?.length !== 3 || payload.roles.some((r, i) => r.key !== plan.members[i].key || r.cwd !== plan.members[i].cwd)) throw Error("Original front-door input identity differs.");
|
|
3333
|
+
const inputDigest = createHash2("sha256").update(readFileSync(input)).digest("hex");
|
|
3334
|
+
const observed = await evaluate(options.endpoint, `(async()=>{if(process.pid!==${options.pid})throw Error('Native process changed');const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');const fs=load('node:fs'),crypto=load('node:crypto'),path=load('node:path'),caps=(${discoverClaudeNativeCapabilities.toString()})(load,process.resourcesPath),plan=${JSON.stringify(plan)},descriptor=${JSON.stringify(descriptor)},roles=${JSON.stringify(payload.roles)};
|
|
3335
|
+
const check=()=>{caps.assertCurrent();(${verifyClaudeDesktopProfileNative.toString()})(${JSON.stringify(profile.profile)},process.pid,load('electron').app,caps.manager,process.env);if(crypto.createHash('sha256').update(fs.readFileSync(${JSON.stringify(profileFile)})).digest('hex')!==${JSON.stringify(profileDigest)})throw Error('Profile receipt changed');(${verifyClaudePlanContext.toString()})(plan,plan.members,fs,crypto,path,(${snapshotClaudeMarkdown.toString()}));};check();
|
|
3336
|
+
const control=caps.manager[Symbol.for('topolo.agent-pack.desktop-task-control.v1')];if(!control||control.failed||control.active!==0||Object.keys(descriptor).some(k=>control.descriptor[k]!==descriptor[k]))throw Error('Existing native control changed');
|
|
3337
|
+
for(let i=0;i<plan.members.length;i++){const m=plan.members[i],raw=await caps.manager.getSessionWhenLoaded(m.sessionId,'topolo-late-startup',{warnOnMiss:false}),dto=await caps.manager.getSession(m.sessionId),history=await caps.manager.getTranscript(m.sessionId);check();if(!raw?.query||!dto||raw.cliSessionId!==roles[i].sessionId||raw.sessionId!==m.sessionId||dto.sessionId!==m.sessionId||raw.cwd!==m.cwd||dto.cwd!==m.cwd||raw.isArchived||dto.isArchived||raw.isRunning||raw.pendingCycle||raw.pendingToolPermissions?.length||caps.manager.hasLosableWork(m.sessionId)||raw.bypassAvailableAtSpawn!==true||dto.isStarred!==true||['model','effort','permissionMode'].some(k=>dto[k]!==m[k]))throw Error('Native coordinator settings or custody changed');const users=history.filter(r=>r.type==='user'),text=users[0]?.message?.content;if(users.length!==1||users[0].uuid!==m.sessionId.slice(6)||text!=='Installation readiness probe only. Do not use tools, read task files, dispatch work or perform any task. Reply exactly TOPOLO_WORKER_READY:'+m.sessionId.slice(6)||history.some(r=>r.type==='assistant'&&r.message?.content?.some(c=>c.type==='tool_use')))throw Error('Coordinator received work after readiness');}
|
|
3338
|
+
if(plan.previousCoordinators?.length)await (${verifyClaudePreviousCoordinatorCustody.toString()})(caps.manager,plan.previousCoordinators,check);check();return{state:'late-startup-native-custody-verified'};})()`);
|
|
3339
|
+
if (observed.state !== "late-startup-native-custody-verified" || readFileSync(original.journal, "utf8") !== original.journalText || createHash2("sha256").update(readFileSync(input)).digest("hex") !== inputDigest) throw Error("Late startup readback or original journal changed.");
|
|
3340
|
+
const binding = bind(root, options.pid, payload.roles, input, void 0, descriptor, true);
|
|
3341
|
+
verifyPlanContext(plan);
|
|
3342
|
+
if (!tail.some((r) => r.kind === "frontdoor-hooks-prepared")) durableRecord(original.journal, { kind: "frontdoor-hooks-prepared", ...binding });
|
|
3343
|
+
if (plan.previousCoordinators?.length) {
|
|
3344
|
+
const retired = await evaluate(options.endpoint, nativeClaudeCoordinatorRetirementExpression(options.pid, plan.previousCoordinators, plan.members, descriptor, original.journal));
|
|
3345
|
+
if (retired.state !== "previous-coordinators-retired" || JSON.stringify(retired.retired) !== JSON.stringify(plan.previousCoordinators.map((r) => r.sessionId))) throw Error("Previous coordinator retirement not acknowledged.");
|
|
3346
|
+
durableRecord(original.journal, { kind: "previous-coordinators-retired", retired: retired.retired });
|
|
3347
|
+
}
|
|
3348
|
+
verifyPlanContext(plan);
|
|
3349
|
+
unlinkSync(resolve(root, "claude/startup-operation.json"));
|
|
3350
|
+
syncDirectory(resolve(root, "claude"));
|
|
3351
|
+
return { state: "native-startup-completed", operationId: plan.operationId, frontdoorHooksPrepared: true, nativeInputsReplayed: false, originalTerminalUnknownPreserved: true, cloudDispatchVerified: false, packActivated: false, journal: original.journal };
|
|
3352
|
+
} finally {
|
|
3353
|
+
release();
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
1741
3356
|
function runClaudeSmokeHelper(root, action, payload, source, launch = spawn) {
|
|
1742
3357
|
if (!["prepare", "arm", "mark", "read", "verify"].includes(action) || typeof root !== "string" || typeof source !== "string")
|
|
1743
3358
|
return Promise.reject(new Error("Closed owned smoke helper required."));
|
|
@@ -2399,27 +4014,50 @@ function operateClaudeLaneControl(options, request = requestClaudeApprovalContro
|
|
|
2399
4014
|
const secret = JSON.parse(readFileSync(connection.secretPath, "utf8"));
|
|
2400
4015
|
return request(connection, options.operation, secret.token);
|
|
2401
4016
|
}
|
|
4017
|
+
|
|
2402
4018
|
export {
|
|
4019
|
+
observeClaudeFrontdoors,
|
|
4020
|
+
discoverClaudeNativeCapabilities,
|
|
4021
|
+
snapshotClaudeWorkerSource,
|
|
4022
|
+
materializeClaudeWorkerGuard,
|
|
4023
|
+
qualifyClaudePython,
|
|
4024
|
+
nativeClaudeDesktopControlExpression,
|
|
4025
|
+
nativeClaudeSettingsExpression,
|
|
2403
4026
|
CLAUDE_NATIVE_QUALIFIER,
|
|
2404
|
-
|
|
2405
|
-
|
|
4027
|
+
validateInspectorEndpoint,
|
|
4028
|
+
snapshotClaudeMarkdown,
|
|
4029
|
+
prepareClaudeNativePlan,
|
|
4030
|
+
verifyClaudePlanContext,
|
|
4031
|
+
nativePackExpression,
|
|
2406
4032
|
evaluateClaudeMain,
|
|
2407
4033
|
lockClaudeLibrary,
|
|
2408
|
-
|
|
4034
|
+
verifyClaudeOrganizationPolicy,
|
|
4035
|
+
configureClaudePack,
|
|
4036
|
+
materializeClaudeHookAdapter,
|
|
4037
|
+
bindClaudeFrontdoorHooks,
|
|
4038
|
+
readClaudeTaskConnection,
|
|
4039
|
+
claudeTaskConnectionCoordinators,
|
|
4040
|
+
readClaudePreviousCoordinatorLibrary,
|
|
4041
|
+
verifyClaudePreviousCoordinatorCustody,
|
|
4042
|
+
retireClaudeCoordinators,
|
|
4043
|
+
ensureClaudeCoordinators,
|
|
4044
|
+
prepareClaudeStartup,
|
|
4045
|
+
reconcileClaudeStartupAbsence,
|
|
4046
|
+
recoverClaudeStartup,
|
|
4047
|
+
readClaudeReadinessContinuation,
|
|
4048
|
+
verifyClaudeReadinessContinuation,
|
|
4049
|
+
configureClaudeStartup,
|
|
4050
|
+
completeClaudeStartup,
|
|
4051
|
+
runClaudeSmokeHelper,
|
|
4052
|
+
requestClaudeApprovalControl,
|
|
2409
4053
|
operateClaudeApprovals,
|
|
2410
|
-
operateClaudeLane,
|
|
2411
|
-
operateClaudeLaneControl,
|
|
2412
4054
|
operateClaudeSmoke,
|
|
2413
4055
|
prepareClaudeDynamicLane,
|
|
2414
|
-
prepareClaudeNativePlan,
|
|
2415
|
-
prepareClaudePrivateHelperConnection,
|
|
2416
4056
|
privateClaudeHelperEnvironment,
|
|
2417
|
-
|
|
2418
|
-
requireClaudePrivateQueryEffectQualified,
|
|
4057
|
+
prepareClaudePrivateHelperConnection,
|
|
2419
4058
|
runClaudeLaneHelper,
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
verifyClaudePlanContext
|
|
4059
|
+
requireClaudePrivateQueryEffectQualified,
|
|
4060
|
+
claudeLaneExpression,
|
|
4061
|
+
operateClaudeLane,
|
|
4062
|
+
operateClaudeLaneControl
|
|
2425
4063
|
};
|