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,259 +0,0 @@
|
|
|
1
|
-
# Shared memory adapters
|
|
2
|
-
|
|
3
|
-
AgentSpine can exchange reviewed context between installations without requiring a vendor account, hosted database, or network service. The first reference adapter uses an ordinary directory. That directory may remain local or be placed on a user-controlled network drive or synchronization service.
|
|
4
|
-
|
|
5
|
-
A signed directory can also be exported as an immutable JSON file for the hardened, provider-neutral [HTTPS snapshot transport](https-transport.md). HTTPS is only another delivery path: all signer trust, quarantine, local review, privacy, history, and authority rules remain identical.
|
|
6
|
-
|
|
7
|
-
Shared memory is optional. Local discovery, identity, learning, relationships, attention, coordination, and auditing remain complete when no adapter exists.
|
|
8
|
-
|
|
9
|
-
## Trust flow
|
|
10
|
-
|
|
11
|
-
```mermaid
|
|
12
|
-
flowchart LR
|
|
13
|
-
L["Locally accepted learning"]
|
|
14
|
-
P["Owner-confirmed publish"]
|
|
15
|
-
E["Immutable shared event"]
|
|
16
|
-
Q["Receiving quarantine"]
|
|
17
|
-
R["Second local review"]
|
|
18
|
-
C["Privacy-filtered shared context"]
|
|
19
|
-
L --> P --> E --> Q --> R --> C
|
|
20
|
-
Q -. "not visible" .-> C
|
|
21
|
-
C -. "never grants" .-> A["Host or delegation authority"]
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Only an accepted local learning record can be published. The receiving installation imports the event as `pending`; it is absent from `shared_context` until a local user explicitly accepts it. This double-review model treats transport integrity and human trust as separate concerns.
|
|
25
|
-
|
|
26
|
-
AgentSpine never shares:
|
|
27
|
-
|
|
28
|
-
- private learning;
|
|
29
|
-
- source Markdown or document contents;
|
|
30
|
-
- evidence summaries or conversation transcripts;
|
|
31
|
-
- delegation policy or assignment grants;
|
|
32
|
-
- tasks, attention cues, credentials, or host settings;
|
|
33
|
-
- graph state or private relationship profiles.
|
|
34
|
-
|
|
35
|
-
## Directory adapter
|
|
36
|
-
|
|
37
|
-
Initialize a scope in a directory outside the scanned project:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
agentspine share-init /srv/agent-memory/team-alpha \
|
|
41
|
-
--scope team:alpha \
|
|
42
|
-
--adapter adapter:team-alpha \
|
|
43
|
-
--confirm-local-share
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
The resulting layout is deliberately portable:
|
|
47
|
-
|
|
48
|
-
```text
|
|
49
|
-
team-alpha/
|
|
50
|
-
.agentspine-exchange.json
|
|
51
|
-
events/
|
|
52
|
-
<sha256-of-event-id>.json
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
The manifest binds the directory to one stable scope. Events are immutable JSON records. Their filenames derive from stable event IDs, which gives different writers deterministic collision behavior without relying on a central server.
|
|
56
|
-
|
|
57
|
-
The directory must be outside the scanned project, including after symlink resolution. Adapter manifests, event files, and the event directory must be regular filesystem objects rather than symlinks. A pull is capped at 2,000 events and 20 MiB; each manifest or event is capped at 64 KiB.
|
|
58
|
-
|
|
59
|
-
## Authenticated signed mode
|
|
60
|
-
|
|
61
|
-
The baseline adapter uses canonical SHA-256 integrity for compatibility. Anyone with write access can replace a baseline event and recompute its digest. Signed mode adds Ed25519 origin authentication while preserving the same event payload, quarantine, privacy, and review workflow.
|
|
62
|
-
|
|
63
|
-
```mermaid
|
|
64
|
-
flowchart LR
|
|
65
|
-
K["Publisher private key\ninstallation-local"] --> S["Signed manifest + events"]
|
|
66
|
-
P["Exported public identity"] --> T["Receiver project trust store"]
|
|
67
|
-
S --> V["Signature verification"]
|
|
68
|
-
T --> V
|
|
69
|
-
V --> Q["Pending quarantine"]
|
|
70
|
-
Q --> R["Independent local content review"]
|
|
71
|
-
R --> C["Context-only memory"]
|
|
72
|
-
V -. "authenticates key, not permission" .-> A["No host or delegation authority"]
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Create a local signer and export only its public identity:
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
agentspine share-keygen signer:team-alpha \
|
|
79
|
-
--root /path/to/publisher-project \
|
|
80
|
-
--public-out /safe/exchange/team-alpha-signer.json \
|
|
81
|
-
--confirm-local-share
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The private key is generated under the AgentSpine installation state directory. It is never returned by the command, copied into the project, placed in the adapter, exposed through MCP, or injected by hooks. The exported JSON contains the Ed25519 public key, stable signer ID, cryptographic key fingerprint, creation time, and integrity digest.
|
|
85
|
-
|
|
86
|
-
The receiving project must trust that exact exported key through a genuine local owner action:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
agentspine share-trust /safe/exchange/team-alpha-signer.json \
|
|
90
|
-
--root /path/to/receiving-project \
|
|
91
|
-
--confirm-local-share
|
|
92
|
-
|
|
93
|
-
agentspine share-trust-list /path/to/receiving-project --json
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Create and publish through a signed adapter:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
agentspine share-init /srv/agent-memory/team-alpha \
|
|
100
|
-
--root /path/to/publisher-project \
|
|
101
|
-
--scope team:alpha \
|
|
102
|
-
--signer signer:team-alpha \
|
|
103
|
-
--confirm-local-share
|
|
104
|
-
|
|
105
|
-
agentspine share-publish /srv/agent-memory/team-alpha \
|
|
106
|
-
--root /path/to/publisher-project \
|
|
107
|
-
--learning learning:release-process \
|
|
108
|
-
--id shared:release-process-v1 \
|
|
109
|
-
--signer signer:team-alpha \
|
|
110
|
-
--confirm-local-share
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
Require authentication at the receiver:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
agentspine share-pull /srv/agent-memory/team-alpha \
|
|
117
|
-
--root /path/to/receiving-project \
|
|
118
|
-
--require-authenticated
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Pull verifies the signed manifest and every signed event before any quarantine write. A signed adapter cannot mix unsigned events, and an unsigned adapter cannot smuggle signed files. Each event signer is checked independently, so a trusted adapter owner does not implicitly trust another writer.
|
|
122
|
-
|
|
123
|
-
### What a signature means
|
|
124
|
-
|
|
125
|
-
A valid signature proves that the envelope matches the private key corresponding to a public key explicitly trusted for this receiving project. It does not prove the operator's legal identity, truthfulness, current role, relationship, or permission. It does not approve the claim, grant delegation, authorize a tool, or bypass the second local content review.
|
|
126
|
-
|
|
127
|
-
Public-key exchange must therefore use a channel appropriate to the deployment. Compare the full key fingerprint out of band when identity matters. Encrypt the transport when metadata confidentiality matters; signatures provide authenticity and integrity, not encryption.
|
|
128
|
-
|
|
129
|
-
### Rotation and revocation
|
|
130
|
-
|
|
131
|
-
Rotation is explicit and never overwrites the old public identity silently:
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
agentspine share-keygen signer:team-alpha \
|
|
135
|
-
--root /path/to/publisher-project \
|
|
136
|
-
--rotate \
|
|
137
|
-
--public-out /safe/exchange/team-alpha-signer-v2.json \
|
|
138
|
-
--confirm-local-share
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
The installation retains the retired public identity in history and removes the retired local private key. Receivers import the new public identity as a separate trusted fingerprint. Old signatures remain cryptographically verifiable as long as the old public trust record remains available.
|
|
142
|
-
|
|
143
|
-
Revoke a compromised or retired key locally:
|
|
144
|
-
|
|
145
|
-
```bash
|
|
146
|
-
agentspine share-trust-revoke ed25519:<full-fingerprint> \
|
|
147
|
-
--root /path/to/receiving-project \
|
|
148
|
-
--reason "Key retired after verified rotation" \
|
|
149
|
-
--confirm-local-share
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Revocation blocks new acceptance immediately. If already accepted context depends on that key, shared context and audit fail closed until the user rolls back, rejects, or permanently deletes the affected import. Revocation never rewrites source Markdown or remote adapter files.
|
|
153
|
-
|
|
154
|
-
## Publish and receive
|
|
155
|
-
|
|
156
|
-
Publish an already accepted learning:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
agentspine share-publish /srv/agent-memory/team-alpha \
|
|
160
|
-
--root /path/to/source-project \
|
|
161
|
-
--learning learning:release-process \
|
|
162
|
-
--id shared:release-process-v1 \
|
|
163
|
-
--confirm-local-share
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Pull on another installation:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
agentspine share-pull /srv/agent-memory/team-alpha \
|
|
170
|
-
--root /path/to/receiving-project
|
|
171
|
-
|
|
172
|
-
agentspine share-inbox /path/to/receiving-project --status pending --json
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
Pulling reads the adapter and writes only to the receiving project's external `sharing.json`. It never changes the adapter, the project, or accepted context. Repeated and concurrent pulls are idempotent.
|
|
176
|
-
|
|
177
|
-
Review locally, then read:
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
agentspine share-review shared:release-process-v1 \
|
|
181
|
-
--root /path/to/receiving-project \
|
|
182
|
-
--decision accept \
|
|
183
|
-
--reason "Confirmed for this installation" \
|
|
184
|
-
--confirmed-by-user
|
|
185
|
-
|
|
186
|
-
agentspine share-context /path/to/receiving-project \
|
|
187
|
-
--scope team:alpha \
|
|
188
|
-
--json
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
`--confirmed-by-user` is an integration attestation, not authentication. A wrapper must bind it to a real local user action. It must never be inferred from an imported event, another agent, memory, Markdown, a task, or a prior confirmation at the publishing installation.
|
|
192
|
-
|
|
193
|
-
## Event contract
|
|
194
|
-
|
|
195
|
-
Every `agentspine.shared-event/v1` contains only:
|
|
196
|
-
|
|
197
|
-
- stable event, scope, and origin-instance IDs;
|
|
198
|
-
- learning kind and descriptive claim;
|
|
199
|
-
- optional subject and exact group scope;
|
|
200
|
-
- confidence and publication timestamp;
|
|
201
|
-
- minimal provenance: source learning ID, acceptance timestamp, automatic/manual marker, evidence count, and a digest of the original review proof;
|
|
202
|
-
- optional predecessor event ID;
|
|
203
|
-
- `authority: context-only`;
|
|
204
|
-
- a SHA-256 digest over canonical JSON.
|
|
205
|
-
|
|
206
|
-
Signed mode wraps the unchanged event in an `agentspine.signed-envelope/v1` containing a strict public identity, envelope kind, signing timestamp, `context-only` marker, and Ed25519 signature. The receiver retains the minimal signature proof with the quarantined record and replays it during reads and audits. Public keys and signatures are intentionally omitted from hook text and MCP context results; callers see only a signer ID, key fingerprint, and verification time.
|
|
207
|
-
|
|
208
|
-
Unknown fields are rejected. IDs are identifiers, not identities or authentication claims. An `originInstanceId` distinguishes local installations but does not prove who operated one.
|
|
209
|
-
|
|
210
|
-
The baseline digest detects accidental damage and unsophisticated mutation. It is not a signature: anyone who can write the adapter directory can replace an event and recompute its digest. Signed mode detects that replacement unless the attacker also controls a locally trusted private key. Both modes retain quarantine because authentication and content trust are different decisions.
|
|
211
|
-
|
|
212
|
-
## Supersession and rollback
|
|
213
|
-
|
|
214
|
-
New shared information does not overwrite old context. Publish the replacement with an explicit predecessor:
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
agentspine share-publish /srv/agent-memory/team-alpha \
|
|
218
|
-
--root /path/to/source-project \
|
|
219
|
-
--learning learning:release-process-v2 \
|
|
220
|
-
--id shared:release-process-v2 \
|
|
221
|
-
--supersedes shared:release-process-v1 \
|
|
222
|
-
--confirm-local-share
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The adapter verifies that predecessor and replacement retain the same kind, subject, and privacy scope. When the receiver accepts the replacement, the old active record becomes `superseded` and remains in history. A rollback restores it atomically:
|
|
226
|
-
|
|
227
|
-
```bash
|
|
228
|
-
agentspine share-rollback shared:release-process-v2 \
|
|
229
|
-
--root /path/to/receiving-project \
|
|
230
|
-
--reason "The replacement was incorrect"
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
Permanent local deletion is CLI-only, requires `--confirm-local-share`, and removes the selected import plus retained local versions. It does not delete the immutable event from the shared adapter.
|
|
234
|
-
|
|
235
|
-
## Privacy and groups
|
|
236
|
-
|
|
237
|
-
Only `shared` and `group` learning can be published. Group events retain the exact group ID and optional subject. The receiving installation must know that group and its visible membership before acceptance. `includePrivate` cannot bypass a missing or different group audience.
|
|
238
|
-
|
|
239
|
-
Lifecycle hooks have no group audience. They expose only counts and kinds of already accepted, non-private shared context. They never inject claims, subjects, adapter paths, pending inbox records, or event provenance.
|
|
240
|
-
|
|
241
|
-
## MCP boundary
|
|
242
|
-
|
|
243
|
-
MCP exposes only `shared_context`, which reads locally accepted records. Adapter initialization, publication, pulling, inbox review, rollback, configuration, deletion, key generation, rotation, trust, and revocation remain local CLI operations. An agent therefore cannot use AgentSpine's MCP surface to connect an arbitrary path, export data, accept its own import, read a private key, trust itself, or widen trust.
|
|
244
|
-
|
|
245
|
-
## Adapter compatibility
|
|
246
|
-
|
|
247
|
-
The implemented HTTPS, mutable-feed, peer, and local SQLite adapters map their transport back into the reference directory semantics before local import. Future hosted adapters must produce the same strict manifest and event semantics or map their transport into them before local import:
|
|
248
|
-
|
|
249
|
-
1. immutable stable event IDs;
|
|
250
|
-
2. canonical integrity digest and, for authenticated transports, a verifiable signed envelope;
|
|
251
|
-
3. one explicit scope per adapter connection;
|
|
252
|
-
4. no private, authority, source-document, evidence-text, task, or policy payloads;
|
|
253
|
-
5. quarantine before local review;
|
|
254
|
-
6. idempotent import and collision detection;
|
|
255
|
-
7. retained supersession history and rollback;
|
|
256
|
-
8. no dependency for local AgentSpine operation.
|
|
257
|
-
9. explicit key trust, rotation, and revocation without converting signatures into authority.
|
|
258
|
-
|
|
259
|
-
Transport plugins may strengthen authenticity, encryption, retention, and remote access control. They may never weaken privacy filtering, local confirmation, the context-only authority marker, or protected-source preservation.
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# Host-native source roots
|
|
2
|
-
|
|
3
|
-
AgentSpine `0.8.0` resolves active user, project, and host-memory sources before every production lifecycle hook. Resolution does not depend on the directory from which the plugin was installed, and it never treats the entire home directory as one project.
|
|
4
|
-
|
|
5
|
-
## Resolution contract
|
|
6
|
-
|
|
7
|
-
```mermaid
|
|
8
|
-
flowchart LR
|
|
9
|
-
H["Native hook payload"] --> R["Provider-neutral source-root resolver"]
|
|
10
|
-
C["Claude profile"] --> R
|
|
11
|
-
X["Codex profile"] --> R
|
|
12
|
-
P["Active project chain"] --> R
|
|
13
|
-
B["Explicit local state binding"] --> R
|
|
14
|
-
R --> U["User-wide sources"]
|
|
15
|
-
R --> J["Exact project sources"]
|
|
16
|
-
R --> M["Exact Claude project memory"]
|
|
17
|
-
U --> S["Byte-budgeted session_briefing"]
|
|
18
|
-
J --> S
|
|
19
|
-
M --> S
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Claude resolution follows the documented user and project hierarchy: `CLAUDE_CONFIG_DIR` or `~/.claude`, user `CLAUDE.md` and rules, the active project chain, and the exact project-memory directory evidenced by `autoMemoryDirectory`, `CLAUDE_CODE_PROJECT_DIR_NAME`, or the native hook `transcript_path`. Inside that directory, `MEMORY.md` is the only index. The live path never enumerates that directory and never follows links found inside a fact file. AgentSpine does not guess Claude's private project-directory encoding. See Anthropic's [memory hierarchy and storage-location documentation](https://code.claude.com/docs/en/memory) and [Claude configuration directory documentation](https://code.claude.com/docs/en/claude-directory).
|
|
23
|
-
|
|
24
|
-
## Indexed and lazy memory
|
|
25
|
-
|
|
26
|
-
Every direct index link is counted as indexed, but its target is opened only when a marker proves relevance:
|
|
27
|
-
|
|
28
|
-
```markdown
|
|
29
|
-
- [Communication style](style.md) <!-- agentspine:always -->
|
|
30
|
-
- [BLUN project](projects/blun.md) <!-- agentspine:project=project:blun -->
|
|
31
|
-
- [Alpha team](groups/alpha.md) <!-- agentspine:group=group:alpha -->
|
|
32
|
-
- [Owner preference](people/owner.md) <!-- agentspine:entity=person:owner -->
|
|
33
|
-
- [Current handoff](tasks/handoff.md) <!-- agentspine:task=task:handoff -->
|
|
34
|
-
- [Carbonara preference](food/pasta.md) <!-- agentspine:keywords=carbonara,pasta -->
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
An exact person, project, group, or task ID must match the current hook scope. Prompt relevance requires a normalized keyword match against the link label, filename, or explicit `keywords` marker. If relevance is uncertain, the target remains unopened. The index itself is always loaded so the host retains its native memory overview.
|
|
38
|
-
|
|
39
|
-
The persistent cache lives under AgentSpine's platform state directory, outside ordinary agent projects. If the active project root is exactly a recognized user home, the configured AgentSpine state subtree may be below that root but is explicitly pruned before source enumeration; it is never context. The exception does not apply to nested project roots. The cache stores integrity-checked snapshots keyed by an opaque root digest and relative path. A cache hit still opens and validates the original path and file identity, but does not reread or rehash unchanged source bytes. Corrections, deletion, link removal, source-binding rollback, purge, restart, and compaction invalidate or prune the affected cache record immediately. Cache contents and relevance markers are context-only.
|
|
40
|
-
|
|
41
|
-
Indexed targets use no-follow open semantics. Parent components, canonical scope, regular-file status, size, identity, modification metadata, and the pathname-to-handle identity are checked around the same read. A changing target is retried a bounded number of times and then rejected as a race; mixed snapshots are never injected.
|
|
42
|
-
|
|
43
|
-
The live resolver processes at most 128 direct index links, one target at a time, under the host resolver's two-second work budget. `MEMORY.md` and each target are limited to 4 MiB, while the complete external cache is capped at 16 MiB. Exceeding a bound fails closed instead of widening discovery.
|
|
44
|
-
|
|
45
|
-
Codex resolution uses `CODEX_HOME` or `~/.codex`, selects `AGENTS.override.md` before `AGENTS.md` at user scope, and walks from the configured project root to `cwd`. Per directory it selects override, regular, then the configured `project_doc_fallback_filenames`; `project_root_markers` and `project_doc_max_bytes` are read from the active profile's `config.toml`. Without a root marker, only `cwd` is the project root. See OpenAI's [AGENTS.md discovery order](https://developers.openai.com/codex/agent-configuration/agents-md) and [configuration reference](https://developers.openai.com/codex/config-reference).
|
|
46
|
-
|
|
47
|
-
Only regular files under these evidenced roots are read. Symlinks are skipped. The resolver caps source count, per-file bytes, aggregate bytes, and recursive host-rule files. A project-root scan is never run when the resolved root is the user's home directory. Foreign repositories and arbitrary hidden directories are not traversed.
|
|
48
|
-
|
|
49
|
-
## Portable user continuity
|
|
50
|
-
|
|
51
|
-
Accepted preferences, no-gos, corrections, and references can be attached once to the local user through an explicit state binding. The binding references the existing external AgentSpine state; it does not copy records between project hashes.
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
agentspine source-bind /path/where/continuity-was-configured \
|
|
55
|
-
--host all \
|
|
56
|
-
--scope state-user \
|
|
57
|
-
--project /current/project \
|
|
58
|
-
--host-home /current/profile \
|
|
59
|
-
--confirm-local-binding
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Only portable low-risk learning and the known person relationship context are read from this binding. Project facts, tasks, attention events, group/private project content, delegation policy, execution grants, jobs, secrets, and trust material remain in the exact project state. The registry is append-audited and supports explicit rollback and purge:
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
agentspine source-status --host claude --cwd /current/project --json
|
|
66
|
-
agentspine source-rollback binding:ID --confirm-local-binding
|
|
67
|
-
agentspine source-purge binding:ID --confirm-local-binding
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Bindings and their provenance are context-only. They cannot create identity equivalence, roles, permissions, delegation, host trust, or self-starter rights.
|
|
71
|
-
|
|
72
|
-
## Empty and damaged state
|
|
73
|
-
|
|
74
|
-
Hook context includes a bounded `sourceResolution` report with checked scopes, counts, profile digest, project root, and the concrete empty or fail-closed reason. Indexed-memory diagnostics add counts for indexed, relevant, loaded, cache hits, cache misses, missing targets, scope omissions, path escapes, symlinks, size rejection, races, and live directory enumeration. They never include fact contents or fact paths. `agentspine doctor --host claude|codex --cwd … --json`, `agentspine source-status`, and `agentspine audit … --host … --json` expose the same report.
|
|
75
|
-
|
|
76
|
-
Live hooks never enumerate orphaned files. An operator can request the separate bounded offline diagnostic explicitly:
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
agentspine doctor --host claude --cwd /current/project --offline-memory-orphans --json
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
It reports counts only, reads no orphan content, follows no symlinks, and grants no cleanup or deletion authority.
|
|
83
|
-
|
|
84
|
-
The installed-bundle check reproduces the original zero-source failure from an AgentSpine checkout and a foreign `cwd`, repeats restart and compaction, exercises custom Claude and Codex homes, Codex fallback and nested override precedence, and proves no broad home scan, no foreign-project visibility, zero model-side MCP calls, exactly one hook set, and unchanged source bytes.
|
|
85
|
-
|
|
86
|
-
The scale acceptance creates 50,000 real unindexed files beside six indexed entries. For the fully matching scope it records five loaded facts, seven safe opens (`MEMORY.md` twice plus five targets), zero directory enumerations, and no opens, reads, hashes, or counts for the 50,000 files. The same instrumentation result is independent of the unindexed file count; wall-clock thresholds are deliberately not used as a correctness oracle.
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Local SQLite snapshot transport
|
|
2
|
-
|
|
3
|
-
AgentSpine can retain authenticated shared-memory snapshots in one operator-selected SQLite file. The adapter is local, provider-neutral, optional, and implemented with Node.js `node:sqlite`; it adds no package dependency or cloud account. SQLite commands require Node.js 22.13 or newer for the hardened read-only and extension-control options. The rest of AgentSpine continues to support the runtime range declared by the package.
|
|
4
|
-
|
|
5
|
-
The database is a transport history, not a new source of identity, policy, or canonical memory. It stores complete signed snapshots outside the scanned project and imports only the latest verified snapshot into the existing quarantine.
|
|
6
|
-
|
|
7
|
-
```mermaid
|
|
8
|
-
sequenceDiagram
|
|
9
|
-
participant O as Local owner CLI
|
|
10
|
-
participant D as Signed directory adapter
|
|
11
|
-
participant S as SQLite file outside project
|
|
12
|
-
participant R as Receiving project
|
|
13
|
-
O->>D: Build independently validated signed snapshot
|
|
14
|
-
O->>S: BEGIN IMMEDIATE
|
|
15
|
-
S->>S: Validate schema, integrity, metadata,<br/>all prior snapshots, and hash chain
|
|
16
|
-
O->>S: Append immutable revision + compare current head
|
|
17
|
-
O->>S: COMMIT revision and head atomically
|
|
18
|
-
R->>S: Read-only integrity and full-chain validation
|
|
19
|
-
R->>R: Verify snapshot + every nested signature
|
|
20
|
-
R->>R: Import claims only into quarantine
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Initialize and publish
|
|
24
|
-
|
|
25
|
-
Start with an authenticated directory adapter. Keep the SQLite file outside every scanned agent project:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
agentspine share-sqlite-init /srv/agent-memory/team-alpha \
|
|
29
|
-
--root /path/to/publisher-project \
|
|
30
|
-
--database /srv/agentspine-db/team-alpha.sqlite \
|
|
31
|
-
--confirm-local-share
|
|
32
|
-
|
|
33
|
-
agentspine share-sqlite-publish /srv/agent-memory/team-alpha \
|
|
34
|
-
--root /path/to/publisher-project \
|
|
35
|
-
--database /srv/agentspine-db/team-alpha.sqlite \
|
|
36
|
-
--id snapshot:team-alpha-2026-08-28 \
|
|
37
|
-
--confirm-local-share
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
Initialization permanently binds the database to the directory adapter's signed manifest, scope, adapter ID, signer ID, and key ID. A later command cannot silently repurpose the file for another adapter or signing identity. Initialization and every publication require an explicit local owner confirmation.
|
|
41
|
-
|
|
42
|
-
Publication uses `BEGIN IMMEDIATE`, validates the entire retained state before mutation, then appends one revision and advances the single head in the same transaction. Repeating the exact same snapshot is idempotent. Different snapshots receive monotonically increasing sequence numbers and link to the previous revision digest. There is no update or delete command for revision history.
|
|
43
|
-
|
|
44
|
-
## Inspect and pull
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
agentspine share-sqlite-inspect \
|
|
48
|
-
--root /path/to/project \
|
|
49
|
-
--database /srv/agentspine-db/team-alpha.sqlite
|
|
50
|
-
|
|
51
|
-
agentspine share-sqlite-pull \
|
|
52
|
-
--root /path/to/receiver-project \
|
|
53
|
-
--database /srv/agentspine-db/team-alpha.sqlite
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Inspection reports only transport metadata, hashes, counts, and context-only authority. Pull opens the database read-only, validates its SQLite integrity, exact application schema, authenticated manifest, every retained snapshot, the complete revision chain, and the atomic head. It then sends the latest snapshot through the normal signed importer. Received claims remain pending and invisible until a second local user review.
|
|
57
|
-
|
|
58
|
-
## Database contract
|
|
59
|
-
|
|
60
|
-
The v1 file has exactly three AgentSpine tables:
|
|
61
|
-
|
|
62
|
-
- `agentspine_meta`: one immutable binding to a signed directory manifest;
|
|
63
|
-
- `agentspine_revisions`: append-only signed-snapshot JSON plus a SHA-256 revision chain;
|
|
64
|
-
- `agentspine_head`: one transactionally advanced pointer to the newest revision.
|
|
65
|
-
|
|
66
|
-
Unexpected tables, views, triggers, indexes, schema versions, rows, broken digests, invalid JSON, malformed snapshots, signature failures, metadata changes, missing links, head mismatches, files over 128 MiB, symbolic-link or hard-link database and sidecar paths, and database paths inside the scanned project fail closed. SQLite extensions and double-quoted string literals are disabled, foreign keys and `trusted_schema=OFF` are set on every connection, defensive mode is enabled when the runtime exposes it, and SQL values use bound parameters.
|
|
67
|
-
|
|
68
|
-
The file contains repeated full snapshots so every retained revision is independently verifiable. It is intentionally capped at 1,000 revisions, 2,000 events per snapshot through the shared snapshot contract, 21 MiB per snapshot, and 128 MiB for the database and retained JSON validation budget. Operators should rotate to a new explicitly initialized file before reaching a limit; AgentSpine does not compact or delete history automatically.
|
|
69
|
-
|
|
70
|
-
## Security and authority boundary
|
|
71
|
-
|
|
72
|
-
Filesystem access to the SQLite file is the transport access-control boundary. AgentSpine requests owner-only file mode on platforms that support POSIX permissions, but operators remain responsible for directory permissions, backups, disk encryption, copying, OS-level locks, and physical device security. The adapter is not a multi-tenant database server, a replication protocol, or a remote credential store.
|
|
73
|
-
|
|
74
|
-
SQLite atomicity, integrity checks, hash chains, snapshot digests, and Ed25519 signatures detect defined corruption and origin-key mismatch. They do not establish real-world identity, freshness beyond the retained local head, truth, permission, delegation, production access, spending rights, or policy exceptions. Restoring an older but internally valid database copy cannot be distinguished without a separately retained external checkpoint.
|
|
75
|
-
|
|
76
|
-
Database initialization, publication, inspection, paths, and pull are absent from MCP and lifecycle hooks. Agents cannot select a database, write a revision, approve an import, or gain database access through AgentSpine's agent-controlled surfaces. Existing `AGENTS.md`, `CLAUDE.md`, `SOUL.md`, `MEMORY.md`, and every other discovered Markdown source remain in place and byte-for-byte unchanged.
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { readFile, stat } from "node:fs/promises";
|
|
4
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
function assert(condition, message) {
|
|
8
|
-
if (!condition) throw new Error(message);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function json(root, path) {
|
|
12
|
-
return JSON.parse(await readFile(resolve(root, path), "utf8"));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function expand(value, variable, root) {
|
|
16
|
-
assert(typeof value === "string" && value.length > 0, "host command values must be non-empty strings");
|
|
17
|
-
return value.split(`\${${variable}}`).join(root);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function validateEntrypoint(root, value) {
|
|
21
|
-
const target = resolve(value);
|
|
22
|
-
const within = relative(root, target);
|
|
23
|
-
assert(within && !within.startsWith("..") && !isAbsolute(within), "MCP entrypoint must remain inside the plugin");
|
|
24
|
-
const metadata = await stat(target);
|
|
25
|
-
assert(metadata.isFile(), "MCP entrypoint must be a regular file");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function validateHooks(root, hooks, { required, commandRoot }) {
|
|
29
|
-
assert(hooks && typeof hooks === "object" && !Array.isArray(hooks), "hook bundle is missing");
|
|
30
|
-
assert(Object.keys(hooks).every((key) => ["description", "hooks"].includes(key)), "hook bundle contains unsupported top-level metadata");
|
|
31
|
-
assert(hooks.description && typeof hooks.description === "string", "hook bundle description is missing");
|
|
32
|
-
for (const event of required) {
|
|
33
|
-
const registrations = hooks.hooks?.[event];
|
|
34
|
-
assert(Array.isArray(registrations) && registrations.length === 1, `${event} must have exactly one registration`);
|
|
35
|
-
assert(Array.isArray(registrations[0].hooks) && registrations[0].hooks.length === 1, `${event} must have exactly one hook command`);
|
|
36
|
-
const command = registrations[0].hooks[0];
|
|
37
|
-
assert(command.type === "command", `${event} must use a command hook`);
|
|
38
|
-
assert(command.command === `node "\${${commandRoot}}/src/hook.js"`, `${event} must use the bundled lifecycle adapter`);
|
|
39
|
-
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} timeout is unsafe`);
|
|
40
|
-
}
|
|
41
|
-
const extras = Object.keys(hooks.hooks || {}).filter((event) => !required.includes(event));
|
|
42
|
-
assert(extras.length === 0, `unknown hook events: ${extras.join(", ")}`);
|
|
43
|
-
return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function validateBlunHooks(root, hooks) {
|
|
47
|
-
const required = [
|
|
48
|
-
"SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse",
|
|
49
|
-
"PreCompact", "PostCompact", "Stop", "SubagentStop"
|
|
50
|
-
];
|
|
51
|
-
assert(Array.isArray(hooks), "BLUN hook bundle is missing");
|
|
52
|
-
assert(hooks.length === required.length, "BLUN must register exactly one command per lifecycle event");
|
|
53
|
-
assert(JSON.stringify(hooks.map(({ event }) => event)) === JSON.stringify(required),
|
|
54
|
-
"BLUN lifecycle events must remain complete and ordered");
|
|
55
|
-
for (const event of required) {
|
|
56
|
-
const registrations = hooks.filter((hook) => hook.event === event);
|
|
57
|
-
assert(registrations.length === 1, `${event} must have exactly one BLUN registration`);
|
|
58
|
-
const command = registrations[0];
|
|
59
|
-
assert(command.command === 'node "./src/hook.js"', `${event} must use the bundled BLUN lifecycle adapter`);
|
|
60
|
-
assert(Number.isInteger(command.timeout) && command.timeout > 0 && command.timeout <= 15, `${event} BLUN timeout is unsafe`);
|
|
61
|
-
}
|
|
62
|
-
return { events: required, commands: required.length, entrypoint: relative(root, resolve(root, "src/hook.js")) };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function initializeServer({ label, root, variable, server, version }) {
|
|
66
|
-
assert(server && typeof server === "object" && !Array.isArray(server), `${label} MCP registration is missing`);
|
|
67
|
-
assert(server.command === "node", `${label} MCP registration must use the Node.js runtime`);
|
|
68
|
-
assert(Array.isArray(server.args) && server.args.length === 1, `${label} MCP registration must name exactly one entrypoint`);
|
|
69
|
-
const command = process.execPath;
|
|
70
|
-
const args = server.args.map((value) => {
|
|
71
|
-
const expanded = expand(value, variable, root);
|
|
72
|
-
return isAbsolute(expanded) ? expanded : resolve(root, expanded);
|
|
73
|
-
});
|
|
74
|
-
assert(!args.some((value) => value.includes("${")), `${label} MCP registration contains an unresolved variable`);
|
|
75
|
-
await validateEntrypoint(root, args[0]);
|
|
76
|
-
|
|
77
|
-
return await new Promise((resolveResult, reject) => {
|
|
78
|
-
const child = spawn(command, args, {
|
|
79
|
-
cwd: root,
|
|
80
|
-
env: { ...process.env, CLAUDE_PLUGIN_ROOT: root, PLUGIN_ROOT: root },
|
|
81
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
82
|
-
});
|
|
83
|
-
let stdout = "";
|
|
84
|
-
let stderr = "";
|
|
85
|
-
let settled = false;
|
|
86
|
-
const finish = (error, value) => {
|
|
87
|
-
if (settled) return;
|
|
88
|
-
settled = true;
|
|
89
|
-
clearTimeout(timer);
|
|
90
|
-
const settle = () => {
|
|
91
|
-
if (error) reject(error);
|
|
92
|
-
else resolveResult(value);
|
|
93
|
-
};
|
|
94
|
-
if (child.exitCode !== null || child.signalCode !== null) return settle();
|
|
95
|
-
child.once("close", settle);
|
|
96
|
-
child.kill();
|
|
97
|
-
};
|
|
98
|
-
const timer = setTimeout(() => {
|
|
99
|
-
finish(new Error(`${label} MCP initialize timed out${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
100
|
-
}, 3000);
|
|
101
|
-
child.stderr.setEncoding("utf8");
|
|
102
|
-
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
103
|
-
child.stdout.setEncoding("utf8");
|
|
104
|
-
child.stdout.on("data", (chunk) => {
|
|
105
|
-
stdout += chunk;
|
|
106
|
-
const newline = stdout.indexOf("\n");
|
|
107
|
-
if (newline < 0) return;
|
|
108
|
-
try {
|
|
109
|
-
const message = JSON.parse(stdout.slice(0, newline));
|
|
110
|
-
assert(message.id === 1, `${label} MCP initialize returned the wrong request id`);
|
|
111
|
-
assert(message.result?.serverInfo?.name === "agent-spine", `${label} MCP initialize returned the wrong server identity`);
|
|
112
|
-
assert(message.result?.serverInfo?.version === version, `${label} MCP initialize returned a stale server version`);
|
|
113
|
-
finish(null, { label, server: message.result.serverInfo.name, entrypoint: relative(root, args[0]) });
|
|
114
|
-
} catch (error) {
|
|
115
|
-
finish(error);
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
child.once("error", (error) => finish(error));
|
|
119
|
-
child.once("close", (code) => {
|
|
120
|
-
if (!settled) finish(new Error(`${label} MCP server exited with ${code}${stderr ? `: ${stderr.trim()}` : ""}`));
|
|
121
|
-
});
|
|
122
|
-
// A real MCP host keeps stdio open after initialization. Closing stdin
|
|
123
|
-
// here let macOS and Windows terminate the server before its queued reply.
|
|
124
|
-
child.stdin.write(`${JSON.stringify({
|
|
125
|
-
jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18" }
|
|
126
|
-
})}\n`);
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export async function checkHosts(root = process.cwd()) {
|
|
131
|
-
root = resolve(root);
|
|
132
|
-
const [pkg, blunManifest, claudeManifest, claudeMcp, codexManifest, claudeHooks, codexHooks, hookVersion] = await Promise.all([
|
|
133
|
-
json(root, "package.json"),
|
|
134
|
-
json(root, "blun.plugin.json"),
|
|
135
|
-
json(root, ".claude-plugin/plugin.json"),
|
|
136
|
-
json(root, ".mcp.json"),
|
|
137
|
-
json(root, ".codex-plugin/plugin.json"),
|
|
138
|
-
json(root, "hooks/hooks.json"),
|
|
139
|
-
json(root, "hooks/codex.json"),
|
|
140
|
-
json(root, "hooks/version.json")
|
|
141
|
-
]);
|
|
142
|
-
assert(blunManifest.version === pkg.version && claudeManifest.version === pkg.version && codexManifest.version === pkg.version,
|
|
143
|
-
"host manifests must use the package cache version");
|
|
144
|
-
assert(hookVersion.schema === "agentspine.hook-bundle/v1" && hookVersion.version === pkg.version,
|
|
145
|
-
"hook bundle version must match the package cache version");
|
|
146
|
-
assert(hookVersion.contract === "agentspine.preflight/v2", "hook bundle preflight contract is missing");
|
|
147
|
-
assert(pkg.bin?.["agentspine-worker"] === "./src/worker.js", "package must register exactly one gateway worker entrypoint");
|
|
148
|
-
await validateEntrypoint(root, resolve(root, pkg.bin["agentspine-worker"]));
|
|
149
|
-
assert(claudeManifest.mcpServers === "./.mcp.json", "Claude manifest must explicitly reference ./.mcp.json");
|
|
150
|
-
assert(claudeManifest.hooks === undefined, "default hooks/hooks.json must not also be registered through a supplemental manifest path");
|
|
151
|
-
assert(codexManifest.hooks === undefined, "Codex manifest must omit unsupported hook registration fields");
|
|
152
|
-
assert(claudeMcp.mcpServers && Object.keys(claudeMcp.mcpServers).length === 1, "Claude MCP file must contain one mcpServers registration");
|
|
153
|
-
assert(codexManifest.mcpServers && Object.keys(codexManifest.mcpServers).length === 1, "Codex manifest must contain one MCP registration");
|
|
154
|
-
const commonEvents = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PreCompact", "PostCompact", "Stop", "SubagentStop"];
|
|
155
|
-
const claudeHookInventory = validateHooks(root, claudeHooks, {
|
|
156
|
-
required: [...commonEvents, "InstructionsLoaded"], commandRoot: "CLAUDE_PLUGIN_ROOT"
|
|
157
|
-
});
|
|
158
|
-
const codexHookInventory = validateHooks(root, codexHooks, { required: commonEvents, commandRoot: "PLUGIN_ROOT" });
|
|
159
|
-
const blunHookInventory = validateBlunHooks(root, blunManifest.hooks);
|
|
160
|
-
const registrations = await Promise.all([
|
|
161
|
-
initializeServer({ label: "blun", root, variable: "BLUN_PLUGIN_ROOT", server: blunManifest.mcpServers["agent-spine"], version: pkg.version }),
|
|
162
|
-
initializeServer({ label: "claude", root, variable: "CLAUDE_PLUGIN_ROOT", server: claudeMcp.mcpServers["agent-spine"], version: pkg.version }),
|
|
163
|
-
initializeServer({ label: "codex", root, variable: "PLUGIN_ROOT", server: codexManifest.mcpServers["agent-spine"], version: pkg.version })
|
|
164
|
-
]);
|
|
165
|
-
return {
|
|
166
|
-
ok: true, root, version: pkg.version, registrations,
|
|
167
|
-
hooks: { blun: blunHookInventory, claude: claudeHookInventory, codex: codexHookInventory },
|
|
168
|
-
hookDiscovery: {
|
|
169
|
-
blun: "plugin-manifest", claude: "default-hooks-directory", codex: "bundled-host-adapter",
|
|
170
|
-
trust: "host-user-required", liveTrustVerified: false
|
|
171
|
-
},
|
|
172
|
-
worker: { entrypoint: pkg.bin["agentspine-worker"], setsPerInstall: 1 },
|
|
173
|
-
exactlyOnce: { mcpServersPerHost: 1, hookSetsPerHost: 1, workerSetsPerInstall: 1 },
|
|
174
|
-
authority: "registration-check-only"
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async function main() {
|
|
179
|
-
const args = process.argv.slice(2);
|
|
180
|
-
let root = process.cwd();
|
|
181
|
-
let pretty = false;
|
|
182
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
183
|
-
if (args[index] === "--root") root = args[++index];
|
|
184
|
-
else if (args[index] === "--json") pretty = true;
|
|
185
|
-
else throw new Error(`unknown host-check argument: ${args[index]}`);
|
|
186
|
-
}
|
|
187
|
-
process.stdout.write(`${JSON.stringify(await checkHosts(root), null, pretty ? 2 : 0)}\n`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
|
|
191
|
-
main().catch((error) => {
|
|
192
|
-
process.stderr.write(`AgentSpine host check failed: ${error.message}\n`);
|
|
193
|
-
process.exitCode = 1;
|
|
194
|
-
});
|
|
195
|
-
}
|