blun-king-cli 9.1.510 → 9.1.512
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LIESMICH.txt +12 -1
- package/README.md +12 -1
- package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +70 -8
- package/agent-spine-plugin/README.md +1 -1
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/docs/acceptance.md +2 -2
- package/agent-spine-plugin/docs/gateway-runtime.md +8 -1
- package/agent-spine-plugin/docs/host-integration.md +34 -34
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/relationships.md +6 -0
- package/agent-spine-plugin/hooks/codex.json +47 -0
- package/agent-spine-plugin/hooks/hooks.json +11 -0
- package/agent-spine-plugin/hooks/version.json +2 -2
- package/agent-spine-plugin/package.json +4 -4
- package/agent-spine-plugin/scripts/check-hosts.js +53 -51
- package/agent-spine-plugin/scripts/check-install.js +53 -35
- package/agent-spine-plugin/scripts/release-check.js +11 -10
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
- package/agent-spine-plugin/src/cli.js +46 -1
- package/agent-spine-plugin/src/hook.js +168 -90
- package/agent-spine-plugin/src/index.js +6 -0
- package/agent-spine-plugin/src/lib/acceptance.js +40 -0
- package/agent-spine-plugin/src/lib/audit.js +9 -2
- package/agent-spine-plugin/src/lib/graph.js +22 -4
- package/agent-spine-plugin/src/lib/persona-runtime.js +103 -31
- package/agent-spine-plugin/src/lib/preflight.js +678 -0
- package/agent-spine-plugin/src/lib/source-roots.js +32 -32
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +20 -3
- package/blun.mjs +48 -2
- package/package.json +1 -1
|
@@ -9,12 +9,12 @@ import { purgeIndexedMemoryCache, resolveIndexedMemory } from "./indexed-memory.
|
|
|
9
9
|
|
|
10
10
|
export const SOURCE_REGISTRY_SCHEMA = "agentspine.source-roots/v1";
|
|
11
11
|
const MAX_REGISTRY_BYTES = 1024 * 1024;
|
|
12
|
-
const MAX_SOURCES = 256;
|
|
13
|
-
const MAX_RULE_FILES = 128;
|
|
14
|
-
const MAX_PROJECT_FILES = 240;
|
|
15
|
-
const MAX_TOTAL_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
16
|
-
const MAX_DIRECTORY_ENTRIES = 4096;
|
|
17
|
-
const MAX_PROJECT_DIRECTORY_ENTRIES = 8192;
|
|
12
|
+
const MAX_SOURCES = 256;
|
|
13
|
+
const MAX_RULE_FILES = 128;
|
|
14
|
+
const MAX_PROJECT_FILES = 240;
|
|
15
|
+
const MAX_TOTAL_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
const MAX_DIRECTORY_ENTRIES = 4096;
|
|
17
|
+
const MAX_PROJECT_DIRECTORY_ENTRIES = 8192;
|
|
18
18
|
const SOURCE_RESOLUTION_MS = 2000;
|
|
19
19
|
const SAFE_NAME = /^[A-Za-z0-9._-]{1,128}$/;
|
|
20
20
|
const SKIP_EXTRA_DIRS = new Set([".git", ".hg", ".svn", ".claude", ".codex", "node_modules", "vendor", "dist", "build", "coverage"]);
|
|
@@ -160,7 +160,7 @@ async function findRoot(cwd, markers) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
async function containsProjectMarker(directory) {
|
|
163
|
+
async function containsProjectMarker(directory) {
|
|
164
164
|
try {
|
|
165
165
|
await lstat(join(directory, ".git"));
|
|
166
166
|
return true;
|
|
@@ -168,37 +168,37 @@ async function containsProjectMarker(directory) {
|
|
|
168
168
|
if (error.code === "ENOENT") return false;
|
|
169
169
|
throw error;
|
|
170
170
|
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function containsEmbeddedHostProfile(directory) {
|
|
174
|
-
return Boolean(
|
|
175
|
-
await existingDirectory(join(directory, ".runtime-private"))
|
|
176
|
-
&& await existingRegular(join(directory, "config.toml"))
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function boundedMarkdownTree(directory, prefix, host, scope, precedenceStart, deadline, {
|
|
181
|
-
projectBoundary = false,
|
|
182
|
-
maxFiles = MAX_RULE_FILES,
|
|
183
|
-
maxDirectoryEntries = MAX_DIRECTORY_ENTRIES
|
|
184
|
-
} = {}) {
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function containsEmbeddedHostProfile(directory) {
|
|
174
|
+
return Boolean(
|
|
175
|
+
await existingDirectory(join(directory, ".runtime-private"))
|
|
176
|
+
&& await existingRegular(join(directory, "config.toml"))
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function boundedMarkdownTree(directory, prefix, host, scope, precedenceStart, deadline, {
|
|
181
|
+
projectBoundary = false,
|
|
182
|
+
maxFiles = MAX_RULE_FILES,
|
|
183
|
+
maxDirectoryEntries = MAX_DIRECTORY_ENTRIES
|
|
184
|
+
} = {}) {
|
|
185
185
|
const root = await existingDirectory(directory);
|
|
186
186
|
if (!root) return [];
|
|
187
187
|
const output = [];
|
|
188
188
|
let visitedEntries = 0;
|
|
189
189
|
async function walk(current) {
|
|
190
190
|
if (Date.now() > deadline) throw new Error(`host-native source resolution exceeded ${SOURCE_RESOLUTION_MS} ms`);
|
|
191
|
-
if (projectBoundary && current !== root
|
|
192
|
-
&& (await containsProjectMarker(current) || await containsEmbeddedHostProfile(current))) return;
|
|
191
|
+
if (projectBoundary && current !== root
|
|
192
|
+
&& (await containsProjectMarker(current) || await containsEmbeddedHostProfile(current))) return;
|
|
193
193
|
const entries = [];
|
|
194
194
|
for await (const entry of await opendir(current)) {
|
|
195
195
|
visitedEntries += 1;
|
|
196
|
-
if (visitedEntries > maxDirectoryEntries) throw new Error(`host-native source tree exceeds ${maxDirectoryEntries} entries`);
|
|
196
|
+
if (visitedEntries > maxDirectoryEntries) throw new Error(`host-native source tree exceeds ${maxDirectoryEntries} entries`);
|
|
197
197
|
entries.push(entry);
|
|
198
198
|
}
|
|
199
199
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
200
200
|
for (const entry of entries) {
|
|
201
|
-
if (output.length >= maxFiles) throw new Error(`host-native rule tree exceeds ${maxFiles} files`);
|
|
201
|
+
if (output.length >= maxFiles) throw new Error(`host-native rule tree exceeds ${maxFiles} files`);
|
|
202
202
|
if (entry.isSymbolicLink()) continue;
|
|
203
203
|
const path = join(current, entry.name);
|
|
204
204
|
if (entry.isDirectory() && !entry.name.startsWith(".") && !SKIP_EXTRA_DIRS.has(entry.name)) await walk(path);
|
|
@@ -388,11 +388,11 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
|
|
|
388
388
|
const { registry } = await readRegistry(env);
|
|
389
389
|
let hostHome;
|
|
390
390
|
let projectRoot;
|
|
391
|
-
let sources;
|
|
392
|
-
let hostDetails = {};
|
|
393
|
-
if (host === "codex") {
|
|
394
|
-
const codexHome = env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
|
|
395
|
-
hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
|
|
391
|
+
let sources;
|
|
392
|
+
let hostDetails = {};
|
|
393
|
+
if (host === "codex") {
|
|
394
|
+
const codexHome = env.CODEX_HOME || env.BLUN_HOME || join(homedir(), ".codex");
|
|
395
|
+
hostHome = await existingDirectory(resolve(codexHome)) || resolve(codexHome);
|
|
396
396
|
const config = await codexConfig(hostHome);
|
|
397
397
|
projectRoot = env.AGENTSPINE_ROOT ? await canonicalPath(env.AGENTSPINE_ROOT) : await findRoot(canonicalCwd, config.rootMarkers);
|
|
398
398
|
sources = await codexSources({ cwd: canonicalCwd, projectRoot, codexHome: hostHome, config });
|
|
@@ -407,8 +407,8 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
|
|
|
407
407
|
memoryDiagnostics: result.memoryDiagnostics };
|
|
408
408
|
}
|
|
409
409
|
if (projectRoot !== homedir() && projectRoot !== dirname(hostHome)) {
|
|
410
|
-
sources.push(...await boundedMarkdownTree(projectRoot, "agentspine:project", host, "project", 3000, deadline,
|
|
411
|
-
{ projectBoundary: true, maxFiles: MAX_PROJECT_FILES, maxDirectoryEntries: MAX_PROJECT_DIRECTORY_ENTRIES }));
|
|
410
|
+
sources.push(...await boundedMarkdownTree(projectRoot, "agentspine:project", host, "project", 3000, deadline,
|
|
411
|
+
{ projectBoundary: true, maxFiles: MAX_PROJECT_FILES, maxDirectoryEntries: MAX_PROJECT_DIRECTORY_ENTRIES }));
|
|
412
412
|
}
|
|
413
413
|
const nativeNames = new Set(host === "codex"
|
|
414
414
|
? ["AGENTS.override.md", "AGENTS.md", ...(hostDetails.fallbackNames || [])]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.10.1";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { watch } from "node:fs";
|
|
4
|
-
import { realpath } from "node:fs/promises";
|
|
5
|
-
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { claimGatewayWork, completeGatewayRun, deliverPrepared, failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth } from "./lib/gateway-runtime.js";
|
|
7
7
|
import { createTelegramAdapter } from "./lib/telegram-adapter.js";
|
|
8
8
|
import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
|
|
@@ -12,11 +12,25 @@ import { isMainModule } from "./lib/runtime.js";
|
|
|
12
12
|
const MAX_FRAME = 64 * 1024;
|
|
13
13
|
const WAKE_FILES = new Set(["attention.json", "channel-runtime.json", "gateway-policy.json", "persona-policy.json", "persona-runtime.json"]);
|
|
14
14
|
|
|
15
|
-
|
|
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 } = {}) {
|
|
16
28
|
const delay = Math.max(250, Math.min(60000, Number(delayMs) || 60000));
|
|
17
29
|
const { directory } = await loadGatewayRuntime(root);
|
|
18
30
|
let watchPath;
|
|
19
31
|
try { watchPath = await realpathFactory(directory); } catch { return "watch-unavailable"; }
|
|
32
|
+
let before;
|
|
33
|
+
try { before = await wakeSnapshot(watchPath); } catch { return "watch-unavailable"; }
|
|
20
34
|
return new Promise((resolvePromise) => {
|
|
21
35
|
let settled = false; let watcher;
|
|
22
36
|
const finish = (reason) => {
|
|
@@ -32,6 +46,9 @@ export async function waitForGatewayWake(root, delayMs, { watchFactory = watch,
|
|
|
32
46
|
if (name === null || WAKE_FILES.has(name)) finish("event");
|
|
33
47
|
});
|
|
34
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"));
|
|
35
52
|
} catch { finish("watch-unavailable"); }
|
|
36
53
|
});
|
|
37
54
|
}
|
package/blun.mjs
CHANGED
|
@@ -262985,6 +262985,7 @@ var init_turn = __esmMin((() => {
|
|
|
262985
262985
|
const { isError, output } = finalResult;
|
|
262986
262986
|
currentStepHadTool = true;
|
|
262987
262987
|
if (isError === true) currentStepHadFailure = true;
|
|
262988
|
+
recordGoalTodoEvidence(this.agent, ctx.toolCall.name, ctx.args, isError);
|
|
262988
262989
|
directReplyTurnStop.noteToolResult(ctx.toolCall.name, isError);
|
|
262989
262990
|
const event = isError === true ? "PostToolUseFailure" : "PostToolUse";
|
|
262990
262991
|
this.agent.hooks?.fireAndForgetTrigger(event, {
|
|
@@ -423291,6 +423292,7 @@ async function handleGoalCommand(host, args) {
|
|
|
423291
423292
|
const IDEA_CONTRACT_MARKER = "Work as a self-directing employee:";
|
|
423292
423293
|
const IDEA_ALLOWED_CHANNELS = Object.freeze([]);
|
|
423293
423294
|
const GOAL_TODO_REFRESH_WORK_CALL_LIMIT = 8;
|
|
423295
|
+
const GOAL_TODO_EVIDENCE_LIMIT = 8;
|
|
423294
423296
|
const IDEA_TERMINAL_TODO_STATUSES = Object.freeze([
|
|
423295
423297
|
"done",
|
|
423296
423298
|
"blocked",
|
|
@@ -423318,6 +423320,45 @@ function validInitialIdeaPlan(value) {
|
|
|
423318
423320
|
}
|
|
423319
423321
|
return active === 1;
|
|
423320
423322
|
}
|
|
423323
|
+
function goalTodoEvidenceTarget(args) {
|
|
423324
|
+
if (args === null || typeof args !== "object") return "";
|
|
423325
|
+
if (Array.isArray(args.requests)) return `${args.requests.length} requested files`;
|
|
423326
|
+
for (const key of ["file_path", "path", "old_path", "new_path"]) {
|
|
423327
|
+
const value = typeof args[key] === "string" ? args[key].trim() : "";
|
|
423328
|
+
if (value === "") continue;
|
|
423329
|
+
return value.replaceAll("\\", "/").split("/").filter(Boolean).at(-1)?.slice(0, 120) ?? "";
|
|
423330
|
+
}
|
|
423331
|
+
return "";
|
|
423332
|
+
}
|
|
423333
|
+
function goalTodoEvidenceLabel(toolName, args, isError) {
|
|
423334
|
+
const name = String(toolName ?? "tool");
|
|
423335
|
+
const outcome = isError === true ? "failed" : "succeeded";
|
|
423336
|
+
const target = goalTodoEvidenceTarget(args);
|
|
423337
|
+
if (target !== "") return `${name} ${outcome}: ${target}`;
|
|
423338
|
+
if (name === "Bash") {
|
|
423339
|
+
const command = typeof args?.command === "string" ? args.command : "";
|
|
423340
|
+
if (/\bnode\s+--check\b/iu.test(command)) return `Node syntax check ${outcome}`;
|
|
423341
|
+
if (/\b(?:sha256sum|Get-FileHash)\b/iu.test(command)) return `SHA-256 measurement ${outcome}`;
|
|
423342
|
+
if (/\b(?:rg|grep)\b/iu.test(command)) return `Shell search ${outcome}`;
|
|
423343
|
+
}
|
|
423344
|
+
return `${name} ${outcome}`;
|
|
423345
|
+
}
|
|
423346
|
+
function recordGoalTodoEvidence(agent, toolName, args, isError) {
|
|
423347
|
+
if (isIdeaGoal(agent) || agent.goal.getActiveGoal() === null) return;
|
|
423348
|
+
const name = String(toolName ?? "");
|
|
423349
|
+
const progress = agent.goalTodoPolicyState ??= {
|
|
423350
|
+
workCallsSinceRefresh: 0,
|
|
423351
|
+
refreshRequired: false
|
|
423352
|
+
};
|
|
423353
|
+
if (name === "TodoList") {
|
|
423354
|
+
if (isError !== true) progress.recentEvidence = [];
|
|
423355
|
+
return;
|
|
423356
|
+
}
|
|
423357
|
+
if (name === "UpdateGoal" || TELEGRAM_DELIVERY_TOOL_RE.test(name)) return;
|
|
423358
|
+
const recentEvidence = Array.isArray(progress.recentEvidence) ? progress.recentEvidence : [];
|
|
423359
|
+
recentEvidence.push(goalTodoEvidenceLabel(name, args, isError));
|
|
423360
|
+
progress.recentEvidence = recentEvidence.slice(-GOAL_TODO_EVIDENCE_LIMIT);
|
|
423361
|
+
}
|
|
423321
423362
|
function goalTodoMaintenanceMode(agent) {
|
|
423322
423363
|
if (isIdeaGoal(agent)) return null;
|
|
423323
423364
|
const todos = ideaTodos(agent);
|
|
@@ -423326,7 +423367,9 @@ function goalTodoMaintenanceMode(agent) {
|
|
|
423326
423367
|
workCallsSinceRefresh: 0,
|
|
423327
423368
|
refreshRequired: false
|
|
423328
423369
|
};
|
|
423329
|
-
|
|
423370
|
+
if (!progress.refreshRequired && progress.workCallsSinceRefresh < GOAL_TODO_REFRESH_WORK_CALL_LIMIT) return null;
|
|
423371
|
+
progress.refreshRequired = true;
|
|
423372
|
+
return "refresh";
|
|
423330
423373
|
}
|
|
423331
423374
|
function buildGoalTodoMaintenanceSystemPrompt(agent, mode) {
|
|
423332
423375
|
const todos = ideaTodos(agent);
|
|
@@ -423343,14 +423386,17 @@ ${visibleTodoList}
|
|
|
423343
423386
|
function buildGoalTodoMaintenanceMessages(agent, mode) {
|
|
423344
423387
|
const goal = agent.goal.getActiveGoal();
|
|
423345
423388
|
const todos = ideaTodos(agent);
|
|
423389
|
+
const recentEvidence = Array.isArray(agent.goalTodoPolicyState?.recentEvidence) ? agent.goalTodoPolicyState.recentEvidence.slice(-GOAL_TODO_EVIDENCE_LIMIT) : [];
|
|
423346
423390
|
const objective = String(goal?.objective ?? "Continue the current multi-step task.").trim();
|
|
423347
423391
|
const visibleTodoList = todos.length === 0 ? "The visible TodoList is empty." : ["Current visible TodoList:", ...todos.map((todo) => `- [${String(todo?.status ?? "pending")}] ${String(todo?.title ?? "").trim()}`)].join("\n");
|
|
423392
|
+
const verifiedEvidence = recentEvidence.length === 0 ? "No new work-tool evidence is available." : ["Recent bounded work-tool evidence since the last accepted TodoList:", ...recentEvidence.map((entry) => `- ${entry}`)].join("\n");
|
|
423348
423393
|
const action = mode === "initial" ? "Create the task-specific TodoList from the active objective. Keep exactly one item in_progress and every later item pending." : "Refresh the TodoList from the current verified state. Mark finished work done, keep the actual current step in_progress, and leave later work pending. Completed items disappear automatically after this call.";
|
|
423349
423394
|
const request = [
|
|
423350
423395
|
"Return exactly one TodoList tool call and no prose.",
|
|
423351
423396
|
action,
|
|
423352
423397
|
`Active objective: ${objective}`,
|
|
423353
|
-
visibleTodoList
|
|
423398
|
+
visibleTodoList,
|
|
423399
|
+
verifiedEvidence
|
|
423354
423400
|
].join("\n\n");
|
|
423355
423401
|
return [{
|
|
423356
423402
|
role: "user",
|