blun-king-cli 9.1.536 → 9.1.561
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/LIESMICH.txt +13 -869
- package/README.md +41 -833
- package/bin/assistant-message-offload-policy.cjs +3 -1
- package/bin/compaction-transaction-policy.cjs +122 -0
- package/bin/context-performance-policy.cjs +2 -5
- package/bin/context-pressure-policy.cjs +20 -0
- package/bin/cron-run-output.cjs +45 -0
- package/bin/cron-run-store.cjs +145 -0
- package/bin/default-model-output-budget-policy.cjs +28 -0
- package/bin/durable-task-resume-policy.cjs +130 -0
- package/bin/durable-task-resume-runtime.cjs +117 -0
- package/bin/durable-task-resume-store.cjs +88 -0
- package/bin/editable-tool-approval-policy.cjs +540 -0
- package/bin/editable-tool-approval-runtime.cjs +99 -0
- package/bin/file-observation-policy.cjs +133 -0
- package/bin/html-to-research-markdown.cjs +146 -0
- package/bin/launcher-runtime.js +0 -1
- package/bin/micro-compaction-policy.cjs +64 -0
- package/bin/mnemo-connect-heartbeat.cjs +1 -3
- package/bin/programmatic-tool-runtime.mjs +330 -4
- package/bin/read-continuation-policy.cjs +36 -5
- package/bin/retry-checkpoint-policy.cjs +13 -0
- package/bin/scoped-cron-run-policy.cjs +358 -0
- package/bin/session-checkpoint-policy.cjs +25 -0
- package/bin/startup-preferences.cjs +4 -3
- package/bin/structured-agent-swarm-output.cjs +325 -0
- package/bin/subagent-context-fork-policy.cjs +155 -0
- package/bin/subagent-skill-policy.cjs +204 -0
- package/bin/telegram-approval-relay.cjs +2 -1
- package/bin/tool-file-persistence.cjs +141 -0
- package/bin/tool-result-offload-policy.cjs +25 -33
- package/bin/turn-thinking-policy.cjs +2 -26
- package/bin/update-notice.js +30 -18
- package/bin/user-message-offload-policy.cjs +3 -1
- package/blun.mjs +1564 -640
- package/codebase-index/README.md +12 -0
- package/codebase-index/codebase_index.py +129 -18
- package/package.json +23 -58
- package/telegram-plugin/bin/telegram-mnemo-capture.cjs +1 -3
- package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
- package/telegram-plugin/dist/bridge.mjs +8 -1
- package/CHANGELOG.md +0 -321
- package/agent-spine-plugin/CHANGELOG.md +0 -406
- package/agent-spine-plugin/CONTRIBUTING.md +0 -52
- package/agent-spine-plugin/README.md +0 -344
- package/agent-spine-plugin/SECURITY.md +0 -47
- package/agent-spine-plugin/docs/acceptance.md +0 -61
- package/agent-spine-plugin/docs/architecture.md +0 -183
- package/agent-spine-plugin/docs/attention.md +0 -121
- package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
- package/agent-spine-plugin/docs/channel-runtime.md +0 -92
- package/agent-spine-plugin/docs/coordination.md +0 -138
- package/agent-spine-plugin/docs/feed-transport.md +0 -99
- package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
- package/agent-spine-plugin/docs/harness-reference.md +0 -45
- package/agent-spine-plugin/docs/host-integration.md +0 -129
- package/agent-spine-plugin/docs/https-transport.md +0 -116
- package/agent-spine-plugin/docs/learning.md +0 -133
- package/agent-spine-plugin/docs/object-transport.md +0 -93
- package/agent-spine-plugin/docs/peer-transport.md +0 -88
- package/agent-spine-plugin/docs/preflight-recall.md +0 -69
- package/agent-spine-plugin/docs/preservation-contract.md +0 -53
- package/agent-spine-plugin/docs/quality-gates.md +0 -50
- package/agent-spine-plugin/docs/relationships.md +0 -73
- package/agent-spine-plugin/docs/releasing.md +0 -83
- package/agent-spine-plugin/docs/roadmap.md +0 -307
- package/agent-spine-plugin/docs/selfstarter.md +0 -88
- package/agent-spine-plugin/docs/session-briefing.md +0 -74
- package/agent-spine-plugin/docs/shared-memory.md +0 -259
- package/agent-spine-plugin/docs/source-roots.md +0 -86
- package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
- package/agent-spine-plugin/scripts/check-hosts.js +0 -195
- package/agent-spine-plugin/scripts/check-install.js +0 -569
- package/agent-spine-plugin/scripts/check-syntax.js +0 -29
- package/agent-spine-plugin/scripts/github-actions.js +0 -11
- package/agent-spine-plugin/scripts/release-check.js +0 -128
- package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
- package/agent-spine-plugin/scripts/run-checks.js +0 -46
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
- package/agent-spine-plugin/spine-example/1-identity.md +0 -12
- package/agent-spine-plugin/spine-example/2-voice.md +0 -6
- package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
- package/agent-spine-plugin/spine-example/4-history.md +0 -4
- package/bin/empty-response-retry-policy.cjs +0 -29
- package/bin/fredrik-glm-provider.cjs +0 -256
- package/bin/package-regression-policy.cjs +0 -77
- package/fredrik-glm-profile.toml.example +0 -26
- package/release-planned-removals.json +0 -15
- package/scripts/check-active-profile-plugin-startup.js +0 -36
- package/scripts/check-active-work-steer-regression.js +0 -46
- package/scripts/check-approval-observability-regression.js +0 -111
- package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
- package/scripts/check-bundled-agent-spine-regression.js +0 -48
- package/scripts/check-codebase-search-packaging-regression.js +0 -92
- package/scripts/check-copy-command-regression.js +0 -74
- package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
- package/scripts/check-current-turn-read-pin-regression.js +0 -94
- package/scripts/check-deepseek-native-max-regression.js +0 -49
- package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
- package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
- package/scripts/check-fredrik-glm-regression.js +0 -169
- package/scripts/check-historical-tool-result-preview-regression.js +0 -77
- package/scripts/check-history-pressure-offload-regression.js +0 -77
- package/scripts/check-mcp-startup-wait-budget.js +0 -48
- package/scripts/check-package-regression.js +0 -38
- package/scripts/check-plugin-startup-regression.js +0 -53
- package/scripts/check-programmatic-context-isolation-regression.js +0 -193
- package/scripts/check-programmatic-tool-regression.js +0 -294
- package/scripts/check-queue-controls-regression.js +0 -189
- package/scripts/check-release-metadata.js +0 -103
- package/scripts/check-reload-agent-spine-regression.js +0 -76
- package/scripts/check-resume-replay-regression.js +0 -102
- package/scripts/check-session-cancel-regression.js +0 -43
- package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
- package/scripts/check-session-start-hook-context-regression.js +0 -228
- package/scripts/check-shell-terminal-isolation-regression.js +0 -81
- package/scripts/check-slash-escape-regression.js +0 -89
- package/scripts/check-startup-swarm-command-regression.js +0 -24
- package/scripts/check-structured-subagent-output-regression.js +0 -331
- package/scripts/check-telegram-bridge-watchdog.js +0 -60
- package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
- package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
- package/scripts/check-todo-loop-regression.js +0 -78
- package/scripts/check-todo-progress-regression.js +0 -416
- package/scripts/check-todo-recovery-catalog-regression.js +0 -50
- package/scripts/check-tool-schema-capacity-regression.js +0 -40
- package/scripts/programmatic-tool-runtime.test.mjs +0 -365
- package/scripts/structured-subagent-output.test.cjs +0 -170
- /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createHash, createHmac } from "node:crypto";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { cp, mkdir, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { basename, join, resolve } from "node:path";
|
|
7
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
|
-
import { checkHosts } from "./check-hosts.js";
|
|
10
|
-
|
|
11
|
-
function hash(value) {
|
|
12
|
-
return createHash("sha256").update(value).digest("hex");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function copyFilter(source) {
|
|
16
|
-
const name = basename(source);
|
|
17
|
-
return !new Set([".git", "node_modules"]).has(name) && !name.endsWith(".tgz");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function copyBundle(source, target) {
|
|
21
|
-
await cp(source, target, { recursive: true, filter: copyFilter });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function removeTree(path, options = {}) {
|
|
25
|
-
for (let attempt = 0; ; attempt += 1) {
|
|
26
|
-
try {
|
|
27
|
-
await rm(path, { recursive: true, ...options });
|
|
28
|
-
return;
|
|
29
|
-
} catch (error) {
|
|
30
|
-
const transient = process.platform === "win32" && ["EACCES", "EBUSY", "EPERM"].includes(error?.code);
|
|
31
|
-
if (!transient || attempt >= 7) throw error;
|
|
32
|
-
await delay(10 * (attempt + 1));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function makePreviousCache(target) {
|
|
38
|
-
for (const path of ["package.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json"]) {
|
|
39
|
-
const file = join(target, path);
|
|
40
|
-
const value = JSON.parse(await readFile(file, "utf8"));
|
|
41
|
-
value.version = "0.8.0";
|
|
42
|
-
await writeFile(file, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
43
|
-
}
|
|
44
|
-
const marketplacePath = join(target, ".claude-plugin/marketplace.json");
|
|
45
|
-
const marketplace = JSON.parse(await readFile(marketplacePath, "utf8"));
|
|
46
|
-
marketplace.plugins[0].version = "0.8.0";
|
|
47
|
-
await writeFile(marketplacePath, `${JSON.stringify(marketplace, null, 2)}\n`, "utf8");
|
|
48
|
-
const hookVersionPath = join(target, "hooks/version.json");
|
|
49
|
-
const hookVersion = JSON.parse(await readFile(hookVersionPath, "utf8"));
|
|
50
|
-
hookVersion.version = "0.8.0";
|
|
51
|
-
await writeFile(hookVersionPath, `${JSON.stringify(hookVersion, null, 2)}\n`, "utf8");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, payload = null, { requireBriefing = true, extraEnv = {} } = {}) {
|
|
55
|
-
return await new Promise((resolveResult, reject) => {
|
|
56
|
-
const child = spawn(process.execPath, [join(pluginRoot, "src/hook.js")], {
|
|
57
|
-
cwd: projectRoot,
|
|
58
|
-
env: {
|
|
59
|
-
...process.env,
|
|
60
|
-
AGENTSPINE_HOST: host,
|
|
61
|
-
AGENTSPINE_STATE_DIR: stateRoot,
|
|
62
|
-
CLAUDE_PLUGIN_ROOT: pluginRoot,
|
|
63
|
-
PLUGIN_ROOT: pluginRoot,
|
|
64
|
-
...extraEnv
|
|
65
|
-
},
|
|
66
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
67
|
-
});
|
|
68
|
-
let stdout = "";
|
|
69
|
-
let stderr = "";
|
|
70
|
-
const timer = setTimeout(() => {
|
|
71
|
-
child.kill();
|
|
72
|
-
reject(new Error(`${host} installed hook timed out`));
|
|
73
|
-
}, 5000);
|
|
74
|
-
child.stdout.setEncoding("utf8");
|
|
75
|
-
child.stderr.setEncoding("utf8");
|
|
76
|
-
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
77
|
-
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
78
|
-
child.once("error", reject);
|
|
79
|
-
child.once("exit", (code) => {
|
|
80
|
-
clearTimeout(timer);
|
|
81
|
-
if (code !== 0) return reject(new Error(`${host} installed hook exited with ${code}: ${stderr.slice(0, 2048)}`));
|
|
82
|
-
try {
|
|
83
|
-
const protocol = JSON.parse(stdout.trim());
|
|
84
|
-
const context = JSON.parse(protocol.hookSpecificOutput?.additionalContext || "null");
|
|
85
|
-
if (requireBriefing && (context?.briefing?.host !== host || !Array.isArray(context?.briefing?.sources?.documents))) {
|
|
86
|
-
throw new Error(`${host} installed hook did not inject a real session briefing: ${protocol.reason || context?.error || context?.sourceResolution?.reason || "missing context"}`);
|
|
87
|
-
}
|
|
88
|
-
resolveResult({
|
|
89
|
-
event: protocol.hookSpecificOutput?.hookEventName || payload?.hook_event_name || null, host,
|
|
90
|
-
sources: context?.briefing?.sources?.documents?.length ?? null,
|
|
91
|
-
sourceIds: context?.briefing?.sources?.documents?.map((item) => item.path) || [],
|
|
92
|
-
sourceContents: context?.briefing?.sources?.documents?.map((item) => item.content).filter(Boolean) || [],
|
|
93
|
-
sourceResolution: context?.sourceResolution || null,
|
|
94
|
-
learningClaims: context?.briefing?.learning?.map((item) => item.claim) || [],
|
|
95
|
-
attentionKinds: context?.briefing?.attention?.items?.map((item) => item.kind) || [],
|
|
96
|
-
capturedAttentionKind: context?.attentionEvent?.kind || null,
|
|
97
|
-
selfstarter: context?.selfstarter || null,
|
|
98
|
-
channelEvent: context?.channelEvent || null,
|
|
99
|
-
voiceBrief: context?.briefing?.voiceBrief || null,
|
|
100
|
-
preflight: context?.preflight || null,
|
|
101
|
-
decision: protocol.decision || null,
|
|
102
|
-
reason: protocol.reason || null
|
|
103
|
-
});
|
|
104
|
-
} catch (error) {
|
|
105
|
-
reject(error);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
child.stdin.end(`${JSON.stringify(payload || { hook_event_name: "SessionStart", cwd: projectRoot, host })}\n`);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function invokeInstalledLiveRoots(pluginRoot, workspace, stateRoot) {
|
|
113
|
-
const home = join(workspace, "live-home");
|
|
114
|
-
const claudeHome = join(home, ".claude-live");
|
|
115
|
-
const codexHome = join(home, ".codex-live");
|
|
116
|
-
const projectA = join(home, "AgentSpine");
|
|
117
|
-
const projectB = join(home, "other-project");
|
|
118
|
-
const nestedB = join(projectB, "api");
|
|
119
|
-
const loose = join(workspace, "foreign-cwd");
|
|
120
|
-
const memory = join(claudeHome, "projects", "project-a", "memory");
|
|
121
|
-
for (const path of [claudeHome, codexHome, projectA, nestedB, loose, memory]) await mkdir(path, { recursive: true });
|
|
122
|
-
await mkdir(join(projectA, ".git"));
|
|
123
|
-
await mkdir(join(projectB, ".git"));
|
|
124
|
-
const protectedSources = new Map([
|
|
125
|
-
[join(claudeHome, "CLAUDE.md"), "# Installed user style\n\nBe calm and humane.\n"],
|
|
126
|
-
[join(projectA, "CLAUDE.md"), "# Installed project A\n"],
|
|
127
|
-
[join(projectB, "CLAUDE.md"), "# Installed project B\n"],
|
|
128
|
-
[join(memory, "MEMORY.md"), "# Installed project A memory\n\n- [Communication style](style.md) <!-- agentspine:always -->\n"],
|
|
129
|
-
[join(memory, "style.md"), "# Installed indexed style\n\nKeep answers calm and direct.\n"],
|
|
130
|
-
[join(memory, "unindexed.md"), "# Installed unindexed private note\n\nThis must never enter the live hook.\n"],
|
|
131
|
-
[join(codexHome, "AGENTS.override.md"), "# Installed Codex user style\n"],
|
|
132
|
-
[join(projectA, "AGENTS.md"), "# Installed Codex A\n"],
|
|
133
|
-
[join(projectB, "TEAM_GUIDE.md"), "# Installed Codex B\n"],
|
|
134
|
-
[join(nestedB, "AGENTS.override.md"), "# Installed Codex API override\n"],
|
|
135
|
-
[join(home, "PRIVATE.md"), "# Never scan this home file\n"]
|
|
136
|
-
]);
|
|
137
|
-
for (const [path, content] of protectedSources) await writeFile(path, content, "utf8");
|
|
138
|
-
const transcript = join(claudeHome, "projects", "project-a", "session.jsonl");
|
|
139
|
-
await writeFile(transcript, "{}\n", "utf8");
|
|
140
|
-
await writeFile(join(loose, "CLAUDE.md"), "# Installed loose cwd\n", "utf8");
|
|
141
|
-
await writeFile(join(codexHome, "config.toml"), [
|
|
142
|
-
'project_doc_fallback_filenames = ["TEAM_GUIDE.md"]', 'project_root_markers = [".git"]', ""
|
|
143
|
-
].join("\n"), "utf8");
|
|
144
|
-
const before = new Map(await Promise.all([...protectedSources.keys()].map(async (path) => [path, hash(await readFile(path))])));
|
|
145
|
-
const claudeEnv = { HOME: home, CLAUDE_CONFIG_DIR: claudeHome };
|
|
146
|
-
const codexEnv = { HOME: home, CODEX_HOME: codexHome };
|
|
147
|
-
const claudeA = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "claude", {
|
|
148
|
-
hook_event_name: "SessionStart", host: "claude", cwd: projectA, transcript_path: transcript
|
|
149
|
-
}, { extraEnv: claudeEnv });
|
|
150
|
-
const claudeRestart = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "claude", {
|
|
151
|
-
hook_event_name: "PostCompact", host: "claude", cwd: projectA
|
|
152
|
-
}, { extraEnv: claudeEnv });
|
|
153
|
-
const claudeLoose = await invokeInstalledHook(pluginRoot, loose, stateRoot, "claude", {
|
|
154
|
-
hook_event_name: "SessionStart", host: "claude", cwd: loose
|
|
155
|
-
}, { extraEnv: claudeEnv });
|
|
156
|
-
const codexA = await invokeInstalledHook(pluginRoot, projectA, stateRoot, "codex", {
|
|
157
|
-
hook_event_name: "SessionStart", host: "codex", cwd: projectA
|
|
158
|
-
}, { extraEnv: codexEnv });
|
|
159
|
-
const codexB = await invokeInstalledHook(pluginRoot, nestedB, stateRoot, "codex", {
|
|
160
|
-
hook_event_name: "PostCompact", host: "codex", cwd: nestedB
|
|
161
|
-
}, { extraEnv: codexEnv });
|
|
162
|
-
if (!claudeA.sourceIds.includes("claude:user/CLAUDE.md") || !claudeA.sourceIds.includes("claude:memory/MEMORY.md")
|
|
163
|
-
|| !claudeA.sourceIds.includes("claude:memory/style.md") || claudeA.sourceIds.includes("claude:memory/unindexed.md")
|
|
164
|
-
|| !claudeRestart.sourceIds.includes("claude:memory/MEMORY.md") || !claudeRestart.sourceIds.includes("claude:memory/style.md")
|
|
165
|
-
|| claudeRestart.sourceIds.includes("claude:memory/unindexed.md") || claudeLoose.sourceIds.some((id) => id.startsWith("claude:memory/"))) {
|
|
166
|
-
throw new Error("installed Claude hook did not preserve exact user/project/memory source scope across restart and foreign cwd");
|
|
167
|
-
}
|
|
168
|
-
for (const result of [claudeA, claudeRestart]) {
|
|
169
|
-
const diagnostic = result.sourceResolution?.memory;
|
|
170
|
-
if (diagnostic?.indexed !== 1 || diagnostic.loaded !== 1 || diagnostic.directoryEnumeration !== 0) {
|
|
171
|
-
throw new Error("installed Claude hook did not use bounded indexed-memory resolution");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (claudeA.sourceContents.some((content) => content.includes("Never scan this"))
|
|
175
|
-
|| claudeLoose.sourceContents.some((content) => content.includes("Installed project A"))) {
|
|
176
|
-
throw new Error("installed Claude hook leaked a broad-home or foreign-project source");
|
|
177
|
-
}
|
|
178
|
-
const codexAIds = codexA.sourceIds.filter((id) => id.startsWith("codex:"));
|
|
179
|
-
const codexBIds = codexB.sourceIds.filter((id) => id.startsWith("codex:"));
|
|
180
|
-
if (JSON.stringify(codexAIds) !== JSON.stringify(["codex:user/AGENTS.override.md", "codex:project/AGENTS.md"])
|
|
181
|
-
|| JSON.stringify(codexBIds) !== JSON.stringify(["codex:user/AGENTS.override.md", "codex:project/TEAM_GUIDE.md", "codex:project/api/AGENTS.override.md"])) {
|
|
182
|
-
throw new Error("installed Codex hook did not preserve native override and fallback precedence");
|
|
183
|
-
}
|
|
184
|
-
for (const [path, expected] of before) {
|
|
185
|
-
if (hash(await readFile(path)) !== expected) throw new Error(`installed live-root hook changed a protected source: ${basename(path)}`);
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
claude: { project: claudeA.sourceIds, restart: claudeRestart.sourceIds, foreign: claudeLoose.sourceIds,
|
|
189
|
-
indexedMemory: claudeA.sourceResolution.memory },
|
|
190
|
-
codex: { projectA: codexAIds, projectB: codexBIds }, broadHomeScan: false, mcpCalls: 0
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function prepareInstalledSelfstarter(pluginRoot, projectRoot, stateRoot, host) {
|
|
195
|
-
const previous = process.env.AGENTSPINE_STATE_DIR;
|
|
196
|
-
process.env.AGENTSPINE_STATE_DIR = stateRoot;
|
|
197
|
-
try {
|
|
198
|
-
const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
|
|
199
|
-
const coordination = await import(pathToFileURL(join(pluginRoot, "src/lib/coordination.js")).href);
|
|
200
|
-
const selfstarter = await import(pathToFileURL(join(pluginRoot, "src/lib/selfstarter.js")).href);
|
|
201
|
-
for (const [id, kind] of [
|
|
202
|
-
["person:install-owner", "person"], ["agent:install-worker", "agent"], ["project:install-runtime", "project"]
|
|
203
|
-
]) await graph.upsertEntity({ root: projectRoot, id, kind, privacy: "shared" });
|
|
204
|
-
await coordination.createTask({
|
|
205
|
-
root: projectRoot, id: `task:install-${host}`, actorId: "agent:install-worker", assigneeId: "agent:install-worker",
|
|
206
|
-
projectId: "project:install-runtime", title: `Installed ${host} self-starter check`, privacy: "private"
|
|
207
|
-
});
|
|
208
|
-
await selfstarter.grantExecution({
|
|
209
|
-
root: projectRoot, id: `execution-grant:install-${host}`, jobId: `job:install-${host}`,
|
|
210
|
-
actorId: "agent:install-worker", taskId: `task:install-${host}`, targetId: "person:install-owner",
|
|
211
|
-
projectId: "project:install-runtime", host, capabilities: ["tool:Write"],
|
|
212
|
-
reason: `Owner approved the exact installed ${host} synthetic write.`, confirmation: "local-owner-confirmed"
|
|
213
|
-
});
|
|
214
|
-
await selfstarter.registerJob({
|
|
215
|
-
root: projectRoot, id: `job:install-${host}`, grantId: `execution-grant:install-${host}`,
|
|
216
|
-
confirmation: "local-owner-confirmed"
|
|
217
|
-
});
|
|
218
|
-
} finally {
|
|
219
|
-
if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
|
|
220
|
-
else process.env.AGENTSPINE_STATE_DIR = previous;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function invokeInstalledSelfstarter(pluginRoot, projectRoot, stateRoot, host) {
|
|
225
|
-
await prepareInstalledSelfstarter(pluginRoot, projectRoot, stateRoot, host);
|
|
226
|
-
const session = `session:install-${host}-one`;
|
|
227
|
-
const nextSession = `session:install-${host}-two`;
|
|
228
|
-
const toolUseId = `tool:install-${host}`;
|
|
229
|
-
const taskId = `task:install-${host}`;
|
|
230
|
-
const artifact = join(projectRoot, `installed-${host}-artifact.txt`);
|
|
231
|
-
const shared = {
|
|
232
|
-
cwd: projectRoot, host, entity_id: "agent:install-worker", project_id: "project:install-runtime",
|
|
233
|
-
task_id: taskId
|
|
234
|
-
};
|
|
235
|
-
const started = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
236
|
-
...shared, hook_event_name: "SessionStart", session_id: session
|
|
237
|
-
});
|
|
238
|
-
if (!started.selfstarter?.active || started.selfstarter.action !== "start") {
|
|
239
|
-
throw new Error(`${host} installed hook did not start the exact authorized job`);
|
|
240
|
-
}
|
|
241
|
-
const authorized = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
242
|
-
...shared, hook_event_name: "PreToolUse", session_id: session,
|
|
243
|
-
tool_name: "Write", tool_use_id: toolUseId,
|
|
244
|
-
tool_input: { file_path: artifact, content: `installed ${host} checkpoint\n` }
|
|
245
|
-
}, { requireBriefing: false });
|
|
246
|
-
if (authorized.decision === "block") throw new Error(`${host} installed PreToolUse denied the exact authorized effect: ${authorized.reason}`);
|
|
247
|
-
await writeFile(artifact, `installed ${host} checkpoint\n`, "utf8");
|
|
248
|
-
await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
249
|
-
...shared, hook_event_name: "PostToolUse", session_id: session,
|
|
250
|
-
tool_name: "Write", tool_use_id: toolUseId, success: true, tool_result: { ok: true }
|
|
251
|
-
}, { requireBriefing: false });
|
|
252
|
-
await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
253
|
-
...shared, hook_event_name: "Stop", session_id: session
|
|
254
|
-
}, { requireBriefing: false });
|
|
255
|
-
const resumed = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
256
|
-
...shared, hook_event_name: "SessionStart", session_id: nextSession
|
|
257
|
-
});
|
|
258
|
-
if (!resumed.selfstarter?.active || resumed.selfstarter.action !== "resume" || resumed.selfstarter.checkpointSequence !== 1) {
|
|
259
|
-
throw new Error(`${host} installed hook did not resume the durable checkpoint`);
|
|
260
|
-
}
|
|
261
|
-
return {
|
|
262
|
-
started: started.selfstarter.action, resumed: resumed.selfstarter.action,
|
|
263
|
-
checkpointSequence: resumed.selfstarter.checkpointSequence, mcpCalls: 0
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function invokeInstalledAcceptance(pluginRoot) {
|
|
268
|
-
const acceptance = await import(pathToFileURL(join(pluginRoot, "src/lib/acceptance.js")).href);
|
|
269
|
-
const report = await acceptance.runVisibleAcceptance();
|
|
270
|
-
if (!report.ok || report.passed !== report.total || report.mcpCalls !== 0) {
|
|
271
|
-
throw new Error("installed bundle did not pass visible lifecycle acceptance");
|
|
272
|
-
}
|
|
273
|
-
return {
|
|
274
|
-
passed: report.passed,
|
|
275
|
-
total: report.total,
|
|
276
|
-
receiptDigest: report.receiptDigest,
|
|
277
|
-
hosts: report.hosts,
|
|
278
|
-
languages: report.languages,
|
|
279
|
-
mcpCalls: report.mcpCalls
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function prepareInstalledAttention(pluginRoot, projectRoot, stateRoot) {
|
|
284
|
-
const previous = process.env.AGENTSPINE_STATE_DIR;
|
|
285
|
-
process.env.AGENTSPINE_STATE_DIR = stateRoot;
|
|
286
|
-
try {
|
|
287
|
-
const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
|
|
288
|
-
const coordination = await import(pathToFileURL(join(pluginRoot, "src/lib/coordination.js")).href);
|
|
289
|
-
const continuity = await import(pathToFileURL(join(pluginRoot, "src/lib/continuity.js")).href);
|
|
290
|
-
await graph.upsertEntity({ root: projectRoot, id: "person:install", kind: "person", privacy: "shared" });
|
|
291
|
-
await graph.upsertEntity({ root: projectRoot, id: "project:install", kind: "project", privacy: "shared" });
|
|
292
|
-
await coordination.createTask({
|
|
293
|
-
root: projectRoot, id: "task:install", actorId: "person:install", assigneeId: "person:install",
|
|
294
|
-
projectId: "project:install", title: "Installed lifecycle check", privacy: "private"
|
|
295
|
-
});
|
|
296
|
-
await continuity.configureContinuity({
|
|
297
|
-
root: projectRoot, config: { enabled: true }, confirmation: "local-user-opt-in"
|
|
298
|
-
});
|
|
299
|
-
} finally {
|
|
300
|
-
if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
|
|
301
|
-
else process.env.AGENTSPINE_STATE_DIR = previous;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function invokeInstalledAttention(pluginRoot, projectRoot, stateRoot, host) {
|
|
306
|
-
await prepareInstalledAttention(pluginRoot, projectRoot, stateRoot);
|
|
307
|
-
const shared = {
|
|
308
|
-
cwd: projectRoot, host, entity_id: "person:install",
|
|
309
|
-
project_id: "project:install", task_id: "task:install", session_id: `session:installed-${host}-attention`
|
|
310
|
-
};
|
|
311
|
-
const captured = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
312
|
-
...shared, hook_event_name: "UserPromptSubmit", event_id: "install:promise",
|
|
313
|
-
prompt: "I promise to verify the installed lifecycle."
|
|
314
|
-
});
|
|
315
|
-
const restarted = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
316
|
-
...shared, hook_event_name: "SessionStart", session_id: "install:restart"
|
|
317
|
-
});
|
|
318
|
-
if (captured.capturedAttentionKind !== "promise" || !restarted.attentionKinds.includes("promise")) {
|
|
319
|
-
throw new Error(`${host} installed hooks did not persist and inject an attention event`);
|
|
320
|
-
}
|
|
321
|
-
return { captured: captured.capturedAttentionKind, restarted: restarted.attentionKinds,
|
|
322
|
-
preflight: captured.preflight ? { schema: captured.preflight.schema,
|
|
323
|
-
receiptId: captured.preflight.receiptId, instructions: captured.preflight.briefing?.instructions?.length || 0 } : null };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function prepareInstalledChannelWake(pluginRoot, projectRoot, stateRoot) {
|
|
327
|
-
const previous = process.env.AGENTSPINE_STATE_DIR;
|
|
328
|
-
process.env.AGENTSPINE_STATE_DIR = stateRoot;
|
|
329
|
-
try {
|
|
330
|
-
const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
|
|
331
|
-
const channel = await import(pathToFileURL(join(pluginRoot, "src/lib/channel-runtime.js")).href);
|
|
332
|
-
await graph.upsertEntity({
|
|
333
|
-
root: projectRoot, id: "agent:install-channel", kind: "agent", displayName: "Franz",
|
|
334
|
-
attributes: { language: "de-DE", voice: { warmth: "warm", directness: "clear", length: "concise" } },
|
|
335
|
-
privacy: "shared"
|
|
336
|
-
});
|
|
337
|
-
await graph.upsertEntity({ root: projectRoot, id: "project:install-channel", kind: "project", privacy: "shared" });
|
|
338
|
-
await graph.upsertEntity({ root: projectRoot, id: "group:install-channel", kind: "group", privacy: "shared" });
|
|
339
|
-
await graph.linkEntities({
|
|
340
|
-
root: projectRoot, from: "agent:install-channel", to: "group:install-channel",
|
|
341
|
-
relation: "member-of", privacy: "group"
|
|
342
|
-
});
|
|
343
|
-
await channel.grantChannelBinding({
|
|
344
|
-
root: projectRoot, id: "channel-binding:install", provider: "telegram",
|
|
345
|
-
tenantId: "tenant:install", accountId: "bot:install", chatId: "chat:install", threadId: "topic:install",
|
|
346
|
-
senderIds: ["user:install"], agentId: "agent:install-channel", projectId: "project:install-channel",
|
|
347
|
-
groupId: "group:install-channel", sessionKey: "session:install-channel",
|
|
348
|
-
secretEnv: "AGENTSPINE_INSTALL_CHANNEL_SECRET", outboundSecretEnv: "AGENTSPINE_INSTALL_CHANNEL_SECRET", capabilities: ["receive", "reply"],
|
|
349
|
-
confirmation: "local-owner-confirmed", now: "2032-01-01T00:00:00.000Z"
|
|
350
|
-
});
|
|
351
|
-
const event = {
|
|
352
|
-
schema: channel.CHANNEL_EVENT_SCHEMA, eventId: "telegram:update:install", provider: "telegram",
|
|
353
|
-
tenantId: "tenant:install", accountId: "bot:install", chatId: "chat:install", threadId: "topic:install",
|
|
354
|
-
senderId: "user:install", replyTo: "message:install", observedAt: "2032-01-01T00:00:01.000Z",
|
|
355
|
-
privacy: "group", text: "Bitte antworte im gebundenen Telegram-Thema."
|
|
356
|
-
};
|
|
357
|
-
const secret = "synthetic-installed-channel-secret-32-bytes";
|
|
358
|
-
const signature = `sha256=${createHmac("sha256", secret).update(channel.channelEventSigningPayload(event)).digest("hex")}`;
|
|
359
|
-
await channel.ingestChannelEvent({
|
|
360
|
-
root: projectRoot, event, signature, env: { AGENTSPINE_INSTALL_CHANNEL_SECRET: secret },
|
|
361
|
-
now: "2032-01-01T00:00:02.000Z"
|
|
362
|
-
});
|
|
363
|
-
return { event, secret };
|
|
364
|
-
} finally {
|
|
365
|
-
if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
|
|
366
|
-
else process.env.AGENTSPINE_STATE_DIR = previous;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async function invokeInstalledChannelWake(pluginRoot, projectRoot, stateRoot, host) {
|
|
371
|
-
const { event, secret } = await prepareInstalledChannelWake(pluginRoot, projectRoot, stateRoot);
|
|
372
|
-
const result = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, {
|
|
373
|
-
hook_event_name: "SessionStart", cwd: projectRoot, host,
|
|
374
|
-
entity_id: "agent:install-channel", project_id: "project:install-channel",
|
|
375
|
-
group_id: "group:install-channel", session_id: `session:installed-${host}-channel`,
|
|
376
|
-
agent_spine_channel_event: { event_id: event.eventId, provider: event.provider }
|
|
377
|
-
}, { extraEnv: { AGENTSPINE_INSTALL_CHANNEL_SECRET: secret } });
|
|
378
|
-
if (result.channelEvent?.eventId !== event.eventId || result.channelEvent?.chatId !== event.chatId
|
|
379
|
-
|| result.channelEvent?.threadId !== event.threadId || result.voiceBrief?.displayName !== "Franz"
|
|
380
|
-
|| result.voiceBrief?.profile?.warmth !== "warm") {
|
|
381
|
-
throw new Error(`${host} installed hook did not inject the exact channel event and voice brief`);
|
|
382
|
-
}
|
|
383
|
-
return {
|
|
384
|
-
eventId: result.channelEvent.eventId, provider: result.channelEvent.provider,
|
|
385
|
-
route: [result.channelEvent.chatId, result.channelEvent.threadId],
|
|
386
|
-
voice: { displayName: result.voiceBrief.displayName, profile: result.voiceBrief.profile },
|
|
387
|
-
mcpCalls: 0
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async function invokeInstalledGateway(pluginRoot, projectRoot, stateRoot) {
|
|
392
|
-
const previous = process.env.AGENTSPINE_STATE_DIR;
|
|
393
|
-
process.env.AGENTSPINE_STATE_DIR = stateRoot;
|
|
394
|
-
try {
|
|
395
|
-
const graph = await import(pathToFileURL(join(pluginRoot, "src/lib/graph.js")).href);
|
|
396
|
-
const persona = await import(pathToFileURL(join(pluginRoot, "src/lib/persona-runtime.js")).href);
|
|
397
|
-
const channel = await import(pathToFileURL(join(pluginRoot, "src/lib/channel-runtime.js")).href);
|
|
398
|
-
const gateway = await import(pathToFileURL(join(pluginRoot, "src/lib/gateway-runtime.js")).href);
|
|
399
|
-
const worker = await import(pathToFileURL(join(pluginRoot, "src/worker.js")).href);
|
|
400
|
-
await graph.upsertEntity({ root: projectRoot, id: "project:installed-gateway", kind: "project", privacy: "shared" });
|
|
401
|
-
const roster = await persona.applyPersonaRoster({
|
|
402
|
-
root: projectRoot,
|
|
403
|
-
bindings: [{
|
|
404
|
-
id: "persona-binding:installed-gateway", authenticator: "host-manifest", issuer: "host:installed",
|
|
405
|
-
tenantId: "tenant:installed-gateway", host: "codex", profileId: "profile:installed-gateway",
|
|
406
|
-
subjectId: "subject:installed-gateway", kind: "agent", displayName: "Installed Franz",
|
|
407
|
-
sourceBinding: ".codex/agents/installed-franz.md", groupId: "group:installed-gateway"
|
|
408
|
-
}, {
|
|
409
|
-
id: "persona-binding:installed-peer", authenticator: "host-manifest", issuer: "host:installed",
|
|
410
|
-
tenantId: "tenant:installed-gateway", host: "codex", profileId: "profile:installed-gateway",
|
|
411
|
-
subjectId: "subject:installed-peer", kind: "bot", displayName: "Installed Peer",
|
|
412
|
-
sourceBinding: ".codex/agents/installed-peer.md", groupId: "group:installed-gateway"
|
|
413
|
-
}],
|
|
414
|
-
confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:00.000Z"
|
|
415
|
-
});
|
|
416
|
-
const agentId = roster.runtime.personas.find((item) => item.bindingId === "persona-binding:installed-gateway").personaId;
|
|
417
|
-
const peerId = roster.runtime.personas.find((item) => item.bindingId === "persona-binding:installed-peer").personaId;
|
|
418
|
-
const relationship = await graph.relationshipContext({ root: projectRoot, entityId: agentId, groupId: "group:installed-gateway" });
|
|
419
|
-
if (!roster.graphReconciled || !relationship.relatedEntities.some((item) => item.id === peerId)) {
|
|
420
|
-
throw new Error("installed persona roster did not materialize its group-scoped team neighborhood");
|
|
421
|
-
}
|
|
422
|
-
await gateway.setGatewayControl({
|
|
423
|
-
root: projectRoot, enabled: true, killSwitch: false,
|
|
424
|
-
confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:00.500Z"
|
|
425
|
-
});
|
|
426
|
-
await channel.grantChannelBinding({
|
|
427
|
-
root: projectRoot, id: "channel-binding:installed-gateway", provider: "telegram",
|
|
428
|
-
tenantId: "tenant:installed-gateway", accountId: "123456789", chatId: "-1001234567890", threadId: "77",
|
|
429
|
-
senderIds: ["888"], agentId, projectId: "project:installed-gateway", groupId: "group:installed-gateway",
|
|
430
|
-
sessionKey: "session:installed-gateway", secretEnv: "AGENTSPINE_INSTALLED_GATEWAY_SECRET",
|
|
431
|
-
outboundSecretEnv: "AGENTSPINE_INSTALLED_GATEWAY_TOKEN", capabilities: ["receive", "reply"],
|
|
432
|
-
confirmation: "local-owner-confirmed", now: "2032-01-02T00:00:01.000Z"
|
|
433
|
-
});
|
|
434
|
-
const event = {
|
|
435
|
-
schema: channel.CHANNEL_EVENT_SCHEMA, eventId: "telegram:update:installed-gateway", provider: "telegram",
|
|
436
|
-
tenantId: "tenant:installed-gateway", accountId: "123456789", chatId: "-1001234567890", threadId: "77",
|
|
437
|
-
senderId: "888", replyTo: "990", observedAt: "2032-01-02T00:00:02.000Z", privacy: "group",
|
|
438
|
-
text: "Bitte führe den installierten Gateway-Test aus."
|
|
439
|
-
};
|
|
440
|
-
const secret = "synthetic-installed-gateway-secret-32-bytes";
|
|
441
|
-
await channel.ingestChannelEvent({
|
|
442
|
-
root: projectRoot, event,
|
|
443
|
-
signature: `sha256=${createHmac("sha256", secret).update(channel.channelEventSigningPayload(event)).digest("hex")}`,
|
|
444
|
-
env: { AGENTSPINE_INSTALLED_GATEWAY_SECRET: secret }, now: "2032-01-02T00:00:03.000Z"
|
|
445
|
-
});
|
|
446
|
-
let hostStart = null; let delivered = null;
|
|
447
|
-
const result = await worker.runWorkerTick({
|
|
448
|
-
root: projectRoot, workerId: "gateway-worker:installed", now: "2032-01-02T00:00:04.000Z",
|
|
449
|
-
env: { AGENTSPINE_STATE_DIR: stateRoot },
|
|
450
|
-
hostRunner: async (item) => { hostStart = item.channelStart; return { text: "Installierter Gateway-Test bestanden." }; },
|
|
451
|
-
adapter: { send: async (outbox) => { delivered = outbox; return { ok: true, receipt: "telegram-message:991" }; } }
|
|
452
|
-
});
|
|
453
|
-
const channelState = await channel.loadChannelRuntime(projectRoot);
|
|
454
|
-
if (result.status !== "delivered" || channelState.runtime.events.at(-1)?.status !== "completed"
|
|
455
|
-
|| hostStart?.agent_spine_channel_event?.event_id !== event.eventId
|
|
456
|
-
|| delivered?.chatId !== event.chatId || delivered?.threadId !== event.threadId || delivered?.replyTo !== event.replyTo) {
|
|
457
|
-
throw new Error("installed gateway did not complete the exact channel wake and delivery path: " + JSON.stringify({
|
|
458
|
-
result, channelStatus: channelState.runtime.events.at(-1)?.status || null,
|
|
459
|
-
hostEvent: hostStart?.agent_spine_channel_event?.event_id || null,
|
|
460
|
-
route: delivered ? [delivered.chatId, delivered.threadId, delivered.replyTo] : null
|
|
461
|
-
}));
|
|
462
|
-
}
|
|
463
|
-
return { status: result.status, eventId: event.eventId, agentId,
|
|
464
|
-
route: [delivered.chatId, delivered.threadId, delivered.replyTo], teamPeers: 1,
|
|
465
|
-
graphChanges: roster.graphChanges, mcpCalls: 0 };
|
|
466
|
-
} finally {
|
|
467
|
-
if (previous === undefined) delete process.env.AGENTSPINE_STATE_DIR;
|
|
468
|
-
else process.env.AGENTSPINE_STATE_DIR = previous;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
export async function checkInstall(root = process.cwd()) {
|
|
473
|
-
root = resolve(root);
|
|
474
|
-
const currentVersion = JSON.parse(await readFile(join(root, "package.json"), "utf8")).version;
|
|
475
|
-
const workspace = await mkdtemp(join(tmpdir(), "agentspine-install-check-"));
|
|
476
|
-
try {
|
|
477
|
-
const userProject = join(workspace, "user-project");
|
|
478
|
-
const source = join(userProject, "SOUL.md");
|
|
479
|
-
await mkdir(userProject, { recursive: true });
|
|
480
|
-
await writeFile(source, "# Existing soul\n\nNever modify me.\n", "utf8");
|
|
481
|
-
await writeFile(join(userProject, "CLAUDE.md"), "# Installed Claude rules\n\nLoad this before every answer.\n", "utf8");
|
|
482
|
-
await writeFile(join(userProject, "AGENTS.md"), "# Installed Codex rules\n\nLoad this before every answer.\n", "utf8");
|
|
483
|
-
const protectedInstallSources = [source, join(userProject, "CLAUDE.md"), join(userProject, "AGENTS.md")];
|
|
484
|
-
const sourceHashes = new Map(await Promise.all(protectedInstallSources.map(async (path) => [path, hash(await readFile(path))])));
|
|
485
|
-
|
|
486
|
-
const fresh = join(workspace, "fresh", "agent-spine");
|
|
487
|
-
await copyBundle(root, fresh);
|
|
488
|
-
const freshResult = await checkHosts(fresh);
|
|
489
|
-
const aliasRoot = join(workspace, "fresh-alias");
|
|
490
|
-
await symlink(join(workspace, "fresh"), aliasRoot, process.platform === "win32" ? "junction" : "dir");
|
|
491
|
-
const aliasResult = await checkHosts(join(aliasRoot, "agent-spine"));
|
|
492
|
-
const freshState = join(workspace, "state-fresh");
|
|
493
|
-
const freshHook = await invokeInstalledHook(fresh, userProject, freshState, "claude");
|
|
494
|
-
const freshAttention = await invokeInstalledAttention(fresh, userProject, freshState, "claude");
|
|
495
|
-
const freshSelfstarter = await invokeInstalledSelfstarter(fresh, userProject, freshState, "claude");
|
|
496
|
-
const freshChannelWake = await invokeInstalledChannelWake(fresh, userProject, freshState, "claude");
|
|
497
|
-
const freshGateway = await invokeInstalledGateway(fresh, userProject, freshState);
|
|
498
|
-
const freshAcceptance = await invokeInstalledAcceptance(fresh);
|
|
499
|
-
const freshLiveRoots = await invokeInstalledLiveRoots(fresh, workspace, join(workspace, "state-live-fresh"));
|
|
500
|
-
|
|
501
|
-
const installed = join(workspace, "cache", "agent-spine");
|
|
502
|
-
await copyBundle(root, installed);
|
|
503
|
-
await makePreviousCache(installed);
|
|
504
|
-
let legacyFailed = false;
|
|
505
|
-
try {
|
|
506
|
-
const legacy = await checkHosts(installed);
|
|
507
|
-
if (legacy.version !== currentVersion) legacyFailed = true;
|
|
508
|
-
} catch { legacyFailed = true; }
|
|
509
|
-
if (!legacyFailed) throw new Error("legacy cache unexpectedly passed the current host inventory");
|
|
510
|
-
|
|
511
|
-
const staging = `${installed}.${currentVersion}.staging`;
|
|
512
|
-
await copyBundle(root, staging);
|
|
513
|
-
await removeTree(installed);
|
|
514
|
-
await rename(staging, installed);
|
|
515
|
-
const upgraded = await checkHosts(installed);
|
|
516
|
-
const upgradeState = join(workspace, "state-upgrade");
|
|
517
|
-
const upgradedHook = await invokeInstalledHook(installed, userProject, upgradeState, "codex");
|
|
518
|
-
const upgradedAttention = await invokeInstalledAttention(installed, userProject, upgradeState, "codex");
|
|
519
|
-
const upgradedSelfstarter = await invokeInstalledSelfstarter(installed, userProject, upgradeState, "codex");
|
|
520
|
-
const upgradedChannelWake = await invokeInstalledChannelWake(installed, userProject, upgradeState, "codex");
|
|
521
|
-
const upgradedGateway = await invokeInstalledGateway(installed, userProject, upgradeState);
|
|
522
|
-
const upgradedAcceptance = await invokeInstalledAcceptance(installed);
|
|
523
|
-
const upgradedLiveRoots = await invokeInstalledLiveRoots(installed, join(workspace, "upgrade-live"), join(workspace, "state-live-upgrade"));
|
|
524
|
-
|
|
525
|
-
await removeTree(fresh);
|
|
526
|
-
await removeTree(installed);
|
|
527
|
-
for (const [path, expected] of sourceHashes) {
|
|
528
|
-
if (hash(await readFile(path)) !== expected) throw new Error("install or uninstall changed an existing source Markdown file");
|
|
529
|
-
}
|
|
530
|
-
return {
|
|
531
|
-
ok: true,
|
|
532
|
-
version: upgraded.version,
|
|
533
|
-
fresh: freshResult.exactlyOnce,
|
|
534
|
-
upgrade: upgraded.exactlyOnce,
|
|
535
|
-
automaticBriefing: { fresh: freshHook, upgrade: upgradedHook },
|
|
536
|
-
automaticAttention: { fresh: freshAttention, upgrade: upgradedAttention },
|
|
537
|
-
automaticSelfstarter: { fresh: freshSelfstarter, upgrade: upgradedSelfstarter },
|
|
538
|
-
automaticChannelWake: { fresh: freshChannelWake, upgrade: upgradedChannelWake },
|
|
539
|
-
automaticGateway: { fresh: freshGateway, upgrade: upgradedGateway },
|
|
540
|
-
visibleAcceptance: { fresh: freshAcceptance, upgrade: upgradedAcceptance },
|
|
541
|
-
liveRootResolution: { fresh: freshLiveRoots, upgrade: upgradedLiveRoots },
|
|
542
|
-
canonicalAliasLaunch: aliasResult.ok,
|
|
543
|
-
previousCacheRejected: legacyFailed,
|
|
544
|
-
uninstallPreservedSources: true,
|
|
545
|
-
authority: "installation-check-only"
|
|
546
|
-
};
|
|
547
|
-
} finally {
|
|
548
|
-
await removeTree(workspace, { force: true });
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
async function main() {
|
|
553
|
-
const args = process.argv.slice(2);
|
|
554
|
-
let root = process.cwd();
|
|
555
|
-
let pretty = false;
|
|
556
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
557
|
-
if (args[index] === "--root") root = args[++index];
|
|
558
|
-
else if (args[index] === "--json") pretty = true;
|
|
559
|
-
else throw new Error(`unknown install-check argument: ${args[index]}`);
|
|
560
|
-
}
|
|
561
|
-
process.stdout.write(`${JSON.stringify(await checkInstall(root), null, pretty ? 2 : 0)}\n`);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
565
|
-
main().catch((error) => {
|
|
566
|
-
process.stderr.write(`AgentSpine install check failed: ${error.message}\n`);
|
|
567
|
-
process.exitCode = 1;
|
|
568
|
-
});
|
|
569
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { opendir } from "node:fs/promises";
|
|
2
|
-
import { extname, join } from "node:path";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
|
|
5
|
-
async function javascriptFiles(directory, files = []) {
|
|
6
|
-
const entries = [];
|
|
7
|
-
for await (const entry of await opendir(directory)) entries.push(entry);
|
|
8
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
9
|
-
for (const entry of entries) {
|
|
10
|
-
const path = join(directory, entry.name);
|
|
11
|
-
if (entry.isDirectory()) await javascriptFiles(path, files);
|
|
12
|
-
else if (entry.isFile() && extname(entry.name) === ".js") files.push(path);
|
|
13
|
-
}
|
|
14
|
-
return files;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const files = [
|
|
18
|
-
...await javascriptFiles("src"),
|
|
19
|
-
...await javascriptFiles("bin"),
|
|
20
|
-
...await javascriptFiles("scripts")
|
|
21
|
-
];
|
|
22
|
-
for (const file of files) {
|
|
23
|
-
const result = spawnSync(process.execPath, ["--check", file], { encoding: "utf8" });
|
|
24
|
-
if (result.status !== 0) {
|
|
25
|
-
process.stderr.write(result.stderr || result.stdout);
|
|
26
|
-
process.exit(result.status || 1);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
process.stdout.write(`Checked ${files.length} JavaScript files.\n`);
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
function escapeWorkflowData(value) {
|
|
2
|
-
return String(value).replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A");
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function escapeWorkflowProperty(value) {
|
|
6
|
-
return escapeWorkflowData(value).replaceAll(":", "%3A").replaceAll(",", "%2C");
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function githubErrorCommand(title, message) {
|
|
10
|
-
return `::error title=${escapeWorkflowProperty(title)}::${escapeWorkflowData(message)}`;
|
|
11
|
-
}
|