blun-king-cli 9.1.567 → 9.1.570
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/CHANGELOG.md +33 -0
- package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +1581 -0
- package/agent-spine-plugin/README.md +30 -4
- package/agent-spine-plugin/blun.plugin.json +3 -3
- package/agent-spine-plugin/docs/acceptance.md +61 -0
- package/agent-spine-plugin/docs/assignment-continuation.md +48 -0
- package/agent-spine-plugin/docs/host-integration.md +178 -0
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/preservation-contract.md +53 -0
- package/agent-spine-plugin/docs/quality-gates.md +50 -0
- package/agent-spine-plugin/docs/releasing.md +85 -0
- package/agent-spine-plugin/docs/session-timeline.md +251 -0
- package/agent-spine-plugin/docs/source-roots.md +113 -0
- package/agent-spine-plugin/docs/structured-completion.md +67 -0
- package/agent-spine-plugin/docs/world-model.md +94 -0
- package/agent-spine-plugin/hooks/codex.json +2 -2
- package/agent-spine-plugin/hooks/hooks.json +1 -1
- package/agent-spine-plugin/hooks/version.json +1 -1
- package/agent-spine-plugin/package.json +13 -3
- package/agent-spine-plugin/scripts/check-codex-install.js +226 -0
- package/agent-spine-plugin/scripts/check-hosts.js +14 -5
- package/agent-spine-plugin/scripts/check-install-hook.js +226 -0
- package/agent-spine-plugin/scripts/check-install-selfstarter.js +154 -0
- package/agent-spine-plugin/scripts/check-install.js +478 -0
- package/agent-spine-plugin/scripts/check-line-budget.js +58 -0
- package/agent-spine-plugin/scripts/check-syntax.js +29 -0
- package/agent-spine-plugin/scripts/github-actions.js +11 -0
- package/agent-spine-plugin/scripts/hermetic-process.js +183 -0
- package/agent-spine-plugin/scripts/release-check.js +145 -0
- package/agent-spine-plugin/scripts/run-acceptance.js +19 -0
- package/agent-spine-plugin/scripts/run-checks.js +47 -0
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +89 -0
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +16 -2
- package/agent-spine-plugin/spine-example/1-identity.md +12 -0
- package/agent-spine-plugin/spine-example/2-voice.md +6 -0
- package/agent-spine-plugin/spine-example/3-conduct.md +8 -0
- package/agent-spine-plugin/spine-example/4-history.md +4 -0
- package/agent-spine-plugin/src/cli-agent.js +296 -0
- package/agent-spine-plugin/src/cli-attention.js +95 -0
- package/agent-spine-plugin/src/cli-autonomy.js +36 -0
- package/agent-spine-plugin/src/cli-common.js +71 -0
- package/agent-spine-plugin/src/cli-continuity.js +116 -0
- package/agent-spine-plugin/src/cli-core.js +128 -0
- package/agent-spine-plugin/src/cli-diagnostics.js +291 -0
- package/agent-spine-plugin/src/cli-host.js +21 -0
- package/agent-spine-plugin/src/cli-learning.js +305 -0
- package/agent-spine-plugin/src/cli-premortem.js +16 -0
- package/agent-spine-plugin/src/cli-sharing.js +230 -0
- package/agent-spine-plugin/src/cli.js +40 -1350
- package/agent-spine-plugin/src/codex-reader-launcher.js +182 -0
- package/agent-spine-plugin/src/hook.js +185 -567
- package/agent-spine-plugin/src/index.js +16 -2
- package/agent-spine-plugin/src/lib/acceptance.js +113 -11
- package/agent-spine-plugin/src/lib/action-lesson-recall.js +53 -0
- package/agent-spine-plugin/src/lib/attention-context.js +167 -0
- package/agent-spine-plugin/src/lib/attention-events.js +113 -0
- package/agent-spine-plugin/src/lib/attention-privacy.js +72 -0
- package/agent-spine-plugin/src/lib/attention-schema.js +164 -0
- package/agent-spine-plugin/src/lib/attention-storage.js +93 -0
- package/agent-spine-plugin/src/lib/attention.js +9 -600
- package/agent-spine-plugin/src/lib/audit-premortem.js +223 -0
- package/agent-spine-plugin/src/lib/audit.js +56 -6
- package/agent-spine-plugin/src/lib/autonomy-policy.js +110 -0
- package/agent-spine-plugin/src/lib/autonomy-store.js +202 -0
- package/agent-spine-plugin/src/lib/autonomy.js +8 -0
- package/agent-spine-plugin/src/lib/briefing.js +67 -4
- package/agent-spine-plugin/src/lib/catalog-document-read.js +51 -0
- package/agent-spine-plugin/src/lib/catalog.js +1 -1
- package/agent-spine-plugin/src/lib/codex-installation.js +231 -0
- package/agent-spine-plugin/src/lib/codex-skill-installation.js +211 -0
- package/agent-spine-plugin/src/lib/context.js +3 -1
- package/agent-spine-plugin/src/lib/delivery-agent-usage.js +224 -0
- package/agent-spine-plugin/src/lib/delivery-assignment.js +220 -0
- package/agent-spine-plugin/src/lib/delivery-command-actions.js +453 -0
- package/agent-spine-plugin/src/lib/delivery-knowledge.js +78 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-binding.js +107 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-closure.js +171 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-codec.js +45 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-correction.js +101 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-file.js +21 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-index.js +493 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-inspection.js +37 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-recovery.js +120 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-rejection.js +65 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-results.js +28 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-session-guard.js +46 -0
- package/agent-spine-plugin/src/lib/delivery-premortem-write-ledger.js +285 -0
- package/agent-spine-plugin/src/lib/delivery-premortem.js +499 -0
- package/agent-spine-plugin/src/lib/delivery-shell-heredoc.js +115 -0
- package/agent-spine-plugin/src/lib/delivery-shell-substitutions.js +111 -0
- package/agent-spine-plugin/src/lib/delivery-shell-wrapper.js +64 -0
- package/agent-spine-plugin/src/lib/delivery-target.js +73 -0
- package/agent-spine-plugin/src/lib/delivery-verification.js +445 -0
- package/agent-spine-plugin/src/lib/documents.js +27 -5
- package/agent-spine-plugin/src/lib/filesystem-retry.js +2 -0
- package/agent-spine-plugin/src/lib/gateway-common.js +68 -0
- package/agent-spine-plugin/src/lib/gateway-control.js +302 -0
- package/agent-spine-plugin/src/lib/gateway-delivery.js +103 -0
- package/agent-spine-plugin/src/lib/gateway-execution.js +350 -0
- package/agent-spine-plugin/src/lib/gateway-host-lifecycle.js +185 -0
- package/agent-spine-plugin/src/lib/gateway-inspection.js +85 -0
- package/agent-spine-plugin/src/lib/gateway-knowledge.js +129 -0
- package/agent-spine-plugin/src/lib/gateway-policy-provenance.js +197 -0
- package/agent-spine-plugin/src/lib/gateway-premortem-disposition.js +82 -0
- package/agent-spine-plugin/src/lib/gateway-premortem.js +363 -0
- package/agent-spine-plugin/src/lib/gateway-runs.js +300 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-identity.js +31 -0
- package/agent-spine-plugin/src/lib/gateway-runtime-records.js +21 -0
- package/agent-spine-plugin/src/lib/gateway-runtime.js +18 -1623
- package/agent-spine-plugin/src/lib/gateway-state-transaction.js +356 -0
- package/agent-spine-plugin/src/lib/gateway-state.js +342 -0
- package/agent-spine-plugin/src/lib/hook-artifact-guards.js +356 -0
- package/agent-spine-plugin/src/lib/hook-audit.js +16 -2
- package/agent-spine-plugin/src/lib/hook-briefing-use.js +68 -0
- package/agent-spine-plugin/src/lib/hook-context.js +424 -0
- package/agent-spine-plugin/src/lib/hook-final-message.js +26 -0
- package/agent-spine-plugin/src/lib/hook-input.js +27 -0
- package/agent-spine-plugin/src/lib/hook-output.js +140 -0
- package/agent-spine-plugin/src/lib/hook-premortem.js +257 -0
- package/agent-spine-plugin/src/lib/hook-process-advisory.js +37 -0
- package/agent-spine-plugin/src/lib/hook-protection.js +95 -0
- package/agent-spine-plugin/src/lib/hook-stop-verification.js +84 -0
- package/agent-spine-plugin/src/lib/hook-timeline.js +91 -0
- package/agent-spine-plugin/src/lib/identifier-analysis.js +446 -0
- package/agent-spine-plugin/src/lib/indexed-memory.js +23 -6
- package/agent-spine-plugin/src/lib/knowledge-evidence.js +431 -0
- package/agent-spine-plugin/src/lib/learning-applications.js +441 -0
- package/agent-spine-plugin/src/lib/learning-candidates.js +274 -0
- package/agent-spine-plugin/src/lib/learning-context.js +130 -0
- package/agent-spine-plugin/src/lib/learning-delivery-contracts.js +310 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-contracts.js +350 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-registration.js +372 -0
- package/agent-spine-plugin/src/lib/learning-evaluation-revocation.js +292 -0
- package/agent-spine-plugin/src/lib/learning-evidence-contracts.js +321 -0
- package/agent-spine-plugin/src/lib/learning-findings.js +458 -0
- package/agent-spine-plugin/src/lib/learning-measurement-contracts.js +285 -0
- package/agent-spine-plugin/src/lib/learning-measurements.js +265 -0
- package/agent-spine-plugin/src/lib/learning-outcome-contracts.js +165 -0
- package/agent-spine-plugin/src/lib/learning-outcomes.js +343 -0
- package/agent-spine-plugin/src/lib/learning-reconciliation.js +290 -0
- package/agent-spine-plugin/src/lib/learning-retry-contracts.js +188 -0
- package/agent-spine-plugin/src/lib/learning-schema.js +229 -0
- package/agent-spine-plugin/src/lib/learning-scope-targets.js +424 -0
- package/agent-spine-plugin/src/lib/learning-state-upgrade.js +477 -0
- package/agent-spine-plugin/src/lib/learning-status-configuration.js +476 -0
- package/agent-spine-plugin/src/lib/learning-storage.js +203 -0
- package/agent-spine-plugin/src/lib/learning-trial-recovery.js +218 -0
- package/agent-spine-plugin/src/lib/learning-validation-contracts.js +375 -0
- package/agent-spine-plugin/src/lib/learning-validation-renewal.js +298 -0
- package/agent-spine-plugin/src/lib/learning-validation-runtime.js +234 -0
- package/agent-spine-plugin/src/lib/learning.js +36 -6923
- package/agent-spine-plugin/src/lib/lesson-recall-session.js +172 -0
- package/agent-spine-plugin/src/lib/mcp-autonomy-tools.js +37 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-completion.js +124 -0
- package/agent-spine-plugin/src/lib/mcp-delivery-tools.js +45 -0
- package/agent-spine-plugin/src/lib/mcp-premortem.js +68 -0
- package/agent-spine-plugin/src/lib/mcp-runtime.js +269 -0
- package/agent-spine-plugin/src/lib/mcp-source-context.js +51 -0
- package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +164 -0
- package/agent-spine-plugin/src/lib/mcp-world-tools.js +52 -0
- package/agent-spine-plugin/src/lib/owned-file-lock.js +30 -10
- package/agent-spine-plugin/src/lib/project-portfolio.js +176 -0
- package/agent-spine-plugin/src/lib/selfstarter-core.js +382 -0
- package/agent-spine-plugin/src/lib/selfstarter-jobs.js +149 -0
- package/agent-spine-plugin/src/lib/selfstarter-lease.js +204 -0
- package/agent-spine-plugin/src/lib/selfstarter-policy.js +90 -0
- package/agent-spine-plugin/src/lib/selfstarter-workspace.js +111 -0
- package/agent-spine-plugin/src/lib/selfstarter.js +11 -911
- package/agent-spine-plugin/src/lib/session-timeline-auth.js +316 -0
- package/agent-spine-plugin/src/lib/session-timeline-codex.js +58 -0
- package/agent-spine-plugin/src/lib/session-timeline-contract.js +48 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +41 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +132 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +17 -0
- package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +500 -0
- package/agent-spine-plugin/src/lib/session-timeline-event-extract.js +157 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +74 -0
- package/agent-spine-plugin/src/lib/session-timeline-host-receipt.js +117 -0
- package/agent-spine-plugin/src/lib/session-timeline-invocation.js +201 -0
- package/agent-spine-plugin/src/lib/session-timeline-king.js +79 -0
- package/agent-spine-plugin/src/lib/session-timeline-prior.js +59 -0
- package/agent-spine-plugin/src/lib/session-timeline-provider.js +34 -0
- package/agent-spine-plugin/src/lib/session-timeline-query.js +55 -0
- package/agent-spine-plugin/src/lib/session-timeline-results.js +31 -0
- package/agent-spine-plugin/src/lib/session-timeline-root.js +11 -0
- package/agent-spine-plugin/src/lib/session-timeline-search.js +82 -0
- package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +217 -0
- package/agent-spine-plugin/src/lib/session-timeline-source.js +83 -0
- package/agent-spine-plugin/src/lib/session-timeline-state.js +45 -0
- package/agent-spine-plugin/src/lib/session-timeline-transport.js +50 -0
- package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +148 -0
- package/agent-spine-plugin/src/lib/session-timeline.js +453 -0
- package/agent-spine-plugin/src/lib/source-roots.js +69 -136
- package/agent-spine-plugin/src/lib/source-tree-scan.js +178 -0
- package/agent-spine-plugin/src/lib/task-knowledge-context.js +78 -0
- package/agent-spine-plugin/src/lib/timeline-tool-guard.js +202 -0
- package/agent-spine-plugin/src/lib/world-knowledge.js +249 -0
- package/agent-spine-plugin/src/lib/world-model.js +278 -0
- package/agent-spine-plugin/src/mcp.js +20 -161
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +22 -5
- package/bin/agent-resume-snapshot.cjs +2 -2
- package/bin/agentspine-king-goal-inbox.mjs +111 -0
- package/bin/agentspine-king-goal-intake.mjs +106 -0
- package/bin/baseline-skill-performance-policy.cjs +1 -16
- package/bin/core-bootstrap.js +3 -0
- package/bin/curiosity-scout-policy.cjs +5 -1
- package/bin/input-draft-persistence.cjs +2 -2
- package/bin/king-tui-function-contract.json +33 -0
- package/bin/launcher-restart-policy.cjs +150 -0
- package/bin/launcher-runtime.js +56 -44
- package/bin/managed-context-startup-policy.cjs +27 -0
- package/bin/managed-plugin-selection.cjs +116 -0
- package/bin/mistake-relevance-policy.cjs +1 -1
- package/bin/observer-hooks.cjs +14 -0
- package/bin/oversized-context-offload-policy.cjs +86 -0
- package/bin/plugin-bootstrap.js +7 -40
- package/bin/proactive-compaction-policy.cjs +1 -1
- package/bin/provider-model-refresh-deadline.cjs +53 -0
- package/bin/provider-model-refresh-policy.cjs +107 -0
- package/bin/release-artifact-freeze-policy.cjs +30 -0
- package/bin/repeated-user-message-projection.cjs +3 -126
- package/bin/research-page-result.cjs +74 -0
- package/bin/runtime-exit-ledger.cjs +1 -0
- package/bin/session-compaction-policy.cjs +84 -0
- package/bin/skill-listing-performance-policy.cjs +2 -2
- package/bin/standard-tools-bootstrap.js +0 -37
- package/bin/subagent-skill-policy.cjs +3 -1
- package/bin/telegram-approval-relay.cjs +12 -7
- package/bin/telegram-private-conversation-policy.cjs +3 -2
- package/bin/telegram-queue-handoff-policy.cjs +24 -0
- package/bin/thinking-activity-status-policy.cjs +1 -1
- package/bin/thinking-only-guard.cjs +16 -12
- package/bin/tool-call-loop-policy.cjs +0 -2
- package/bin/tool-result-offload-policy.cjs +11 -2
- package/bin/tui-functional-contract.cjs +55 -0
- package/bin/update-notice.js +18 -14
- package/bin/user-message-offload-policy.cjs +1 -1
- package/bin/user-prompt-hook-origin-policy.cjs +34 -0
- package/bin/windows-node-crash-dump.cjs +110 -0
- package/blun.mjs +2861 -1173
- package/codebase-index/codebase_index.py +4 -3
- package/package.json +5 -18
- package/standard-skills/research-evidence/SKILL.md +39 -0
- package/standard-skills/research-evidence/references/evidence-format.md +82 -0
- package/standard-skills/research-evidence/scripts/evidence-collection.cjs +254 -0
- package/standard-skills/research-evidence/scripts/score-report.cjs +112 -0
- package/standard-skills/web-lesen/SKILL.md +37 -22
- package/standard-skills/web-lesen/scripts/crawl_public.py +376 -0
- package/telegram-plugin/DELIVERY.md +36 -0
- package/telegram-plugin/bin/telegram-approval-relay.cjs +13 -7
- package/telegram-plugin/bin/telegram-launcher-status-queue.cjs +122 -0
- package/telegram-plugin/bin/telegram-private-conversation-policy.cjs +3 -2
- package/telegram-plugin/bin/telegram-reply-parts.cjs +149 -0
- package/telegram-plugin/dist/bridge.mjs +7 -56
- package/telegram-plugin/dist/mcp-server.mjs +33 -4
- package/agent-spine-plugin/skill/SKILL.md +0 -76
- package/bin/mnemo-connect-heartbeat.cjs +0 -204
- package/bin/mnemo-tool-agent-policy.cjs +0 -22
- package/telegram-plugin/bin/telegram-mnemo-capture.cjs +0 -297
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-spine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.0",
|
|
4
4
|
"description": "A non-destructive identity and memory spine for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,10 +18,19 @@
|
|
|
18
18
|
".mcp.json",
|
|
19
19
|
"assets",
|
|
20
20
|
"bin",
|
|
21
|
-
"docs",
|
|
21
|
+
"docs/acceptance.md",
|
|
22
|
+
"docs/assignment-continuation.md",
|
|
23
|
+
"docs/host-integration.md",
|
|
24
|
+
"docs/preflight-recall.md",
|
|
25
|
+
"docs/preservation-contract.md",
|
|
26
|
+
"docs/quality-gates.md",
|
|
27
|
+
"docs/releasing.md",
|
|
28
|
+
"docs/session-timeline.md",
|
|
29
|
+
"docs/source-roots.md",
|
|
30
|
+
"docs/structured-completion.md",
|
|
31
|
+
"docs/world-model.md",
|
|
22
32
|
"hooks",
|
|
23
33
|
"scripts",
|
|
24
|
-
"skill",
|
|
25
34
|
"skills",
|
|
26
35
|
"spine-example",
|
|
27
36
|
"src",
|
|
@@ -36,6 +45,7 @@
|
|
|
36
45
|
"host:check": "node scripts/check-hosts.js --json",
|
|
37
46
|
"host:install-check": "node scripts/check-install.js --json",
|
|
38
47
|
"lint": "node scripts/check-syntax.js",
|
|
48
|
+
"line-budget": "node scripts/check-line-budget.js",
|
|
39
49
|
"release:check": "node scripts/release-check.js --json",
|
|
40
50
|
"release:sbom": "npm sbom --sbom-format cyclonedx",
|
|
41
51
|
"smoke": "node bin/agentspine.js doctor --json",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { basename, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { installCodexMcp, stripManagedCodexBlock } from "../src/lib/codex-installation.js";
|
|
8
|
+
import { VERSION } from "../src/version.js";
|
|
9
|
+
|
|
10
|
+
function sha256(value) {
|
|
11
|
+
return createHash("sha256").update(value).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function copyFilter(source) {
|
|
15
|
+
const name = basename(source);
|
|
16
|
+
return !new Set([".git", "node_modules"]).has(name) && !name.endsWith(".tgz");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toolValue(response) {
|
|
20
|
+
const text = response.result?.content?.[0]?.text;
|
|
21
|
+
return text ? JSON.parse(text) : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function invokeLauncher(launcher, cwd, env, requests, timeoutMs = 10000) {
|
|
25
|
+
return new Promise((resolveResult, reject) => {
|
|
26
|
+
const child = spawn(process.execPath, [launcher], {
|
|
27
|
+
cwd, env: { ...process.env, ...env }, stdio: ["pipe", "pipe", "pipe"]
|
|
28
|
+
});
|
|
29
|
+
const expected = new Set(requests.map((request) => request.id));
|
|
30
|
+
const responses = new Map();
|
|
31
|
+
let stdout = "";
|
|
32
|
+
let stderr = "";
|
|
33
|
+
let settled = false;
|
|
34
|
+
let complete = false;
|
|
35
|
+
const finish = (error) => {
|
|
36
|
+
if (settled) return;
|
|
37
|
+
settled = true;
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
if (error) child.kill();
|
|
40
|
+
if (error) reject(error);
|
|
41
|
+
else resolveResult(responses);
|
|
42
|
+
};
|
|
43
|
+
const timer = setTimeout(() => finish(new Error(`Codex launcher probe timed out${stderr ? `: ${stderr}` : ""}`)), timeoutMs);
|
|
44
|
+
child.stderr.setEncoding("utf8");
|
|
45
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
46
|
+
child.stdout.setEncoding("utf8");
|
|
47
|
+
child.stdout.on("data", (chunk) => {
|
|
48
|
+
stdout += chunk;
|
|
49
|
+
let newline;
|
|
50
|
+
while ((newline = stdout.indexOf("\n")) >= 0) {
|
|
51
|
+
const line = stdout.slice(0, newline).trim();
|
|
52
|
+
stdout = stdout.slice(newline + 1);
|
|
53
|
+
if (!line) continue;
|
|
54
|
+
let response;
|
|
55
|
+
try { response = JSON.parse(line); } catch (error) { return finish(error); }
|
|
56
|
+
if (expected.has(response.id)) responses.set(response.id, response);
|
|
57
|
+
if (responses.size === expected.size && !complete) {
|
|
58
|
+
complete = true;
|
|
59
|
+
child.stdin.end();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
child.on("error", finish);
|
|
64
|
+
child.on("close", (code) => {
|
|
65
|
+
if (!settled) finish(complete ? null
|
|
66
|
+
: new Error(`Codex launcher exited with ${code}${stderr ? `: ${stderr}` : ""}`));
|
|
67
|
+
});
|
|
68
|
+
for (const request of requests) child.stdin.write(`${JSON.stringify(request)}\n`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hookRequirement(packageRoot, projectRoot, stateRoot) {
|
|
73
|
+
const input = {
|
|
74
|
+
host: "codex", cwd: projectRoot, session_id: "session:synthetic-codex-install",
|
|
75
|
+
hook_event_name: "UserPromptSubmit", event_id: "prompt:synthetic-codex-install",
|
|
76
|
+
prompt: "Continue the synthetic installed delivery.",
|
|
77
|
+
agent_spine_scope: { project_id: "project:synthetic-codex-install" }
|
|
78
|
+
};
|
|
79
|
+
const result = spawnSync(process.execPath, [join(packageRoot, "src", "hook.js")], {
|
|
80
|
+
cwd: projectRoot, env: { ...process.env, AGENTSPINE_STATE_DIR: stateRoot },
|
|
81
|
+
encoding: "utf8", input: JSON.stringify(input)
|
|
82
|
+
});
|
|
83
|
+
if (result.status !== 0) throw new Error(`installed Codex hook failed: ${result.stderr}`);
|
|
84
|
+
const context = JSON.parse(JSON.parse(result.stdout).hookSpecificOutput.additionalContext);
|
|
85
|
+
const requirementId = context.preflight?.premortem?.requirementId;
|
|
86
|
+
if (!requirementId) throw new Error("installed Codex hook did not issue a delivery requirement");
|
|
87
|
+
return requirementId;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function oldPackage(root, target) {
|
|
91
|
+
await cp(root, target, { recursive: true, filter: copyFilter });
|
|
92
|
+
const pkgPath = join(target, "package.json");
|
|
93
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
94
|
+
pkg.version = "0.72.5";
|
|
95
|
+
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
|
96
|
+
await writeFile(join(target, "src", "version.js"), 'export const VERSION = "0.72.5";\n', "utf8");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function checkCodexInstall(root, workspace) {
|
|
100
|
+
root = resolve(root);
|
|
101
|
+
const codexHome = join(workspace, "codex-home");
|
|
102
|
+
const skillsRoot = join(workspace, "user-home", ".agents", "skills");
|
|
103
|
+
const projectRoot = join(workspace, "project");
|
|
104
|
+
const stateRoot = join(workspace, "state");
|
|
105
|
+
const previousRoot = join(workspace, "cache-0.72.5");
|
|
106
|
+
await Promise.all([
|
|
107
|
+
mkdir(codexHome, { recursive: true }), mkdir(join(projectRoot, ".git"), { recursive: true }),
|
|
108
|
+
mkdir(stateRoot, { recursive: true }), oldPackage(root, previousRoot)
|
|
109
|
+
]);
|
|
110
|
+
const foreignConfig = [
|
|
111
|
+
'model = "gpt-synthetic"',
|
|
112
|
+
"[mcp_servers.foreign]",
|
|
113
|
+
'command = "foreign-server"',
|
|
114
|
+
'args = ["--keep-byte-exact"]',
|
|
115
|
+
""
|
|
116
|
+
].join("\r\n");
|
|
117
|
+
const source = "# Synthetic Codex project\n\nKeep this source byte-exact.\n";
|
|
118
|
+
const unknown = '{"schema":"external.unknown-event/v99","decision":"not-authority"}\n';
|
|
119
|
+
const configPath = join(codexHome, "config.toml");
|
|
120
|
+
const sourcePath = join(projectRoot, "AGENTS.md");
|
|
121
|
+
const unknownPath = join(stateRoot, "external-event.json");
|
|
122
|
+
await Promise.all([
|
|
123
|
+
writeFile(configPath, foreignConfig, "utf8"),
|
|
124
|
+
writeFile(sourcePath, source, "utf8"),
|
|
125
|
+
writeFile(unknownPath, unknown, "utf8")
|
|
126
|
+
]);
|
|
127
|
+
const initial = await installCodexMcp({ codexHome, skillsRoot, packageRoot: previousRoot });
|
|
128
|
+
const requirementId = hookRequirement(previousRoot, projectRoot, stateRoot);
|
|
129
|
+
const oldResponses = await invokeLauncher(initial.launcherPath, projectRoot, {
|
|
130
|
+
AGENTSPINE_STATE_DIR: stateRoot, PLUGIN_ROOT: join(workspace, "wrong-plugin-root")
|
|
131
|
+
}, [
|
|
132
|
+
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18" } },
|
|
133
|
+
{ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
|
|
134
|
+
{ jsonrpc: "2.0", id: 3, method: "tools/call", params: { name: "session_briefing",
|
|
135
|
+
arguments: { root: projectRoot, cwd: projectRoot, host: "codex", maxBytes: 4096,
|
|
136
|
+
includeSourceContent: false } } }
|
|
137
|
+
]);
|
|
138
|
+
if (oldResponses.get(1)?.result?.serverInfo?.version !== "0.72.5"
|
|
139
|
+
|| (oldResponses.get(2)?.result?.tools || []).length < 3 || oldResponses.get(3)?.result?.isError) {
|
|
140
|
+
throw new Error("pre-update Codex process did not load the registered reader");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const updated = await installCodexMcp({ codexHome, skillsRoot, packageRoot: root });
|
|
144
|
+
await rm(previousRoot, { recursive: true, force: true });
|
|
145
|
+
const requests = [
|
|
146
|
+
{ jsonrpc: "2.0", id: 10, method: "initialize", params: { protocolVersion: "2025-06-18" } },
|
|
147
|
+
{ jsonrpc: "2.0", id: 11, method: "tools/list", params: {} },
|
|
148
|
+
{ jsonrpc: "2.0", id: 12, method: "tools/call", params: { name: "read_document",
|
|
149
|
+
arguments: { root: projectRoot, host: "codex", path: "AGENTS.md", offset: 0, length: 4096 } } },
|
|
150
|
+
{ jsonrpc: "2.0", id: 13, method: "tools/call", params: { name: "session_briefing",
|
|
151
|
+
arguments: { root: projectRoot, cwd: projectRoot, host: "codex", projectId: "project:synthetic-codex-install",
|
|
152
|
+
requirementId, maxBytes: 4096, includeSourceContent: false } } },
|
|
153
|
+
{ jsonrpc: "2.0", id: 14, method: "tools/call", params: { name: "delivery_knowledge_query",
|
|
154
|
+
arguments: { root: projectRoot, requirementId, targetPaths: ["artifact.txt"], contractPaths: ["AGENTS.md"],
|
|
155
|
+
recentErrorTerms: ["stale reader", "missing registration"], maxBytes: 8192 } } },
|
|
156
|
+
{ jsonrpc: "2.0", id: 15, method: "tools/call", params: { name: "record_delivery_premortem",
|
|
157
|
+
arguments: { root: projectRoot, requirementId, items: [
|
|
158
|
+
{ category: "baseline-environment", failure: "this delivery fails because the installed reader is stale", check: "Check the loaded reader version." },
|
|
159
|
+
{ category: "contract-tests", failure: "this delivery fails because required tools are missing", check: "List and call the required tools." },
|
|
160
|
+
{ category: "delivery-path", failure: "this delivery fails because configuration bytes changed", check: "Hash unmanaged configuration and sources." }
|
|
161
|
+
] } } }
|
|
162
|
+
];
|
|
163
|
+
const responses = await invokeLauncher(updated.launcherPath, projectRoot, {
|
|
164
|
+
AGENTSPINE_STATE_DIR: stateRoot, PLUGIN_ROOT: join(workspace, "wrong-plugin-root")
|
|
165
|
+
}, requests);
|
|
166
|
+
const toolNames = new Set((responses.get(11)?.result?.tools || []).map((tool) => tool.name));
|
|
167
|
+
const read = toolValue(responses.get(12));
|
|
168
|
+
const briefing = toolValue(responses.get(13));
|
|
169
|
+
const knowledge = toolValue(responses.get(14));
|
|
170
|
+
const premortem = toolValue(responses.get(15));
|
|
171
|
+
for (const name of ["session_briefing", "delivery_knowledge_query", "record_delivery_premortem"]) {
|
|
172
|
+
if (!toolNames.has(name)) throw new Error(`updated Codex reader is missing ${name}`);
|
|
173
|
+
}
|
|
174
|
+
if (responses.get(10)?.result?.serverInfo?.version !== VERSION || read?.content !== source
|
|
175
|
+
|| briefing?.deliveryUseReceipt?.blocked || knowledge?.deliveryUseReceipt?.blocked
|
|
176
|
+
|| premortem?.blocked || premortem?.status !== "recorded") {
|
|
177
|
+
throw new Error("updated Codex reader did not preserve the existing session and required calls");
|
|
178
|
+
}
|
|
179
|
+
const config = await readFile(configPath, "utf8");
|
|
180
|
+
const installedSkill = await readFile(updated.skill.skillPath);
|
|
181
|
+
const sourceSkill = await readFile(join(root, "skills", "agent-spine", "SKILL.md"));
|
|
182
|
+
if (stripManagedCodexBlock(config) !== foreignConfig
|
|
183
|
+
|| (config.match(/^\[mcp_servers\.agent-spine\]$/gm) || []).length !== 1
|
|
184
|
+
|| !installedSkill.equals(sourceSkill)
|
|
185
|
+
|| await readFile(sourcePath, "utf8") !== source || await readFile(unknownPath, "utf8") !== unknown) {
|
|
186
|
+
throw new Error("Codex installation changed foreign configuration, source bytes, or unknown state");
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
ok: true,
|
|
190
|
+
version: VERSION,
|
|
191
|
+
oldVersion: "0.72.5",
|
|
192
|
+
configuredOnce: true,
|
|
193
|
+
foreignConfigDigest: sha256(foreignConfig),
|
|
194
|
+
unmanagedConfigDigest: updated.unmanagedAfterDigest,
|
|
195
|
+
sourceDigest: sha256(source),
|
|
196
|
+
unknownStateDigest: sha256(unknown),
|
|
197
|
+
tools: [...toolNames].filter((name) => REQUIRED_TOOL_NAMES.has(name)).sort(),
|
|
198
|
+
existingSessionReadable: true,
|
|
199
|
+
boundRequiredCalls: true,
|
|
200
|
+
nativeSkillInstalled: true,
|
|
201
|
+
wrongPluginRootIgnored: true,
|
|
202
|
+
authority: "installation-check-only"
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const REQUIRED_TOOL_NAMES = new Set([
|
|
207
|
+
"session_briefing", "delivery_knowledge_query", "record_delivery_premortem", "read_document"
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
async function main() {
|
|
211
|
+
const root = resolve(process.argv[2] || process.cwd());
|
|
212
|
+
const workspace = resolve(process.argv[3] || join(root, ".synthetic-codex-install"));
|
|
213
|
+
await mkdir(workspace, { recursive: true });
|
|
214
|
+
try {
|
|
215
|
+
process.stdout.write(`${JSON.stringify(await checkCodexInstall(root, workspace), null, 2)}\n`);
|
|
216
|
+
} finally {
|
|
217
|
+
if (!process.argv[3]) await rm(workspace, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
222
|
+
main().catch((error) => {
|
|
223
|
+
process.stderr.write(`AgentSpine Codex install check failed: ${error.message}\n`);
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
@@ -3,6 +3,10 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { installedHostEnvironment } from "./check-install-hook.js";
|
|
7
|
+
|
|
8
|
+
const TIMELINE_PRE_TOOL_MATCHER = "^(?:Edit|Write|apply_patch|Bash|PowerShell|mcp__plugin_agent-spine_agent-spine__session_timeline_(?:index|search))$";
|
|
9
|
+
const BLUN_TIMELINE_PRE_TOOL_MATCHER = "^(?:Edit|Write|apply_patch|Bash|PowerShell|exec_command|mcp__(?:agent-spine|plugin-agent-spine_agent-spine)__session_timeline_(?:index|search))$";
|
|
6
10
|
|
|
7
11
|
function assert(condition, message) {
|
|
8
12
|
if (!condition) throw new Error(message);
|
|
@@ -25,7 +29,7 @@ async function validateEntrypoint(root, value) {
|
|
|
25
29
|
assert(metadata.isFile(), "MCP entrypoint must be a regular file");
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
function validateHooks(root, hooks, { required, commandRoot }) {
|
|
32
|
+
function validateHooks(root, hooks, { required, commandRoot, matcher = TIMELINE_PRE_TOOL_MATCHER }) {
|
|
29
33
|
assert(hooks && typeof hooks === "object" && !Array.isArray(hooks), "hook bundle is missing");
|
|
30
34
|
assert(Object.keys(hooks).every((key) => ["description", "hooks"].includes(key)), "hook bundle contains unsupported top-level metadata");
|
|
31
35
|
assert(hooks.description && typeof hooks.description === "string", "hook bundle description is missing");
|
|
@@ -39,6 +43,8 @@ function validateHooks(root, hooks, { required, commandRoot }) {
|
|
|
39
43
|
? " --silent-oversize-post-tool-use" : ""}`;
|
|
40
44
|
assert(command.command === expected, `${event} must use the bundled lifecycle adapter`);
|
|
41
45
|
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} timeout is unsafe`);
|
|
46
|
+
if (event === "PreToolUse") assert(registrations[0].matcher === matcher,
|
|
47
|
+
"PreToolUse must route only exact mutations and timeline MCP calls through one guard");
|
|
42
48
|
}
|
|
43
49
|
const extras = Object.keys(hooks.hooks || {}).filter((event) => !required.includes(event));
|
|
44
50
|
assert(extras.length === 0, `unknown hook events: ${extras.join(", ")}`);
|
|
@@ -62,6 +68,8 @@ function validateBlunHooks(root, hooks) {
|
|
|
62
68
|
? " --silent-oversize-post-tool-use" : ""}`;
|
|
63
69
|
assert(command.command === expected, `${event} must use the bundled BLUN lifecycle adapter`);
|
|
64
70
|
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} BLUN timeout is unsafe`);
|
|
71
|
+
if (event === "PreToolUse") assert(command.matcher === BLUN_TIMELINE_PRE_TOOL_MATCHER,
|
|
72
|
+
"BLUN PreToolUse must route only exact mutations and timeline MCP calls through one guard");
|
|
65
73
|
}
|
|
66
74
|
return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
|
|
67
75
|
}
|
|
@@ -81,7 +89,7 @@ async function initializeServer({ label, root, variable, server, version }) {
|
|
|
81
89
|
return await new Promise((resolveResult, reject) => {
|
|
82
90
|
const child = spawn(command, args, {
|
|
83
91
|
cwd: root,
|
|
84
|
-
env:
|
|
92
|
+
env: installedHostEnvironment(label, root),
|
|
85
93
|
stdio: ["pipe", "pipe", "pipe"]
|
|
86
94
|
});
|
|
87
95
|
let stdout = "";
|
|
@@ -152,14 +160,15 @@ export async function checkHosts(root = process.cwd()) {
|
|
|
152
160
|
await validateEntrypoint(root, resolve(root, pkg.bin["agentspine-worker"]));
|
|
153
161
|
assert(claudeManifest.mcpServers === "./.mcp.json", "Claude manifest must explicitly reference ./.mcp.json");
|
|
154
162
|
assert(claudeManifest.hooks === undefined, "default hooks/hooks.json must not also be registered through a supplemental manifest path");
|
|
155
|
-
assert(codexManifest.hooks ===
|
|
163
|
+
assert(codexManifest.hooks === "./hooks/codex.json", "Codex manifest must select its host-specific hook adapter");
|
|
156
164
|
assert(claudeMcp.mcpServers && Object.keys(claudeMcp.mcpServers).length === 1, "Claude MCP file must contain one mcpServers registration");
|
|
157
165
|
assert(codexManifest.mcpServers && Object.keys(codexManifest.mcpServers).length === 1, "Codex manifest must contain one MCP registration");
|
|
158
166
|
const commonEvents = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PreCompact", "PostCompact", "Stop", "SubagentStop"];
|
|
159
167
|
const claudeHookInventory = validateHooks(root, claudeHooks, {
|
|
160
168
|
required: [...commonEvents, "InstructionsLoaded"], commandRoot: "CLAUDE_PLUGIN_ROOT"
|
|
161
169
|
});
|
|
162
|
-
const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT"
|
|
170
|
+
const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT",
|
|
171
|
+
matcher: "^(?:Edit|Write|apply_patch|Bash|PowerShell|mcp__(?:plugin_agent-spine_agent-spine|agent-spine)__session_timeline_(?:index|search))$" });
|
|
163
172
|
const blunHookInventory = validateBlunHooks(root, blunManifest.hooks);
|
|
164
173
|
const registrations = await Promise.all([
|
|
165
174
|
initializeServer({ label: "blun", root, variable: "BLUN_PLUGIN_ROOT", server: blunManifest.mcpServers["agent-spine"], version: pkg.version }),
|
|
@@ -170,7 +179,7 @@ export async function checkHosts(root = process.cwd()) {
|
|
|
170
179
|
ok: true, root, version: pkg.version, registrations,
|
|
171
180
|
hooks: { blun: blunHookInventory, claude: claudeHookInventory, codex: codexHookInventory },
|
|
172
181
|
hookDiscovery: {
|
|
173
|
-
blun: "plugin-manifest", claude: "default-hooks-directory", codex: "
|
|
182
|
+
blun: "plugin-manifest", claude: "default-hooks-directory", codex: "plugin-manifest",
|
|
174
183
|
trust: "host-user-required", liveTrustVerified: false
|
|
175
184
|
},
|
|
176
185
|
worker: { entrypoint: pkg.bin["agentspine-worker"], setsPerInstall: 1 },
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
const HOST_ROOTS = {
|
|
7
|
+
blun: ["BLUN_PLUGIN_ROOT"],
|
|
8
|
+
claude: ["CLAUDE_PLUGIN_ROOT"],
|
|
9
|
+
codex: ["PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT"]
|
|
10
|
+
};
|
|
11
|
+
const HOST_ROOT_KEYS = ["BLUN_PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT", "PLUGIN_ROOT"];
|
|
12
|
+
|
|
13
|
+
export function installedHostEnvironment(host, pluginRoot, extraEnv = {}) {
|
|
14
|
+
const rootKeys = HOST_ROOTS[host];
|
|
15
|
+
if (!rootKeys) throw new Error(`unsupported installed host: ${host}`);
|
|
16
|
+
const env = { ...process.env, ...extraEnv };
|
|
17
|
+
for (const key of HOST_ROOT_KEYS) delete env[key];
|
|
18
|
+
for (const key of rootKeys) env[key] = pluginRoot;
|
|
19
|
+
return env;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseProtocol(stdout, host) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(stdout.trim());
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`${host} installed hook returned invalid protocol JSON: ${stdout.slice(0, 2048)}`, { cause: error });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseContext(protocol, host, requireBriefing) {
|
|
31
|
+
const raw = protocol.hookSpecificOutput?.additionalContext;
|
|
32
|
+
if (typeof raw !== "string" || !raw) return null;
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (!requireBriefing) return null;
|
|
37
|
+
throw new Error(`${host} installed hook returned invalid briefing context JSON`, { cause: error });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function protocolShape(protocol) {
|
|
42
|
+
const specific = protocol.hookSpecificOutput;
|
|
43
|
+
return {
|
|
44
|
+
protocolKeys: Object.keys(protocol).sort(),
|
|
45
|
+
hookSpecificKeys: specific && typeof specific === "object" ? Object.keys(specific).sort() : [],
|
|
46
|
+
permissionDecision: specific?.permissionDecision || null,
|
|
47
|
+
permissionDecisionReason: specific?.permissionDecisionReason || null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function invokeInstalledHook(pluginRoot, projectRoot, stateRoot, host, payload = null,
|
|
52
|
+
{ requireBriefing = true, extraEnv = {} } = {}) {
|
|
53
|
+
return await new Promise((resolveResult, reject) => {
|
|
54
|
+
const child = spawn(process.execPath, [join(pluginRoot, "src/hook.js")], {
|
|
55
|
+
cwd: projectRoot,
|
|
56
|
+
env: installedHostEnvironment(host, pluginRoot, {
|
|
57
|
+
AGENTSPINE_HOST: host,
|
|
58
|
+
AGENTSPINE_STATE_DIR: stateRoot,
|
|
59
|
+
...extraEnv
|
|
60
|
+
}),
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
62
|
+
});
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
child.kill();
|
|
67
|
+
reject(new Error(`${host} installed hook timed out`));
|
|
68
|
+
}, 5000);
|
|
69
|
+
child.stdout.setEncoding("utf8");
|
|
70
|
+
child.stderr.setEncoding("utf8");
|
|
71
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
72
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
73
|
+
child.once("error", reject);
|
|
74
|
+
child.once("exit", (code) => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
if (code !== 0) {
|
|
77
|
+
reject(new Error(`${host} installed hook exited with ${code}: ${stderr.slice(0, 2048)}`));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const protocol = parseProtocol(stdout, host);
|
|
82
|
+
const context = parseContext(protocol, host, requireBriefing);
|
|
83
|
+
if (requireBriefing && (context?.briefing?.host !== host
|
|
84
|
+
|| !Array.isArray(context?.briefing?.sources?.documents))) {
|
|
85
|
+
throw new Error(`${host} installed hook did not inject a real session briefing: `
|
|
86
|
+
+ `${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
|
+
message: protocol.hookSpecificOutput?.message || null,
|
|
104
|
+
...protocolShape(protocol)
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
reject(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
child.stdin.end(`${JSON.stringify(payload || {
|
|
111
|
+
hook_event_name: "SessionStart", cwd: projectRoot, host
|
|
112
|
+
})}\n`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function invokeInstalledBlunPostWriteDigest(pluginRoot, projectRoot, stateRoot) {
|
|
117
|
+
const sessionId = `session:installed-blun-post-${stateRoot.split(/[\\/]/).at(-1)}`;
|
|
118
|
+
const shared = {
|
|
119
|
+
hook_event_name: "UserPromptSubmit", cwd: projectRoot, host: "blun",
|
|
120
|
+
project_id: "project:installed-blun-protocol", session_id: sessionId,
|
|
121
|
+
event_id: `prompt:${sessionId}`, prompt: "Change the synthetic installed BLUN artifact safely."
|
|
122
|
+
};
|
|
123
|
+
const prompted = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", shared,
|
|
124
|
+
{ requireBriefing: false });
|
|
125
|
+
const requirementId = prompted.message?.match(
|
|
126
|
+
/Requirement: (premortem-requirement:[a-f0-9]{64}:[a-f0-9]{64})/
|
|
127
|
+
)?.[1];
|
|
128
|
+
if (!requirementId) throw new Error("installed BLUN prompt did not expose its premortem requirement");
|
|
129
|
+
const previousStateRoot = process.env.AGENTSPINE_STATE_DIR;
|
|
130
|
+
try {
|
|
131
|
+
process.env.AGENTSPINE_STATE_DIR = stateRoot;
|
|
132
|
+
const premortem = await import(pathToFileURL(join(pluginRoot, "src/lib/delivery-premortem.js")).href);
|
|
133
|
+
const usage = await import(pathToFileURL(join(pluginRoot, "src/lib/delivery-agent-usage.js")).href);
|
|
134
|
+
await usage.recordDeliveryBriefingUse({ root: projectRoot, requirementId,
|
|
135
|
+
input: { root: projectRoot }, result: { schema: "synthetic-installed-briefing" } });
|
|
136
|
+
await usage.recordDeliveryKnowledgeUse({ root: projectRoot, requirementId,
|
|
137
|
+
input: { targets: ["AGENTS.md"] }, result: { schema: "synthetic-installed-knowledge" } });
|
|
138
|
+
const recorded = await premortem.recordDeliveryPremortem({
|
|
139
|
+
root: projectRoot, requirementId, items: [
|
|
140
|
+
{ category: "baseline-environment", failure: "this delivery fails because the installed baseline is stale",
|
|
141
|
+
check: "Compare the installed synthetic snapshot digest." },
|
|
142
|
+
{ category: "contract-tests", failure: "this delivery fails because the installed contract regresses",
|
|
143
|
+
check: "Run the installed synthetic Node test." },
|
|
144
|
+
{ category: "delivery-path", failure: "this delivery fails because the installed artifact is misplaced",
|
|
145
|
+
check: "Verify the installed synthetic artifact path and digest." }
|
|
146
|
+
]
|
|
147
|
+
});
|
|
148
|
+
if (recorded.blocked) throw new Error(`installed BLUN premortem registration failed: ${recorded.reason}`);
|
|
149
|
+
} finally {
|
|
150
|
+
if (previousStateRoot === undefined) delete process.env.AGENTSPINE_STATE_DIR;
|
|
151
|
+
else process.env.AGENTSPINE_STATE_DIR = previousStateRoot;
|
|
152
|
+
}
|
|
153
|
+
const filePath = join(projectRoot, "installed-blun-post-write.txt");
|
|
154
|
+
const tool = { tool_name: "Write", tool_use_id: `write:${sessionId}`,
|
|
155
|
+
tool_input: { file_path: filePath, content: "synthetic installed BLUN write\n" } };
|
|
156
|
+
const pre = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", {
|
|
157
|
+
...shared, ...tool, hook_event_name: "PreToolUse"
|
|
158
|
+
}, { requireBriefing: false });
|
|
159
|
+
if (pre.decision === "block" || pre.permissionDecision === "deny") {
|
|
160
|
+
throw new Error(`installed BLUN PreToolUse denied its registered premortem: ${pre.reason}`);
|
|
161
|
+
}
|
|
162
|
+
await writeFile(filePath, tool.tool_input.content, "utf8");
|
|
163
|
+
const post = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "blun", {
|
|
164
|
+
...shared, ...tool, hook_event_name: "PostToolUse", success: true, tool_response: { ok: true }
|
|
165
|
+
}, { requireBriefing: false });
|
|
166
|
+
const digest = post.message?.match(/Premortem latest write sha256 ([a-f0-9]{64})/)?.[1];
|
|
167
|
+
if (!digest || !exactKeys(post.protocolKeys, ["hookSpecificOutput"])
|
|
168
|
+
|| !exactKeys(post.hookSpecificKeys, ["hookEventName", "message"])) {
|
|
169
|
+
throw new Error("installed BLUN PostToolUse did not return its latest-write digest through message");
|
|
170
|
+
}
|
|
171
|
+
return { contextField: "message", latestWriteDigest: digest };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function exactKeys(value, expected) {
|
|
175
|
+
return JSON.stringify(value) === JSON.stringify([...expected].sort());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function invokeInstalledBlockingProtocols(pluginRoot, projectRoot, stateRoot) {
|
|
179
|
+
const writePayload = (host) => ({
|
|
180
|
+
hook_event_name: "PreToolUse", cwd: projectRoot, host,
|
|
181
|
+
project_id: "project:installed-protocol",
|
|
182
|
+
session_id: `session:installed-${host}-protocol`, tool_name: "Write",
|
|
183
|
+
tool_use_id: `tool:installed-${host}-protocol`,
|
|
184
|
+
tool_input: { file_path: "AGENTS.md", content: "synthetic\n" }
|
|
185
|
+
});
|
|
186
|
+
const claudePreTool = await invokeInstalledHook(
|
|
187
|
+
pluginRoot, projectRoot, stateRoot, "claude", writePayload("claude"), { requireBriefing: false }
|
|
188
|
+
);
|
|
189
|
+
const claudeStop = await invokeInstalledHook(pluginRoot, projectRoot, stateRoot, "claude", {
|
|
190
|
+
hook_event_name: "Stop", cwd: projectRoot, host: "claude",
|
|
191
|
+
project_id: "project:installed-protocol",
|
|
192
|
+
session_id: "session:installed-claude-stop-protocol",
|
|
193
|
+
agent_spine_exchange_directory: projectRoot,
|
|
194
|
+
final_assistant_message: "missing-installed-artifact.txt sha256 0000000000000000"
|
|
195
|
+
}, { requireBriefing: false });
|
|
196
|
+
const codexPreTool = await invokeInstalledHook(
|
|
197
|
+
pluginRoot, projectRoot, stateRoot, "codex", writePayload("codex"), { requireBriefing: false }
|
|
198
|
+
);
|
|
199
|
+
if (claudePreTool.decision !== null || claudePreTool.permissionDecision !== "deny"
|
|
200
|
+
|| !claudePreTool.permissionDecisionReason?.includes("protected source")
|
|
201
|
+
|| !exactKeys(claudePreTool.protocolKeys, ["hookSpecificOutput"])
|
|
202
|
+
|| !exactKeys(claudePreTool.hookSpecificKeys,
|
|
203
|
+
["hookEventName", "permissionDecision", "permissionDecisionReason"])) {
|
|
204
|
+
throw new Error("installed Claude PreToolUse denial did not use its nested permissionDecision schema");
|
|
205
|
+
}
|
|
206
|
+
if (claudeStop.decision !== null || claudeStop.permissionDecision !== null) {
|
|
207
|
+
throw new Error("installed Claude Stop must not block replies for artifact evidence");
|
|
208
|
+
}
|
|
209
|
+
if (codexPreTool.decision !== "block" || !codexPreTool.reason?.includes("protected source")
|
|
210
|
+
|| codexPreTool.permissionDecision !== null
|
|
211
|
+
|| !exactKeys(codexPreTool.protocolKeys, ["decision", "reason"])) {
|
|
212
|
+
const observed = {
|
|
213
|
+
decision: codexPreTool.decision, reason: codexPreTool.reason,
|
|
214
|
+
protocolKeys: codexPreTool.protocolKeys, hookSpecificKeys: codexPreTool.hookSpecificKeys,
|
|
215
|
+
permissionDecision: codexPreTool.permissionDecision,
|
|
216
|
+
sourceResolution: codexPreTool.sourceResolution, preflight: codexPreTool.preflight,
|
|
217
|
+
selfstarter: codexPreTool.selfstarter
|
|
218
|
+
};
|
|
219
|
+
throw new Error("installed Codex denial did not preserve its strict top-level decision/reason schema: "
|
|
220
|
+
+ JSON.stringify(observed).slice(0, 8192));
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
claude: { preTool: "nested-permission-deny", stop: "advisory" },
|
|
224
|
+
codex: { preTool: "top-level-block" }
|
|
225
|
+
};
|
|
226
|
+
}
|