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.
Files changed (35) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LIESMICH.txt +12 -1
  3. package/README.md +12 -1
  4. package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
  5. package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
  6. package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
  7. package/agent-spine-plugin/CHANGELOG.md +70 -8
  8. package/agent-spine-plugin/README.md +1 -1
  9. package/agent-spine-plugin/blun.plugin.json +33 -33
  10. package/agent-spine-plugin/docs/acceptance.md +2 -2
  11. package/agent-spine-plugin/docs/gateway-runtime.md +8 -1
  12. package/agent-spine-plugin/docs/host-integration.md +34 -34
  13. package/agent-spine-plugin/docs/preflight-recall.md +69 -0
  14. package/agent-spine-plugin/docs/relationships.md +6 -0
  15. package/agent-spine-plugin/hooks/codex.json +47 -0
  16. package/agent-spine-plugin/hooks/hooks.json +11 -0
  17. package/agent-spine-plugin/hooks/version.json +2 -2
  18. package/agent-spine-plugin/package.json +4 -4
  19. package/agent-spine-plugin/scripts/check-hosts.js +53 -51
  20. package/agent-spine-plugin/scripts/check-install.js +53 -35
  21. package/agent-spine-plugin/scripts/release-check.js +11 -10
  22. package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
  23. package/agent-spine-plugin/src/cli.js +46 -1
  24. package/agent-spine-plugin/src/hook.js +168 -90
  25. package/agent-spine-plugin/src/index.js +6 -0
  26. package/agent-spine-plugin/src/lib/acceptance.js +40 -0
  27. package/agent-spine-plugin/src/lib/audit.js +9 -2
  28. package/agent-spine-plugin/src/lib/graph.js +22 -4
  29. package/agent-spine-plugin/src/lib/persona-runtime.js +103 -31
  30. package/agent-spine-plugin/src/lib/preflight.js +678 -0
  31. package/agent-spine-plugin/src/lib/source-roots.js +32 -32
  32. package/agent-spine-plugin/src/version.js +1 -1
  33. package/agent-spine-plugin/src/worker.js +20 -3
  34. package/blun.mjs +48 -2
  35. 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.8.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
- export async function waitForGatewayWake(root, delayMs, { watchFactory = watch, realpathFactory = realpath } = {}) {
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
- return progress.refreshRequired || progress.workCallsSinceRefresh >= GOAL_TODO_REFRESH_WORK_CALL_LIMIT ? "refresh" : null;
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.510",
3
+ "version": "9.1.512",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {