blun-king-cli 9.1.564 → 9.1.566

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.
Files changed (88) hide show
  1. package/LIESMICH.txt +2 -2
  2. package/README.md +1 -1
  3. package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
  4. package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
  5. package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
  6. package/agent-spine-plugin/.mcp.json +8 -8
  7. package/agent-spine-plugin/CONTRIBUTING.md +52 -0
  8. package/agent-spine-plugin/LICENSE +186 -186
  9. package/agent-spine-plugin/README.md +406 -0
  10. package/agent-spine-plugin/SECURITY.md +47 -0
  11. package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
  12. package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
  13. package/agent-spine-plugin/bin/agentspine.js +7 -7
  14. package/agent-spine-plugin/blun.plugin.json +33 -33
  15. package/agent-spine-plugin/hooks/codex.json +47 -47
  16. package/agent-spine-plugin/hooks/hooks.json +106 -106
  17. package/agent-spine-plugin/hooks/version.json +5 -5
  18. package/agent-spine-plugin/package.json +69 -69
  19. package/agent-spine-plugin/scripts/check-hosts.js +199 -199
  20. package/agent-spine-plugin/skill/SKILL.md +76 -76
  21. package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
  22. package/agent-spine-plugin/src/cli.js +1488 -1442
  23. package/agent-spine-plugin/src/hook.js +882 -861
  24. package/agent-spine-plugin/src/index.js +94 -93
  25. package/agent-spine-plugin/src/lib/acceptance.js +333 -333
  26. package/agent-spine-plugin/src/lib/attention.js +755 -755
  27. package/agent-spine-plugin/src/lib/audit.js +351 -342
  28. package/agent-spine-plugin/src/lib/authentication.js +515 -515
  29. package/agent-spine-plugin/src/lib/briefing.js +317 -317
  30. package/agent-spine-plugin/src/lib/catalog.js +167 -167
  31. package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
  32. package/agent-spine-plugin/src/lib/context.js +154 -154
  33. package/agent-spine-plugin/src/lib/continuity.js +338 -338
  34. package/agent-spine-plugin/src/lib/coordination.js +577 -577
  35. package/agent-spine-plugin/src/lib/documents.js +217 -217
  36. package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
  37. package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
  38. package/agent-spine-plugin/src/lib/gateway-runtime.js +1623 -760
  39. package/agent-spine-plugin/src/lib/graph.js +337 -337
  40. package/agent-spine-plugin/src/lib/hook-audit.js +29 -29
  41. package/agent-spine-plugin/src/lib/https-transport.js +392 -392
  42. package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
  43. package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
  44. package/agent-spine-plugin/src/lib/learning.js +6923 -6466
  45. package/agent-spine-plugin/src/lib/object-transport.js +206 -206
  46. package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
  47. package/agent-spine-plugin/src/lib/paths.js +109 -109
  48. package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
  49. package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
  50. package/agent-spine-plugin/src/lib/preflight.js +702 -702
  51. package/agent-spine-plugin/src/lib/runtime.js +13 -13
  52. package/agent-spine-plugin/src/lib/selfstarter.js +911 -866
  53. package/agent-spine-plugin/src/lib/sharing.js +969 -969
  54. package/agent-spine-plugin/src/lib/source-roots.js +530 -498
  55. package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
  56. package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
  57. package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
  58. package/agent-spine-plugin/src/mcp.js +597 -572
  59. package/agent-spine-plugin/src/version.js +1 -1
  60. package/agent-spine-plugin/src/worker.js +210 -195
  61. package/bin/active-steer-priority-policy.cjs +24 -0
  62. package/bin/launcher-runtime.js +8 -1
  63. package/bin/mnemo-tool-agent-policy.cjs +22 -0
  64. package/bin/native-runtime-cache.cjs +76 -0
  65. package/bin/thinking-activity-status-policy.cjs +132 -0
  66. package/bin/thinking-only-guard.cjs +75 -0
  67. package/bin/tool-call-loop-policy.cjs +53 -0
  68. package/bin/turn-thinking-policy.cjs +25 -1
  69. package/blun.mjs +556 -63
  70. package/package.json +4 -1
  71. package/standard-skills/translate-native/LICENSE +21 -21
  72. package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
  73. package/standard-skills/translate-native/references/native-orthography.md +79 -79
  74. package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
  75. package/standard-skills/translate-native/references/structured-content.md +72 -72
  76. package/standard-skills/translate-native/references/translationese-review.md +77 -77
  77. package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
  78. package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
  79. package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
  80. package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
  81. package/standard-skills/translate-native/scripts/language_quality.py +377 -377
  82. package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
  83. package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
  84. package/standard-tools/language-guard/blun_language_guard.py +697 -697
  85. package/standard-tools/language-guard/check_diacritics.py +353 -353
  86. package/standard-tools/language-guard/guard_service_client.py +264 -264
  87. package/standard-tools/language-guard/language_quality.py +377 -377
  88. package/standard-tools/language-guard/translation_guard.py +916 -916
@@ -1 +1 @@
1
- export const VERSION = "0.49.0";
1
+ export const VERSION = "0.59.1";
@@ -1,195 +1,210 @@
1
- #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
3
- import { watch } from "node:fs";
4
- import { lstat, realpath } from "node:fs/promises";
5
- import { isAbsolute, join, resolve } from "node:path";
6
- import { claimGatewayWork, completeGatewayRun, deliverPrepared, failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth } from "./lib/gateway-runtime.js";
7
- import { createTelegramAdapter } from "./lib/telegram-adapter.js";
8
- import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
9
- import { loadPersonaRuntime, syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
10
- import { isMainModule } from "./lib/runtime.js";
11
-
12
- const MAX_FRAME = 64 * 1024;
13
- const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
14
-
15
- async function wakeSnapshot(directory) {
16
- return Promise.all([...WAKE_FILES].sort().map(async (name) => {
17
- try {
18
- const metadata = await lstat(join(directory, name), { bigint: true });
19
- return `${name}:${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}`;
20
- } catch (error) {
21
- if (error.code === "ENOENT") return `${name}:missing`;
22
- throw error;
23
- }
24
- })).then((entries) => entries.join("\n"));
25
- }
26
-
27
- export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath, onReady = null } = {}) {
28
- const delay = Math.max(250, Math.min(60000, Number(delayMs) || 60000));
29
- const { directory } = await loadGatewayRuntime(root);
30
- let watchPath;
31
- try { watchPath = await realpathFactory(directory); } catch { return "watch-unavailable"; }
32
- let before;
33
- try { before = await wakeSnapshot(watchPath); } catch { return "watch-unavailable"; }
34
- return new Promise((resolvePromise) => {
35
- let settled = false; let watcher;
36
- const finish = (reason) => {
37
- if (settled) return;
38
- settled = true; clearTimeout(timer);
39
- watcher?.close();
40
- resolvePromise(reason);
41
- };
42
- const timer = setTimeout(() => finish("timer"), delay);
43
- try {
44
- watcher = watchFactory(watchPath, { persistent: true }, (_eventType, filename) => {
45
- const name = filename === null ? null : String(filename);
46
- if (name === null || WAKE_FILES.has(name)) finish("event");
47
- });
48
- watcher.on?.("error", () => finish("watch-error"));
49
- Promise.resolve().then(() => onReady?.(watchPath)).then(() => wakeSnapshot(watchPath)).then((after) => {
50
- if (after !== before) finish("event");
51
- }).catch(() => finish("watch-error"));
52
- } catch { finish("watch-unavailable"); }
53
- });
54
- }
55
-
56
- function exactRunner(env) {
57
- const runner = env.AGENTSPINE_HOST_RUNNER;
58
- if (!runner || !isAbsolute(runner)) throw new Error("AGENTSPINE_HOST_RUNNER must be an absolute locally approved executable path");
59
- return resolve(runner);
60
- }
61
-
62
- async function invokeHostRunner(item, { env = process.env, timeoutMs = 300000, spawnProcess = spawn } = {}) {
63
- const executable = exactRunner(env);
64
- return new Promise((resolvePromise, reject) => {
65
- const child = spawnProcess(executable, [], {
66
- shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"],
67
- env: Object.fromEntries(["PATH", "Path", "HOME", "USERPROFILE", "SystemRoot", "SYSTEMROOT", "WINDIR",
68
- "AGENTSPINE_STATE_DIR", "CLAUDE_CONFIG_DIR", "CODEX_HOME"].filter((key) => env[key] !== undefined).map((key) => [key, env[key]]))
69
- });
70
- let stdout = Buffer.alloc(0); let stderr = Buffer.alloc(0); let settled = false;
71
- const finish = (error, value) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolvePromise(value); };
72
- child.stdout.on("data", (chunk) => { stdout = Buffer.concat([stdout, chunk]); if (stdout.length > MAX_FRAME) child.kill(); });
73
- child.stderr.on("data", (chunk) => { stderr = Buffer.concat([stderr, chunk]).subarray(0, 2048); });
74
- child.on("error", (error) => finish(error));
75
- child.on("close", (code) => {
76
- if (stdout.length > MAX_FRAME) return finish(new Error("host runner response exceeds 64 KiB"));
77
- if (code !== 0) return finish(new Error("host runner failed: " + stderr.toString("utf8").slice(0, 2048)));
78
- try { finish(null, JSON.parse(stdout.toString("utf8"))); } catch { finish(new Error("host runner returned invalid JSON")); }
79
- });
80
- const timer = setTimeout(() => { child.kill(); finish(new Error("host runner exceeded its bounded timeout")); }, timeoutMs);
81
- child.stdin.end(JSON.stringify({ schema: "agentspine.run-request/v1", item, authority: "execution-state-only" }) + "\n");
82
- });
83
- }
84
-
85
- async function hostWorkItem(root, item) {
86
- const [{ policy }, personas, channel] = await Promise.all([
87
- loadGatewayRuntime(root), loadPersonaRuntime(root), loadChannelRuntime(root)
88
- ]);
89
- const persona = personas.runtime.personas.find((entry) => entry.personaId === item.agentId && entry.status === "active");
90
- const identity = personas.policy.bindings.find((entry) => entry.id === persona?.bindingId && entry.active);
91
- if (!persona || !identity) throw new Error("claimed work lost its authenticated agent identity");
92
- const goal = item.goalId === null ? null : policy.goals.find((entry) => entry.goalId === item.goalId) || null;
93
- const event = item.channelEventId === null ? null
94
- : channel.runtime.events.find((entry) => entry.eventId === item.channelEventId) || null;
95
- if (item.channelEventId && !event) throw new Error("claimed channel work lost its exact event");
96
- return {
97
- ...structuredClone(item), host: identity.host, profileId: identity.profileId, projectRoot: root,
98
- goal: goal ? structuredClone(goal) : null,
99
- hostEnvironment: {
100
- AGENTSPINE_GATEWAY_CONTEXT: "agentspine.gateway-start/v1",
101
- AGENTSPINE_ENTITY_ID: item.agentId,
102
- AGENTSPINE_PROJECT_ID: item.projectId,
103
- ...(item.groupId === null ? {} : { AGENTSPINE_GROUP_ID: item.groupId }),
104
- ...(event ? {
105
- AGENTSPINE_CHANNEL_EVENT_ID: event.eventId,
106
- AGENTSPINE_CHANNEL_PROVIDER: event.provider
107
- } : {})
108
- },
109
- channelStart: event ? {
110
- agent_spine_scope: { entity_id: event.agentId, project_id: event.projectId, group_id: event.groupId },
111
- agent_spine_channel_event: { event_id: event.eventId, provider: event.provider },
112
- session_key: event.sessionKey,
113
- authority: "explicit-local-channel-policy"
114
- } : null
115
- };
116
- }
117
-
118
- export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = new Date(),
119
- hostRunner = invokeHostRunner, adapter = null, env = process.env } = {}) {
120
- const deliveryAdapter = adapter || createTelegramAdapter({ root, env });
121
- await syncPersonaRosterFromEnvironment({ root, env, now });
122
- const initial = await loadGatewayRuntime(root);
123
- if (!initial.policy.enabled || initial.policy.killSwitch) return { status: "stopped", processed: false };
124
- if (typeof deliveryAdapter.poll === "function") {
125
- try { await deliveryAdapter.poll(); }
126
- catch (error) {
127
- await updateGatewayHealth({ root, worker: "degraded", adapter: "failed", now });
128
- throw error;
129
- }
130
- }
131
- await reconcileGateway({ root, now });
132
- await updateGatewayHealth({ root, worker: "healthy", adapter: "healthy", now });
133
- const afterReconcile = await loadGatewayRuntime(root);
134
- const dueOutbox = afterReconcile.runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status)
135
- && new Date(item.nextAttemptAt) <= new Date(now)).sort((a, b) => a.nextAttemptAt.localeCompare(b.nextAttemptAt)
136
- || a.createdAt.localeCompare(b.createdAt) || a.outboxId.localeCompare(b.outboxId))[0] || null;
137
- if (dueOutbox) {
138
- const delivery = await deliverPrepared({ root, outboxId: dueOutbox.outboxId, adapter: deliveryAdapter, now });
139
- if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
140
- eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
141
- deliveryReceiptId: delivery.receipt.id, now });
142
- return { status: delivery.outbox.status, processed: true, outboxId: delivery.outbox.outboxId,
143
- recoveredDelivery: true };
144
- }
145
- const claim = await claimGatewayWork({ root, workerId, now });
146
- if (!claim.item) return { status: claim.reason, processed: false };
147
- let result;
148
- try { result = await hostRunner(await hostWorkItem(root, claim.item), { env }); }
149
- catch (error) {
150
- const failed = await failGatewayRun({ root, queueId: claim.item.queueId, workerId,
151
- error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now });
152
- return { status: failed.item.status, processed: true, queueId: failed.item.queueId, retryAt: failed.item.availableAt };
153
- }
154
- const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId, result, now });
155
- if (!completed.outbox) return { status: completed.item.status, processed: true, queueId: completed.item.queueId };
156
- const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
157
- adapter: deliveryAdapter, now });
158
- if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
159
- eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
160
- deliveryReceiptId: delivery.receipt.id, now });
161
- return { status: delivery.outbox.status, processed: true, queueId: completed.item.queueId, outboxId: delivery.outbox.outboxId };
162
- }
163
-
164
- export async function runWorker({ root = process.cwd(), once = false, env = process.env,
165
- waitForWake = waitForGatewayWake } = {}) {
166
- const workerId = "gateway-worker:" + process.pid;
167
- do {
168
- const result = await runWorkerTick({ root, workerId, env });
169
- if (once) return result;
170
- const { policy, runtime } = await loadGatewayRuntime(root);
171
- if (!policy.enabled || policy.killSwitch) return { status: "stopped", processed: false };
172
- const pending = [
173
- ...runtime.queue.filter((item) => item.status === "pending").map((item) => new Date(item.availableAt).getTime()),
174
- ...runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status))
175
- .map((item) => new Date(item.nextAttemptAt).getTime())
176
- ];
177
- const delay = pending.length ? Math.max(250, Math.min(60000, Math.min(...pending) - Date.now())) : 60000;
178
- await waitForWake(root, delay);
179
- } while (true);
180
- }
181
-
182
- if (isMainModule(import.meta.url)) {
183
- const args = process.argv.slice(2); const once = args.includes("--once");
184
- const rootIndex = args.indexOf("--root");
185
- if (rootIndex >= 0 && (!args[rootIndex + 1] || args[rootIndex + 1].startsWith("--"))) throw new Error("--root requires a path");
186
- const root = rootIndex >= 0 ? args[rootIndex + 1] : process.cwd();
187
- const rosterIndex = args.indexOf("--persona-roster");
188
- if (rosterIndex >= 0 && (!args[rosterIndex + 1] || args[rosterIndex + 1].startsWith("--"))) {
189
- throw new Error("--persona-roster requires a path");
190
- }
191
- const env = { ...process.env };
192
- if (rosterIndex >= 0) env.AGENTSPINE_PERSONA_ROSTER_FILE = args[rosterIndex + 1];
193
- runWorker({ root, once, env }).then((result) => process.stdout.write(JSON.stringify(result) + "\n"))
194
- .catch((error) => { process.stderr.write("AgentSpine worker: " + String(error.message).slice(0, 2048) + "\n"); process.exitCode = 1; });
195
- }
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { watch } from "node:fs";
4
+ import { lstat, realpath } from "node:fs/promises";
5
+ import { isAbsolute, join, resolve } from "node:path";
6
+ import {
7
+ claimGatewayWork, completeGatewayRun, deliverPrepared, executionAttemptForStep,
8
+ failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth
9
+ } from "./lib/gateway-runtime.js";
10
+ import { createTelegramAdapter } from "./lib/telegram-adapter.js";
11
+ import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
12
+ import { loadPersonaRuntime, syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
13
+ import { isMainModule } from "./lib/runtime.js";
14
+
15
+ const MAX_FRAME = 64 * 1024;
16
+ const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
17
+
18
+ async function wakeSnapshot(directory) {
19
+ return Promise.all([...WAKE_FILES].sort().map(async (name) => {
20
+ try {
21
+ const metadata = await lstat(join(directory, name), { bigint: true });
22
+ return `${name}:${metadata.dev}:${metadata.ino}:${metadata.size}:${metadata.mtimeNs}`;
23
+ } catch (error) {
24
+ if (error.code === "ENOENT") return `${name}:missing`;
25
+ throw error;
26
+ }
27
+ })).then((entries) => entries.join("\n"));
28
+ }
29
+
30
+ export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath, onReady = null } = {}) {
31
+ const delay = Math.max(250, Math.min(60000, Number(delayMs) || 60000));
32
+ const { directory } = await loadGatewayRuntime(root);
33
+ let watchPath;
34
+ try { watchPath = await realpathFactory(directory); } catch { return "watch-unavailable"; }
35
+ let before;
36
+ try { before = await wakeSnapshot(watchPath); } catch { return "watch-unavailable"; }
37
+ return new Promise((resolvePromise) => {
38
+ let settled = false; let watcher;
39
+ const finish = (reason) => {
40
+ if (settled) return;
41
+ settled = true; clearTimeout(timer);
42
+ watcher?.close();
43
+ resolvePromise(reason);
44
+ };
45
+ const timer = setTimeout(() => finish("timer"), delay);
46
+ try {
47
+ watcher = watchFactory(watchPath, { persistent: true }, (_eventType, filename) => {
48
+ const name = filename === null ? null : String(filename);
49
+ if (name === null || WAKE_FILES.has(name)) finish("event");
50
+ });
51
+ watcher.on?.("error", () => finish("watch-error"));
52
+ Promise.resolve().then(() => onReady?.(watchPath)).then(() => wakeSnapshot(watchPath)).then((after) => {
53
+ if (after !== before) finish("event");
54
+ }).catch(() => finish("watch-error"));
55
+ } catch { finish("watch-unavailable"); }
56
+ });
57
+ }
58
+
59
+ function exactRunner(env) {
60
+ const runner = env.AGENTSPINE_HOST_RUNNER;
61
+ if (!runner || !isAbsolute(runner)) throw new Error("AGENTSPINE_HOST_RUNNER must be an absolute locally approved executable path");
62
+ return resolve(runner);
63
+ }
64
+
65
+ async function invokeHostRunner(item, { env = process.env, timeoutMs = 300000, spawnProcess = spawn } = {}) {
66
+ const executable = exactRunner(env);
67
+ return new Promise((resolvePromise, reject) => {
68
+ const child = spawnProcess(executable, [], {
69
+ shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"],
70
+ env: Object.fromEntries(["PATH", "Path", "HOME", "USERPROFILE", "SystemRoot", "SYSTEMROOT", "WINDIR",
71
+ "AGENTSPINE_STATE_DIR", "CLAUDE_CONFIG_DIR", "CODEX_HOME"].filter((key) => env[key] !== undefined).map((key) => [key, env[key]]))
72
+ });
73
+ let stdout = Buffer.alloc(0); let stderr = Buffer.alloc(0); let settled = false;
74
+ const finish = (error, value) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolvePromise(value); };
75
+ child.stdout.on("data", (chunk) => { stdout = Buffer.concat([stdout, chunk]); if (stdout.length > MAX_FRAME) child.kill(); });
76
+ child.stderr.on("data", (chunk) => { stderr = Buffer.concat([stderr, chunk]).subarray(0, 2048); });
77
+ child.on("error", (error) => finish(error));
78
+ child.on("close", (code) => {
79
+ if (stdout.length > MAX_FRAME) return finish(new Error("host runner response exceeds 64 KiB"));
80
+ if (code !== 0) return finish(new Error("host runner failed: " + stderr.toString("utf8").slice(0, 2048)));
81
+ try { finish(null, JSON.parse(stdout.toString("utf8"))); } catch { finish(new Error("host runner returned invalid JSON")); }
82
+ });
83
+ const timer = setTimeout(() => { child.kill(); finish(new Error("host runner exceeded its bounded timeout")); }, timeoutMs);
84
+ child.stdin.end(JSON.stringify({ schema: "agentspine.run-request/v1", item, authority: "execution-state-only" }) + "\n");
85
+ });
86
+ }
87
+
88
+ async function hostWorkItem(root, item) {
89
+ const [{ policy }, personas, channel] = await Promise.all([
90
+ loadGatewayRuntime(root), loadPersonaRuntime(root), loadChannelRuntime(root)
91
+ ]);
92
+ const persona = personas.runtime.personas.find((entry) => entry.personaId === item.agentId && entry.status === "active");
93
+ const identity = personas.policy.bindings.find((entry) => entry.id === persona?.bindingId && entry.active);
94
+ if (!persona || !identity) throw new Error("claimed work lost its authenticated agent identity");
95
+ const goal = item.goalId === null ? null : policy.goals.find((entry) => entry.goalId === item.goalId) || null;
96
+ const goalStep = item.goalStepId === null || item.goalStepId === undefined ? null
97
+ : goal?.plan?.steps.find((entry) => entry.stepId === item.goalStepId) || null;
98
+ if (item.goalStepId && !goalStep) throw new Error("claimed work lost its exact goal-plan step");
99
+ const executionAttempt = executionAttemptForStep(goalStep);
100
+ const event = item.channelEventId === null ? null
101
+ : channel.runtime.events.find((entry) => entry.eventId === item.channelEventId) || null;
102
+ if (item.channelEventId && !event) throw new Error("claimed channel work lost its exact event");
103
+ return {
104
+ ...structuredClone(item), host: identity.host, profileId: identity.profileId, projectRoot: root,
105
+ goal: goal ? structuredClone(goal) : null,
106
+ goalStep: goalStep ? { ...structuredClone(goalStep),
107
+ ...(executionAttempt === null ? {} : { executionAttempt }) } : null,
108
+ hostEnvironment: {
109
+ AGENTSPINE_GATEWAY_CONTEXT: "agentspine.gateway-start/v1",
110
+ AGENTSPINE_ENTITY_ID: item.agentId,
111
+ AGENTSPINE_PROJECT_ID: item.projectId,
112
+ ...(item.groupId === null ? {} : { AGENTSPINE_GROUP_ID: item.groupId }),
113
+ ...(event ? {
114
+ AGENTSPINE_CHANNEL_EVENT_ID: event.eventId,
115
+ AGENTSPINE_CHANNEL_PROVIDER: event.provider
116
+ } : {})
117
+ },
118
+ channelStart: event ? {
119
+ agent_spine_scope: { entity_id: event.agentId, project_id: event.projectId, group_id: event.groupId },
120
+ agent_spine_channel_event: { event_id: event.eventId, provider: event.provider },
121
+ session_key: event.sessionKey,
122
+ authority: "explicit-local-channel-policy"
123
+ } : null
124
+ };
125
+ }
126
+
127
+ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = new Date(),
128
+ hostRunner = invokeHostRunner, adapter = null, env = process.env } = {}) {
129
+ const deliveryAdapter = adapter || createTelegramAdapter({ root, env });
130
+ await syncPersonaRosterFromEnvironment({ root, env, now });
131
+ const initial = await loadGatewayRuntime(root);
132
+ if (!initial.policy.enabled || initial.policy.killSwitch) return { status: "stopped", processed: false };
133
+ if (typeof deliveryAdapter.poll === "function") {
134
+ try { await deliveryAdapter.poll(); }
135
+ catch (error) {
136
+ await updateGatewayHealth({ root, worker: "degraded", adapter: "failed", now });
137
+ throw error;
138
+ }
139
+ }
140
+ await reconcileGateway({ root, now });
141
+ await updateGatewayHealth({ root, worker: "healthy", adapter: "healthy", now });
142
+ const afterReconcile = await loadGatewayRuntime(root);
143
+ const dueOutbox = afterReconcile.runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status)
144
+ && new Date(item.nextAttemptAt) <= new Date(now)).sort((a, b) => a.nextAttemptAt.localeCompare(b.nextAttemptAt)
145
+ || a.createdAt.localeCompare(b.createdAt) || a.outboxId.localeCompare(b.outboxId))[0] || null;
146
+ if (dueOutbox) {
147
+ const delivery = await deliverPrepared({ root, outboxId: dueOutbox.outboxId, adapter: deliveryAdapter, now });
148
+ if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
149
+ eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
150
+ deliveryReceiptId: delivery.receipt.id, now });
151
+ return { status: delivery.outbox.status, processed: true, outboxId: delivery.outbox.outboxId,
152
+ recoveredDelivery: true };
153
+ }
154
+ const claim = await claimGatewayWork({ root, workerId, now });
155
+ if (!claim.item) return { status: claim.reason, processed: false };
156
+ let result;
157
+ try { result = await hostRunner(await hostWorkItem(root, claim.item), { env }); }
158
+ catch (error) {
159
+ const failed = await failGatewayRun({ root, queueId: claim.item.queueId, workerId,
160
+ error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now });
161
+ return { status: failed.item.status, processed: true, queueId: failed.item.queueId, retryAt: failed.item.availableAt };
162
+ }
163
+ const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId, result, now });
164
+ if (!completed.outbox) return {
165
+ status: completed.clarification ? "needs-clarification"
166
+ : completed.exploration ? "exploring" : completed.item.status,
167
+ processed: true, queueId: completed.item.queueId,
168
+ ...(completed.clarification ? { clarification: completed.clarification } : {}),
169
+ ...(completed.exploration ? { exploration: completed.exploration } : {})
170
+ };
171
+ const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
172
+ adapter: deliveryAdapter, now });
173
+ if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
174
+ eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
175
+ deliveryReceiptId: delivery.receipt.id, now });
176
+ return { status: delivery.outbox.status, processed: true, queueId: completed.item.queueId, outboxId: delivery.outbox.outboxId };
177
+ }
178
+
179
+ export async function runWorker({ root = process.cwd(), once = false, env = process.env,
180
+ waitForWake = waitForGatewayWake } = {}) {
181
+ const workerId = "gateway-worker:" + process.pid;
182
+ do {
183
+ const result = await runWorkerTick({ root, workerId, env });
184
+ if (once) return result;
185
+ const { policy, runtime } = await loadGatewayRuntime(root);
186
+ if (!policy.enabled || policy.killSwitch) return { status: "stopped", processed: false };
187
+ const pending = [
188
+ ...runtime.queue.filter((item) => item.status === "pending").map((item) => new Date(item.availableAt).getTime()),
189
+ ...runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status))
190
+ .map((item) => new Date(item.nextAttemptAt).getTime())
191
+ ];
192
+ const delay = pending.length ? Math.max(250, Math.min(60000, Math.min(...pending) - Date.now())) : 60000;
193
+ await waitForWake(root, delay);
194
+ } while (true);
195
+ }
196
+
197
+ if (isMainModule(import.meta.url)) {
198
+ const args = process.argv.slice(2); const once = args.includes("--once");
199
+ const rootIndex = args.indexOf("--root");
200
+ if (rootIndex >= 0 && (!args[rootIndex + 1] || args[rootIndex + 1].startsWith("--"))) throw new Error("--root requires a path");
201
+ const root = rootIndex >= 0 ? args[rootIndex + 1] : process.cwd();
202
+ const rosterIndex = args.indexOf("--persona-roster");
203
+ if (rosterIndex >= 0 && (!args[rosterIndex + 1] || args[rosterIndex + 1].startsWith("--"))) {
204
+ throw new Error("--persona-roster requires a path");
205
+ }
206
+ const env = { ...process.env };
207
+ if (rosterIndex >= 0) env.AGENTSPINE_PERSONA_ROSTER_FILE = args[rosterIndex + 1];
208
+ runWorker({ root, once, env }).then((result) => process.stdout.write(JSON.stringify(result) + "\n"))
209
+ .catch((error) => { process.stderr.write("AgentSpine worker: " + String(error.message).slice(0, 2048) + "\n"); process.exitCode = 1; });
210
+ }
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const ACTIVE_STEER_INTERRUPT_CODE = 'BLUN_ACTIVE_STEER_INTERRUPT';
4
+ const ACTIVE_STEER_PRIORITY_REMINDER = [
5
+ 'A newer user steer is the highest-priority next action.',
6
+ 'Act on it now with the available tools. Do not continue an idle prompt or ask for another task.',
7
+ ].join(' ');
8
+
9
+ function createActiveSteerInterrupt() {
10
+ const error = new Error('Active model request superseded by a newer user steer');
11
+ error.code = ACTIVE_STEER_INTERRUPT_CODE;
12
+ return error;
13
+ }
14
+
15
+ function isActiveSteerInterrupt(error) {
16
+ return error?.code === ACTIVE_STEER_INTERRUPT_CODE;
17
+ }
18
+
19
+ module.exports = {
20
+ ACTIVE_STEER_INTERRUPT_CODE,
21
+ ACTIVE_STEER_PRIORITY_REMINDER,
22
+ createActiveSteerInterrupt,
23
+ isActiveSteerInterrupt,
24
+ };
@@ -31,7 +31,10 @@ const {
31
31
  const { CORE_LOADED_MESSAGE } = require('./core-bootstrap');
32
32
  const { prepareManagedNodeRuntime } = require('./node-runtime');
33
33
  const { repairConfiguredNativeModules } = require('./native-module-repair');
34
- const { startMnemoConnectHeartbeat } = require('./mnemo-connect-heartbeat.cjs');
34
+ const {
35
+ resolveMnemoProfileConnectConfig,
36
+ startMnemoConnectHeartbeat,
37
+ } = require('./mnemo-connect-heartbeat.cjs');
35
38
  const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
36
39
  const { acquireSharedRuntimeLease } = require('./update-lease');
37
40
  const { runExplicitUpdate, runUpdateNotice } = require('./update-notice');
@@ -539,10 +542,14 @@ async function runLauncher(options = {}) {
539
542
  env.BLUN_SHARED_HOME = privatePaths.sharedHome;
540
543
  env.BLUN_LOG_HOME = privatePaths.sharedHome;
541
544
  env.BLUN_PROFILE = PROFILE.profileName;
545
+ const mnemoProfileConfig = resolveMnemoProfileConnectConfig(path.join(profilePaths.home, 'mcp.json'));
546
+ if (mnemoProfileConfig?.agentName) env.MNEMO_AGENT = mnemoProfileConfig.agentName;
547
+ if (mnemoProfileConfig?.baseUrl) env.BLUN_MNEMO_HUB_URL = mnemoProfileConfig.baseUrl;
542
548
  const mnemoConnect = startMnemoConnectHeartbeat({
543
549
  env,
544
550
  profileName: PROFILE.profileName,
545
551
  mcpConfigPath: path.join(profilePaths.home, 'mcp.json'),
552
+ profileConfig: mnemoProfileConfig,
546
553
  version: readPackageVersion(),
547
554
  });
548
555
  try {
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const MNEMO_TOOL_PREFIX = 'mcp__mnemo__';
4
+
5
+ function configuredMnemoAgent(env = process.env) {
6
+ const value = typeof env.MNEMO_AGENT === 'string' ? env.MNEMO_AGENT.trim() : '';
7
+ return value && !/[\u0000-\u001f\u007f]/u.test(value) && value.length <= 128 ? value : null;
8
+ }
9
+
10
+ function applyMnemoAgentToToolArgs(toolName, args, env = process.env) {
11
+ if (typeof toolName !== 'string' || !toolName.toLowerCase().startsWith(MNEMO_TOOL_PREFIX)) return args;
12
+ if (!args || typeof args !== 'object' || Array.isArray(args) || !Object.hasOwn(args, 'agent_name')) return args;
13
+ const agentName = configuredMnemoAgent(env);
14
+ if (!agentName || args.agent_name === agentName) return args;
15
+ return { ...args, agent_name: agentName };
16
+ }
17
+
18
+ module.exports = {
19
+ MNEMO_TOOL_PREFIX,
20
+ applyMnemoAgentToToolArgs,
21
+ configuredMnemoAgent,
22
+ };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ function isPathInside(root, candidate) {
9
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
10
+ return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
11
+ }
12
+
13
+ function cacheBase(options = {}) {
14
+ if (options.cacheBase) return path.resolve(options.cacheBase);
15
+ const env = options.env || process.env;
16
+ const platform = options.platform || process.platform;
17
+ if (env.BLUN_CACHE_DIR) return path.resolve(env.BLUN_CACHE_DIR);
18
+ if (platform === 'win32') {
19
+ return path.resolve(env.LOCALAPPDATA || path.join(options.homeDir || os.homedir(), 'AppData', 'Local'), 'blun');
20
+ }
21
+ if (platform === 'darwin') return path.join(options.homeDir || os.homedir(), 'Library', 'Caches', 'blun');
22
+ return path.join(env.XDG_CACHE_HOME || path.join(options.homeDir || os.homedir(), '.cache'), 'blun');
23
+ }
24
+
25
+ function sha256(bytes) {
26
+ return crypto.createHash('sha256').update(bytes).digest('hex');
27
+ }
28
+
29
+ function ensureCachedFile(sourcePath, targetPath, expectedHash) {
30
+ try {
31
+ if (sha256(fs.readFileSync(targetPath)) === expectedHash) return targetPath;
32
+ } catch {}
33
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
34
+ const temporary = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
35
+ fs.copyFileSync(sourcePath, temporary);
36
+ try {
37
+ fs.renameSync(temporary, targetPath);
38
+ } catch (error) {
39
+ try {
40
+ if (sha256(fs.readFileSync(targetPath)) === expectedHash) {
41
+ fs.rmSync(temporary, { force: true });
42
+ return targetPath;
43
+ }
44
+ } catch {}
45
+ fs.rmSync(temporary, { force: true });
46
+ throw error;
47
+ }
48
+ return targetPath;
49
+ }
50
+
51
+ function resolveNativeRuntimePath(requestPath, options = {}) {
52
+ if (typeof requestPath !== 'string' || path.extname(requestPath).toLowerCase() !== '.node') return requestPath;
53
+ const packageRoot = options.packageRoot && path.resolve(options.packageRoot);
54
+ const sourcePath = path.resolve(requestPath);
55
+ if (!packageRoot || !isPathInside(packageRoot, sourcePath)) return requestPath;
56
+ let bytes;
57
+ try {
58
+ bytes = fs.readFileSync(sourcePath);
59
+ } catch {
60
+ return requestPath;
61
+ }
62
+ const hash = sha256(bytes);
63
+ const relativePath = path.relative(packageRoot, sourcePath);
64
+ const relativeHash = sha256(Buffer.from(relativePath.replaceAll('\\', '/'))).slice(0, 16);
65
+ const targetPath = path.join(cacheBase(options), 'native-runtime', hash, `${relativeHash}-${path.basename(sourcePath)}`);
66
+ try {
67
+ return ensureCachedFile(sourcePath, targetPath, hash);
68
+ } catch {
69
+ return requestPath;
70
+ }
71
+ }
72
+
73
+ module.exports = {
74
+ isPathInside,
75
+ resolveNativeRuntimePath,
76
+ };