blun-king-cli 9.1.563 → 9.1.565

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 (85) hide show
  1. package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
  2. package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
  3. package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
  4. package/agent-spine-plugin/.mcp.json +8 -8
  5. package/agent-spine-plugin/CONTRIBUTING.md +52 -0
  6. package/agent-spine-plugin/LICENSE +186 -186
  7. package/agent-spine-plugin/README.md +394 -0
  8. package/agent-spine-plugin/SECURITY.md +47 -0
  9. package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
  10. package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
  11. package/agent-spine-plugin/bin/agentspine.js +7 -7
  12. package/agent-spine-plugin/blun.plugin.json +33 -33
  13. package/agent-spine-plugin/hooks/codex.json +47 -47
  14. package/agent-spine-plugin/hooks/hooks.json +106 -106
  15. package/agent-spine-plugin/hooks/version.json +5 -5
  16. package/agent-spine-plugin/package.json +69 -69
  17. package/agent-spine-plugin/scripts/check-hosts.js +199 -199
  18. package/agent-spine-plugin/skill/SKILL.md +76 -76
  19. package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
  20. package/agent-spine-plugin/src/cli.js +1488 -1442
  21. package/agent-spine-plugin/src/hook.js +886 -812
  22. package/agent-spine-plugin/src/index.js +93 -93
  23. package/agent-spine-plugin/src/lib/acceptance.js +333 -333
  24. package/agent-spine-plugin/src/lib/attention.js +755 -755
  25. package/agent-spine-plugin/src/lib/audit.js +351 -342
  26. package/agent-spine-plugin/src/lib/authentication.js +515 -515
  27. package/agent-spine-plugin/src/lib/briefing.js +317 -317
  28. package/agent-spine-plugin/src/lib/catalog.js +167 -167
  29. package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
  30. package/agent-spine-plugin/src/lib/context.js +154 -154
  31. package/agent-spine-plugin/src/lib/continuity.js +338 -338
  32. package/agent-spine-plugin/src/lib/coordination.js +577 -577
  33. package/agent-spine-plugin/src/lib/documents.js +217 -217
  34. package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
  35. package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
  36. package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
  37. package/agent-spine-plugin/src/lib/graph.js +337 -337
  38. package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
  39. package/agent-spine-plugin/src/lib/https-transport.js +392 -392
  40. package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
  41. package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
  42. package/agent-spine-plugin/src/lib/learning.js +6923 -6466
  43. package/agent-spine-plugin/src/lib/object-transport.js +206 -206
  44. package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
  45. package/agent-spine-plugin/src/lib/paths.js +109 -109
  46. package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
  47. package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
  48. package/agent-spine-plugin/src/lib/preflight.js +702 -702
  49. package/agent-spine-plugin/src/lib/runtime.js +13 -13
  50. package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
  51. package/agent-spine-plugin/src/lib/sharing.js +969 -969
  52. package/agent-spine-plugin/src/lib/source-roots.js +529 -482
  53. package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
  54. package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
  55. package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
  56. package/agent-spine-plugin/src/mcp.js +597 -572
  57. package/agent-spine-plugin/src/version.js +1 -1
  58. package/agent-spine-plugin/src/worker.js +202 -195
  59. package/bin/active-steer-priority-policy.cjs +24 -0
  60. package/bin/launcher-runtime.js +8 -1
  61. package/bin/mnemo-tool-agent-policy.cjs +22 -0
  62. package/bin/thinking-activity-status-policy.cjs +132 -0
  63. package/bin/thinking-only-guard.cjs +75 -0
  64. package/bin/tool-call-loop-policy.cjs +53 -0
  65. package/bin/turn-thinking-policy.cjs +25 -1
  66. package/blun.mjs +554 -64
  67. package/package.json +4 -1
  68. package/standard-skills/translate-native/LICENSE +21 -21
  69. package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
  70. package/standard-skills/translate-native/references/native-orthography.md +79 -79
  71. package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
  72. package/standard-skills/translate-native/references/structured-content.md +72 -72
  73. package/standard-skills/translate-native/references/translationese-review.md +77 -77
  74. package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
  75. package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
  76. package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
  77. package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
  78. package/standard-skills/translate-native/scripts/language_quality.py +377 -377
  79. package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
  80. package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
  81. package/standard-tools/language-guard/blun_language_guard.py +697 -697
  82. package/standard-tools/language-guard/check_diacritics.py +353 -353
  83. package/standard-tools/language-guard/guard_service_client.py +264 -264
  84. package/standard-tools/language-guard/language_quality.py +377 -377
  85. package/standard-tools/language-guard/translation_guard.py +916 -916
@@ -1 +1 @@
1
- export const VERSION = "0.49.0";
1
+ export const VERSION = "0.54.0";
@@ -1,195 +1,202 @@
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 { 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 goalStep = item.goalStepId === null || item.goalStepId === undefined ? null
94
+ : goal?.plan?.steps.find((entry) => entry.stepId === item.goalStepId) || null;
95
+ if (item.goalStepId && !goalStep) throw new Error("claimed work lost its exact goal-plan step");
96
+ const event = item.channelEventId === null ? null
97
+ : channel.runtime.events.find((entry) => entry.eventId === item.channelEventId) || null;
98
+ if (item.channelEventId && !event) throw new Error("claimed channel work lost its exact event");
99
+ return {
100
+ ...structuredClone(item), host: identity.host, profileId: identity.profileId, projectRoot: root,
101
+ goal: goal ? structuredClone(goal) : null, goalStep: goalStep ? structuredClone(goalStep) : null,
102
+ hostEnvironment: {
103
+ AGENTSPINE_GATEWAY_CONTEXT: "agentspine.gateway-start/v1",
104
+ AGENTSPINE_ENTITY_ID: item.agentId,
105
+ AGENTSPINE_PROJECT_ID: item.projectId,
106
+ ...(item.groupId === null ? {} : { AGENTSPINE_GROUP_ID: item.groupId }),
107
+ ...(event ? {
108
+ AGENTSPINE_CHANNEL_EVENT_ID: event.eventId,
109
+ AGENTSPINE_CHANNEL_PROVIDER: event.provider
110
+ } : {})
111
+ },
112
+ channelStart: event ? {
113
+ agent_spine_scope: { entity_id: event.agentId, project_id: event.projectId, group_id: event.groupId },
114
+ agent_spine_channel_event: { event_id: event.eventId, provider: event.provider },
115
+ session_key: event.sessionKey,
116
+ authority: "explicit-local-channel-policy"
117
+ } : null
118
+ };
119
+ }
120
+
121
+ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-worker:local", now = new Date(),
122
+ hostRunner = invokeHostRunner, adapter = null, env = process.env } = {}) {
123
+ const deliveryAdapter = adapter || createTelegramAdapter({ root, env });
124
+ await syncPersonaRosterFromEnvironment({ root, env, now });
125
+ const initial = await loadGatewayRuntime(root);
126
+ if (!initial.policy.enabled || initial.policy.killSwitch) return { status: "stopped", processed: false };
127
+ if (typeof deliveryAdapter.poll === "function") {
128
+ try { await deliveryAdapter.poll(); }
129
+ catch (error) {
130
+ await updateGatewayHealth({ root, worker: "degraded", adapter: "failed", now });
131
+ throw error;
132
+ }
133
+ }
134
+ await reconcileGateway({ root, now });
135
+ await updateGatewayHealth({ root, worker: "healthy", adapter: "healthy", now });
136
+ const afterReconcile = await loadGatewayRuntime(root);
137
+ const dueOutbox = afterReconcile.runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status)
138
+ && new Date(item.nextAttemptAt) <= new Date(now)).sort((a, b) => a.nextAttemptAt.localeCompare(b.nextAttemptAt)
139
+ || a.createdAt.localeCompare(b.createdAt) || a.outboxId.localeCompare(b.outboxId))[0] || null;
140
+ if (dueOutbox) {
141
+ const delivery = await deliverPrepared({ root, outboxId: dueOutbox.outboxId, adapter: deliveryAdapter, now });
142
+ if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
143
+ eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
144
+ deliveryReceiptId: delivery.receipt.id, now });
145
+ return { status: delivery.outbox.status, processed: true, outboxId: delivery.outbox.outboxId,
146
+ recoveredDelivery: true };
147
+ }
148
+ const claim = await claimGatewayWork({ root, workerId, now });
149
+ if (!claim.item) return { status: claim.reason, processed: false };
150
+ let result;
151
+ try { result = await hostRunner(await hostWorkItem(root, claim.item), { env }); }
152
+ catch (error) {
153
+ const failed = await failGatewayRun({ root, queueId: claim.item.queueId, workerId,
154
+ error: "Host runtime unavailable: " + String(error.message).slice(0, 400), now });
155
+ return { status: failed.item.status, processed: true, queueId: failed.item.queueId, retryAt: failed.item.availableAt };
156
+ }
157
+ const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId, result, now });
158
+ if (!completed.outbox) return {
159
+ status: completed.clarification ? "needs-clarification" : completed.item.status,
160
+ processed: true, queueId: completed.item.queueId,
161
+ ...(completed.clarification ? { clarification: completed.clarification } : {})
162
+ };
163
+ const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
164
+ adapter: deliveryAdapter, now });
165
+ if (delivery.outbox.status === "delivered") await acknowledgeChannelDelivery({ root,
166
+ eventId: delivery.outbox.eventId, bindingId: delivery.outbox.bindingId,
167
+ deliveryReceiptId: delivery.receipt.id, now });
168
+ return { status: delivery.outbox.status, processed: true, queueId: completed.item.queueId, outboxId: delivery.outbox.outboxId };
169
+ }
170
+
171
+ export async function runWorker({ root = process.cwd(), once = false, env = process.env,
172
+ waitForWake = waitForGatewayWake } = {}) {
173
+ const workerId = "gateway-worker:" + process.pid;
174
+ do {
175
+ const result = await runWorkerTick({ root, workerId, env });
176
+ if (once) return result;
177
+ const { policy, runtime } = await loadGatewayRuntime(root);
178
+ if (!policy.enabled || policy.killSwitch) return { status: "stopped", processed: false };
179
+ const pending = [
180
+ ...runtime.queue.filter((item) => item.status === "pending").map((item) => new Date(item.availableAt).getTime()),
181
+ ...runtime.outbox.filter((item) => ["prepared", "failed"].includes(item.status))
182
+ .map((item) => new Date(item.nextAttemptAt).getTime())
183
+ ];
184
+ const delay = pending.length ? Math.max(250, Math.min(60000, Math.min(...pending) - Date.now())) : 60000;
185
+ await waitForWake(root, delay);
186
+ } while (true);
187
+ }
188
+
189
+ if (isMainModule(import.meta.url)) {
190
+ const args = process.argv.slice(2); const once = args.includes("--once");
191
+ const rootIndex = args.indexOf("--root");
192
+ if (rootIndex >= 0 && (!args[rootIndex + 1] || args[rootIndex + 1].startsWith("--"))) throw new Error("--root requires a path");
193
+ const root = rootIndex >= 0 ? args[rootIndex + 1] : process.cwd();
194
+ const rosterIndex = args.indexOf("--persona-roster");
195
+ if (rosterIndex >= 0 && (!args[rosterIndex + 1] || args[rosterIndex + 1].startsWith("--"))) {
196
+ throw new Error("--persona-roster requires a path");
197
+ }
198
+ const env = { ...process.env };
199
+ if (rosterIndex >= 0) env.AGENTSPINE_PERSONA_ROSTER_FILE = args[rosterIndex + 1];
200
+ runWorker({ root, once, env }).then((result) => process.stdout.write(JSON.stringify(result) + "\n"))
201
+ .catch((error) => { process.stderr.write("AgentSpine worker: " + String(error.message).slice(0, 2048) + "\n"); process.exitCode = 1; });
202
+ }
@@ -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,132 @@
1
+ 'use strict';
2
+
3
+ const ACTIVITY_CHANGE_INTERVAL_MS = 3_000;
4
+
5
+ const THINKING_ACTIVITY_WORDS = Object.freeze({
6
+ Thinking: Object.freeze([
7
+ 'Thinkering', 'Reasoning', 'Pondering', 'Analyzing', 'Exploring',
8
+ 'Connecting', 'Unraveling', 'Imagining', 'Envisioning', 'Strategizing',
9
+ 'Deciphering', 'Framing', 'Ideating', 'Synthesizing', 'Reflecting',
10
+ ]),
11
+ Planning: Object.freeze([
12
+ 'Architecting', 'Blueprinting', 'Structuring', 'Orchestrating', 'Sequencing',
13
+ 'Scoping', 'Prioritizing', 'Coordinating', 'Decomposing', 'Aligning',
14
+ 'Roadmapping', 'Modeling', 'Designing', 'Drafting', 'Preparing',
15
+ ]),
16
+ Researching: Object.freeze([
17
+ 'Searching', 'Browsing', 'Discovering', 'Investigating', 'Examining',
18
+ 'Inspecting', 'Tracing', 'Comparing', 'Verifying', 'Cross-checking',
19
+ 'Fact-finding', 'Source-hunting', 'Deep-diving', 'Contextualizing', 'Evidence-gathering',
20
+ ]),
21
+ Building: Object.freeze([
22
+ 'Building', 'Crafting', 'Coding', 'Engineering', 'Assembling',
23
+ 'Integrating', 'Implementing', 'Wiring', 'Configuring', 'Compiling',
24
+ 'Rendering', 'Generating', 'Prototyping', 'Shaping', 'Forging',
25
+ ]),
26
+ Solving: Object.freeze([
27
+ 'Debugging', 'Diagnosing', 'Troubleshooting', 'Untangling', 'Repairing',
28
+ 'Patching', 'Resolving', 'Reworking', 'Refining', 'Optimizing',
29
+ 'Simplifying', 'Stabilizing', 'Recovering', 'Calibrating', 'Tuning',
30
+ ]),
31
+ Validating: Object.freeze([
32
+ 'Testing', 'Validating', 'Reviewing', 'Auditing', 'Proofreading',
33
+ 'Measuring', 'Benchmarking', 'Stress-testing', 'Safeguarding', 'Hardening',
34
+ 'Polishing', 'Fine-tuning', 'Checking', 'Confirming', 'Certifying',
35
+ ]),
36
+ Processing: Object.freeze([
37
+ 'Reading', 'Parsing', 'Indexing', 'Remembering', 'Recalling',
38
+ 'Organizing', 'Classifying', 'Filtering', 'Sorting', 'Matching',
39
+ 'Merging', 'Summarizing', 'Compressing', 'Transforming', 'Learning',
40
+ ]),
41
+ Collaborating: Object.freeze([
42
+ 'Delegating', 'Collaborating', 'Consulting', 'Briefing', 'Dispatching',
43
+ 'Synchronizing', 'Negotiating', 'Handshaking', 'Queuing', 'Routing',
44
+ 'Supervising', 'Monitoring', 'Reconciling', 'Reporting', 'Converging',
45
+ ]),
46
+ Creating: Object.freeze([
47
+ 'Writing', 'Rewriting', 'Translating', 'Localizing', 'Narrating',
48
+ 'Explaining', 'Illustrating', 'Visualizing', 'Formatting', 'Styling',
49
+ 'Presenting', 'Documenting', 'Captioning', 'Storyboarding', 'Publishing',
50
+ ]),
51
+ 'BLUN Magic': Object.freeze([
52
+ 'BLUNing', 'Sparkering', 'Wondermaking', 'Dreamweaving', 'Futurecrafting',
53
+ 'Brightening', 'Flowing', 'Accelerating', 'Automating', 'Empowering',
54
+ 'Launching', 'Scaling', 'Evolving', 'Delivering', 'Finishing',
55
+ ]),
56
+ });
57
+
58
+ const EVIDENCE_KEYS = new Set([
59
+ 'command', 'cmd', 'description', 'file', 'file_path', 'path', 'pattern',
60
+ 'prompt', 'query', 'task', 'title', 'url',
61
+ ]);
62
+
63
+ function activityEvidence(toolCall) {
64
+ if (!toolCall || typeof toolCall !== 'object') return '';
65
+ const values = [toolCall.name];
66
+ const args = toolCall.args;
67
+ if (args && typeof args === 'object' && !Array.isArray(args)) {
68
+ for (const [key, value] of Object.entries(args)) {
69
+ if (!EVIDENCE_KEYS.has(key.toLowerCase())) continue;
70
+ if (typeof value === 'string') values.push(value.slice(0, 2_000));
71
+ }
72
+ } else if (typeof toolCall.argumentsText === 'string') {
73
+ values.push(toolCall.argumentsText.slice(0, 2_000));
74
+ }
75
+ return values.filter(Boolean).join(' ').toLowerCase();
76
+ }
77
+
78
+ function matchesAny(text, patterns) {
79
+ return patterns.some((pattern) => text.includes(pattern));
80
+ }
81
+
82
+ function inferThinkingActivityGroup(toolCall, options = {}) {
83
+ if (options.completing === true) return 'BLUN Magic';
84
+ if (options.retrying === true || options.failed === true) return 'Solving';
85
+
86
+ const name = typeof toolCall?.name === 'string' ? toolCall.name.toLowerCase() : '';
87
+ const evidence = activityEvidence(toolCall);
88
+ if (!name) return 'Thinking';
89
+ if (name === 'todolist' || matchesAny(evidence, [' plan', 'roadmap', 'architect', 'scope'])) return 'Planning';
90
+ if (matchesAny(name, ['agent', 'swarm', 'telegram']) || matchesAny(evidence, ['delegate', 'handoff', 'telegram', 'report to'])) return 'Collaborating';
91
+ if (matchesAny(name, ['mnemo', 'memory', 'compact']) || matchesAny(evidence, ['memory', 'mnemo', 'compact', 'summar', 'index'])) return 'Processing';
92
+ if (matchesAny(name, ['grep', 'glob', 'search', 'webfetch', 'web_fetch', 'websearch', 'web_search', 'ls']) || matchesAny(evidence, ['search', 'find ', 'inspect', 'trace'])) return 'Researching';
93
+ if (matchesAny(evidence, ['publish', 'release', 'deploy', 'launch', 'deliver', 'finish'])) return 'BLUN Magic';
94
+ if (matchesAny(evidence, ['error', 'fail', 'debug', 'diagnos', 'repair', 'patch', 'fix ', 'recover', 'troubleshoot'])) return 'Solving';
95
+ if (matchesAny(evidence, ['test', 'check', 'verify', 'validat', 'audit', 'benchmark', 'measure', 'compare', 'sha256', 'diff '])) return 'Validating';
96
+ if (name === 'read' || name === 'readbatch') return 'Processing';
97
+ if (name === 'write' || name === 'edit') {
98
+ if (matchesAny(evidence, ['.md', '.txt', 'readme', 'changelog', 'document', 'translat', 'localiz'])) return 'Creating';
99
+ return 'Building';
100
+ }
101
+ if (matchesAny(name, ['bash', 'shell', 'programmatictool']) || matchesAny(evidence, ['build', 'compile', 'npm ', 'node ', 'code'])) return 'Building';
102
+ return 'Processing';
103
+ }
104
+
105
+ class ThinkingActivityRotator {
106
+ constructor(changeIntervalMs = ACTIVITY_CHANGE_INTERVAL_MS) {
107
+ this.changeIntervalMs = Math.max(ACTIVITY_CHANGE_INTERVAL_MS, Number(changeIntervalMs) || 0);
108
+ this.currentWord = undefined;
109
+ this.changedAtMs = undefined;
110
+ this.nextIndexByGroup = new Map();
111
+ }
112
+
113
+ resolve(group, nowMs = Date.now()) {
114
+ const safeGroup = Object.hasOwn(THINKING_ACTIVITY_WORDS, group) ? group : 'Thinking';
115
+ const now = Number.isFinite(nowMs) ? nowMs : Date.now();
116
+ if (this.currentWord !== undefined && now - this.changedAtMs < this.changeIntervalMs) return this.currentWord;
117
+
118
+ const words = THINKING_ACTIVITY_WORDS[safeGroup];
119
+ const index = this.nextIndexByGroup.get(safeGroup) ?? 0;
120
+ this.currentWord = words[index % words.length];
121
+ this.nextIndexByGroup.set(safeGroup, (index + 1) % words.length);
122
+ this.changedAtMs = now;
123
+ return this.currentWord;
124
+ }
125
+ }
126
+
127
+ module.exports = {
128
+ ACTIVITY_CHANGE_INTERVAL_MS,
129
+ THINKING_ACTIVITY_WORDS,
130
+ ThinkingActivityRotator,
131
+ inferThinkingActivityGroup,
132
+ };