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.
- package/agent-spine-plugin/.claude-plugin/marketplace.json +20 -20
- package/agent-spine-plugin/.claude-plugin/plugin.json +14 -14
- package/agent-spine-plugin/.codex-plugin/plugin.json +34 -34
- package/agent-spine-plugin/.mcp.json +8 -8
- package/agent-spine-plugin/CONTRIBUTING.md +52 -0
- package/agent-spine-plugin/LICENSE +186 -186
- package/agent-spine-plugin/README.md +394 -0
- package/agent-spine-plugin/SECURITY.md +47 -0
- package/agent-spine-plugin/assets/agentspine-banner.svg +32 -32
- package/agent-spine-plugin/bin/agentspine-mcp.js +4 -4
- package/agent-spine-plugin/bin/agentspine.js +7 -7
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/hooks/codex.json +47 -47
- package/agent-spine-plugin/hooks/hooks.json +106 -106
- package/agent-spine-plugin/hooks/version.json +5 -5
- package/agent-spine-plugin/package.json +69 -69
- package/agent-spine-plugin/scripts/check-hosts.js +199 -199
- package/agent-spine-plugin/skill/SKILL.md +76 -76
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +85 -85
- package/agent-spine-plugin/src/cli.js +1488 -1442
- package/agent-spine-plugin/src/hook.js +886 -812
- package/agent-spine-plugin/src/index.js +93 -93
- package/agent-spine-plugin/src/lib/acceptance.js +333 -333
- package/agent-spine-plugin/src/lib/attention.js +755 -755
- package/agent-spine-plugin/src/lib/audit.js +351 -342
- package/agent-spine-plugin/src/lib/authentication.js +515 -515
- package/agent-spine-plugin/src/lib/briefing.js +317 -317
- package/agent-spine-plugin/src/lib/catalog.js +167 -167
- package/agent-spine-plugin/src/lib/channel-runtime.js +665 -665
- package/agent-spine-plugin/src/lib/context.js +154 -154
- package/agent-spine-plugin/src/lib/continuity.js +338 -338
- package/agent-spine-plugin/src/lib/coordination.js +577 -577
- package/agent-spine-plugin/src/lib/documents.js +217 -217
- package/agent-spine-plugin/src/lib/feed-transport.js +499 -499
- package/agent-spine-plugin/src/lib/filesystem-retry.js +32 -32
- package/agent-spine-plugin/src/lib/gateway-runtime.js +1119 -760
- package/agent-spine-plugin/src/lib/graph.js +337 -337
- package/agent-spine-plugin/src/lib/hook-audit.js +29 -0
- package/agent-spine-plugin/src/lib/https-transport.js +392 -392
- package/agent-spine-plugin/src/lib/indexed-memory-offline.js +40 -40
- package/agent-spine-plugin/src/lib/indexed-memory.js +281 -281
- package/agent-spine-plugin/src/lib/learning.js +6923 -6466
- package/agent-spine-plugin/src/lib/object-transport.js +206 -206
- package/agent-spine-plugin/src/lib/owned-file-lock.js +143 -143
- package/agent-spine-plugin/src/lib/paths.js +109 -109
- package/agent-spine-plugin/src/lib/peer-transport.js +283 -283
- package/agent-spine-plugin/src/lib/persona-runtime.js +581 -581
- package/agent-spine-plugin/src/lib/preflight.js +702 -702
- package/agent-spine-plugin/src/lib/runtime.js +13 -13
- package/agent-spine-plugin/src/lib/selfstarter.js +892 -819
- package/agent-spine-plugin/src/lib/sharing.js +969 -969
- package/agent-spine-plugin/src/lib/source-roots.js +529 -482
- package/agent-spine-plugin/src/lib/sqlite-transport.js +501 -501
- package/agent-spine-plugin/src/lib/telegram-adapter.js +119 -119
- package/agent-spine-plugin/src/lib/voice-runtime.js +39 -39
- package/agent-spine-plugin/src/mcp.js +597 -572
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +202 -195
- package/bin/active-steer-priority-policy.cjs +24 -0
- package/bin/launcher-runtime.js +8 -1
- package/bin/mnemo-tool-agent-policy.cjs +22 -0
- package/bin/thinking-activity-status-policy.cjs +132 -0
- package/bin/thinking-only-guard.cjs +75 -0
- package/bin/tool-call-loop-policy.cjs +53 -0
- package/bin/turn-thinking-policy.cjs +25 -1
- package/blun.mjs +554 -64
- package/package.json +4 -1
- package/standard-skills/translate-native/LICENSE +21 -21
- package/standard-skills/translate-native/references/evaluation-protocol.md +95 -95
- package/standard-skills/translate-native/references/native-orthography.md +79 -79
- package/standard-skills/translate-native/references/native-translation-standard.md +94 -94
- package/standard-skills/translate-native/references/structured-content.md +72 -72
- package/standard-skills/translate-native/references/translationese-review.md +77 -77
- package/standard-skills/translate-native/scripts/blun_language_guard.py +697 -697
- package/standard-skills/translate-native/scripts/check_diacritics.py +353 -353
- package/standard-skills/translate-native/scripts/guard_service_client.py +264 -264
- package/standard-skills/translate-native/scripts/language_gateway.py +62 -62
- package/standard-skills/translate-native/scripts/language_quality.py +377 -377
- package/standard-skills/translate-native/scripts/pre_output_guard.py +64 -64
- package/standard-skills/translate-native/scripts/translation_guard.py +916 -916
- package/standard-tools/language-guard/blun_language_guard.py +697 -697
- package/standard-tools/language-guard/check_diacritics.py +353 -353
- package/standard-tools/language-guard/guard_service_client.py +264 -264
- package/standard-tools/language-guard/language_quality.py +377 -377
- package/standard-tools/language-guard/translation_guard.py +916 -916
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "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
|
|
94
|
-
:
|
|
95
|
-
if (item.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
};
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -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 {
|
|
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
|
+
};
|