blun-king-cli 9.1.565 → 9.1.567

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.
@@ -1,11 +1,12 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { lstat, open, opendir, readFile, realpath, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
3
4
  import { join, relative, resolve, sep } from "node:path";
4
5
  import { buildCatalog } from "./catalog.js";
5
6
  import { isFileLockContention, replaceFileWithRetry } from "./filesystem-retry.js";
6
7
  import { loadCoordination } from "./coordination.js";
7
8
  import { loadGraph } from "./graph.js";
8
- import { projectStateDir } from "./paths.js";
9
+ import { comparablePath, projectStateDir } from "./paths.js";
9
10
 
10
11
  const POLICY_SCHEMA = "agentspine.execution-policy/v1";
11
12
  const JOB_SCHEMA = "agentspine.selfstarter/v1";
@@ -24,6 +25,18 @@ const SECRET_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----|\b(?:sk|gh[opusu])_[A-Za-z
24
25
 
25
26
  function workspaceScanError(error) { error.agentSpineScan = true; return error; }
26
27
 
28
+ function configuredHostProfileRoots(env = process.env) {
29
+ return [env.CODEX_HOME, env.BLUN_HOME, env.CLAUDE_CONFIG_DIR, join(homedir(), ".codex"), join(homedir(), ".claude")]
30
+ .filter((value) => typeof value === "string" && value)
31
+ .map((value) => comparablePath(value));
32
+ }
33
+
34
+ function isConfiguredHostProfileRoot(root, env = process.env) {
35
+ const target = comparablePath(root);
36
+ return configuredHostProfileRoots(env).some((profile) => process.platform === "win32"
37
+ ? profile.toLowerCase() === target.toLowerCase() : profile === target);
38
+ }
39
+
27
40
  function emptyPolicy(root) {
28
41
  return { schema: POLICY_SCHEMA, root, revision: 0, grants: [], history: [] };
29
42
  }
@@ -140,6 +153,9 @@ async function withLock(path, read, operation, save = true) {
140
153
  }
141
154
 
142
155
  async function pathsFor(root, providedCatalog = null) {
156
+ if (isConfiguredHostProfileRoot(root)) {
157
+ throw new Error("self-starter cannot use a host profile as its workspace root");
158
+ }
143
159
  let catalog = providedCatalog;
144
160
  if (!catalog) {
145
161
  try {
@@ -224,6 +240,9 @@ export async function collectWorkspaceFiles(root) {
224
240
 
225
241
  export async function workspaceFingerprint(inputRoot = process.cwd()) {
226
242
  const root = resolve(inputRoot);
243
+ if (isConfiguredHostProfileRoot(root)) {
244
+ throw new Error("self-starter cannot fingerprint a host profile root");
245
+ }
227
246
  const collected = await collectWorkspaceFiles(root);
228
247
  const files = [];
229
248
  const skipped = [...collected.skipped];
@@ -473,8 +473,9 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
473
473
  }
474
474
  const knownHomeRoots = await homeRoots(env);
475
475
  const skippedHomeTree = knownHomeRoots.some((root) => samePath(root, projectRoot));
476
+ const skippedProfileTree = samePath(hostHome, projectRoot);
476
477
  const skippedFallbackHomeTree = skippedHomeTree && rootResolution === "cwd-fallback";
477
- if (!skippedHomeTree) {
478
+ if (!skippedHomeTree && !skippedProfileTree) {
478
479
  sources.push(...await boundedMarkdownTree(projectRoot, "agentspine:project", host, "project", 3000, deadline,
479
480
  { projectBoundary: true, maxFiles: MAX_PROJECT_FILES, maxDirectoryEntries: MAX_PROJECT_DIRECTORY_ENTRIES, skipped }));
480
481
  }
@@ -504,7 +505,7 @@ export async function resolveHostSourceCatalog({ host, cwd = process.cwd(), inpu
504
505
  reason: documents.length ? null : "No regular, non-symlink host-native Markdown source exists in the checked scope.",
505
506
  personalContinuityLoaded: documents.some((item) => item.sourceScope === "user") || Boolean(activeUserState),
506
507
  broadHomeScan: false, projectTreeScan: skippedFallbackHomeTree ? "skipped-unmarked-home"
507
- : skippedHomeTree ? "skipped-home-root" : "bounded",
508
+ : skippedHomeTree ? "skipped-home-root" : skippedProfileTree ? "skipped-profile-root" : "bounded",
508
509
  skipped: skipped.sort((a, b) => a.path.localeCompare(b.path) || a.operation.localeCompare(b.operation)),
509
510
  rootResolution, registryRevision: registry.revision,
510
511
  ...(host === "claude" ? {
@@ -1 +1 @@
1
- export const VERSION = "0.54.0";
1
+ export const VERSION = "0.59.1";
@@ -3,7 +3,10 @@ import { spawn } from "node:child_process";
3
3
  import { watch } from "node:fs";
4
4
  import { lstat, realpath } from "node:fs/promises";
5
5
  import { isAbsolute, join, resolve } from "node:path";
6
- import { claimGatewayWork, completeGatewayRun, deliverPrepared, failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth } from "./lib/gateway-runtime.js";
6
+ import {
7
+ claimGatewayWork, completeGatewayRun, deliverPrepared, executionAttemptForStep,
8
+ failGatewayRun, loadGatewayRuntime, reconcileGateway, updateGatewayHealth
9
+ } from "./lib/gateway-runtime.js";
7
10
  import { createTelegramAdapter } from "./lib/telegram-adapter.js";
8
11
  import { acknowledgeChannelDelivery, loadChannelRuntime } from "./lib/channel-runtime.js";
9
12
  import { loadPersonaRuntime, syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
@@ -93,12 +96,15 @@ async function hostWorkItem(root, item) {
93
96
  const goalStep = item.goalStepId === null || item.goalStepId === undefined ? null
94
97
  : goal?.plan?.steps.find((entry) => entry.stepId === item.goalStepId) || null;
95
98
  if (item.goalStepId && !goalStep) throw new Error("claimed work lost its exact goal-plan step");
99
+ const executionAttempt = executionAttemptForStep(goalStep);
96
100
  const event = item.channelEventId === null ? null
97
101
  : channel.runtime.events.find((entry) => entry.eventId === item.channelEventId) || null;
98
102
  if (item.channelEventId && !event) throw new Error("claimed channel work lost its exact event");
99
103
  return {
100
104
  ...structuredClone(item), host: identity.host, profileId: identity.profileId, projectRoot: root,
101
- goal: goal ? structuredClone(goal) : null, goalStep: goalStep ? structuredClone(goalStep) : null,
105
+ goal: goal ? structuredClone(goal) : null,
106
+ goalStep: goalStep ? { ...structuredClone(goalStep),
107
+ ...(executionAttempt === null ? {} : { executionAttempt }) } : null,
102
108
  hostEnvironment: {
103
109
  AGENTSPINE_GATEWAY_CONTEXT: "agentspine.gateway-start/v1",
104
110
  AGENTSPINE_ENTITY_ID: item.agentId,
@@ -156,9 +162,11 @@ export async function runWorkerTick({ root = process.cwd(), workerId = "gateway-
156
162
  }
157
163
  const completed = await completeGatewayRun({ root, queueId: claim.item.queueId, workerId, result, now });
158
164
  if (!completed.outbox) return {
159
- status: completed.clarification ? "needs-clarification" : completed.item.status,
165
+ status: completed.clarification ? "needs-clarification"
166
+ : completed.exploration ? "exploring" : completed.item.status,
160
167
  processed: true, queueId: completed.item.queueId,
161
- ...(completed.clarification ? { clarification: completed.clarification } : {})
168
+ ...(completed.clarification ? { clarification: completed.clarification } : {}),
169
+ ...(completed.exploration ? { exploration: completed.exploration } : {})
162
170
  };
163
171
  const delivery = await deliverPrepared({ root, outboxId: completed.outbox.outboxId,
164
172
  adapter: deliveryAdapter, now });
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+
8
+ function isPathInside(root, candidate) {
9
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
10
+ return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
11
+ }
12
+
13
+ function cacheBase(options = {}) {
14
+ if (options.cacheBase) return path.resolve(options.cacheBase);
15
+ const env = options.env || process.env;
16
+ const platform = options.platform || process.platform;
17
+ if (env.BLUN_CACHE_DIR) return path.resolve(env.BLUN_CACHE_DIR);
18
+ if (platform === 'win32') {
19
+ return path.resolve(env.LOCALAPPDATA || path.join(options.homeDir || os.homedir(), 'AppData', 'Local'), 'blun');
20
+ }
21
+ if (platform === 'darwin') return path.join(options.homeDir || os.homedir(), 'Library', 'Caches', 'blun');
22
+ return path.join(env.XDG_CACHE_HOME || path.join(options.homeDir || os.homedir(), '.cache'), 'blun');
23
+ }
24
+
25
+ function sha256(bytes) {
26
+ return crypto.createHash('sha256').update(bytes).digest('hex');
27
+ }
28
+
29
+ function ensureCachedFile(sourcePath, targetPath, expectedHash) {
30
+ try {
31
+ if (sha256(fs.readFileSync(targetPath)) === expectedHash) return targetPath;
32
+ } catch {}
33
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
34
+ const temporary = `${targetPath}.${process.pid}.${Date.now()}.tmp`;
35
+ fs.copyFileSync(sourcePath, temporary);
36
+ try {
37
+ fs.renameSync(temporary, targetPath);
38
+ } catch (error) {
39
+ try {
40
+ if (sha256(fs.readFileSync(targetPath)) === expectedHash) {
41
+ fs.rmSync(temporary, { force: true });
42
+ return targetPath;
43
+ }
44
+ } catch {}
45
+ fs.rmSync(temporary, { force: true });
46
+ throw error;
47
+ }
48
+ return targetPath;
49
+ }
50
+
51
+ function resolveNativeRuntimePath(requestPath, options = {}) {
52
+ if (typeof requestPath !== 'string' || path.extname(requestPath).toLowerCase() !== '.node') return requestPath;
53
+ const packageRoot = options.packageRoot && path.resolve(options.packageRoot);
54
+ const sourcePath = path.resolve(requestPath);
55
+ if (!packageRoot || !isPathInside(packageRoot, sourcePath)) return requestPath;
56
+ let bytes;
57
+ try {
58
+ bytes = fs.readFileSync(sourcePath);
59
+ } catch {
60
+ return requestPath;
61
+ }
62
+ const hash = sha256(bytes);
63
+ const relativePath = path.relative(packageRoot, sourcePath);
64
+ const relativeHash = sha256(Buffer.from(relativePath.replaceAll('\\', '/'))).slice(0, 16);
65
+ const targetPath = path.join(cacheBase(options), 'native-runtime', hash, `${relativeHash}-${path.basename(sourcePath)}`);
66
+ try {
67
+ return ensureCachedFile(sourcePath, targetPath, hash);
68
+ } catch {
69
+ return requestPath;
70
+ }
71
+ }
72
+
73
+ module.exports = {
74
+ isPathInside,
75
+ resolveNativeRuntimePath,
76
+ };
package/blun.mjs CHANGED
@@ -17,6 +17,7 @@ import sessionScrollbackArchive from "./bin/session-scrollback-archive.cjs";
17
17
  import sessionReplayWindowPolicy from "./bin/session-replay-window-policy.cjs";
18
18
  import globPatternPolicy from "./bin/glob-pattern-policy.cjs";
19
19
  import userHomePathPolicy from "./bin/user-home-path-policy.cjs";
20
+ import nativeRuntimeCachePolicy from "./bin/native-runtime-cache.cjs";
20
21
  import editableToolApprovalPolicy from "./bin/editable-tool-approval-policy.cjs";
21
22
  import editableToolApprovalRuntime from "./bin/editable-tool-approval-runtime.cjs";
22
23
  import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
@@ -59,6 +60,7 @@ const { SessionScrollbackArchive, sessionScrollbackArchiveDirectory } = sessionS
59
60
  const { replayWindowStartIndex, resolveReplayActiveTurnCount } = sessionReplayWindowPolicy;
60
61
  const { globPatternError } = globPatternPolicy;
61
62
  const { unsupportedUserHomePathError } = userHomePathPolicy;
63
+ const { resolveNativeRuntimePath } = nativeRuntimeCachePolicy;
62
64
  const {
63
65
  buildApprovalEditedEvent,
64
66
  canonicalEditedToolInputJson,
@@ -523527,6 +523529,7 @@ function installNativeModuleHook() {
523527
523529
  installed = true;
523528
523530
  const moduleBuiltin = nodeRequire("node:module");
523529
523531
  const originalLoad = moduleBuiltin._load;
523532
+ const originalResolveFilename = moduleBuiltin._resolveFilename;
523530
523533
  if (originalLoad === void 0) return;
523531
523534
  moduleBuiltin._load = function loadWithNativeAssets(request, parent, isMain) {
523532
523535
  if (request === "node-pty") {
@@ -523543,6 +523546,13 @@ function installNativeModuleHook() {
523543
523546
  }
523544
523547
  }
523545
523548
  }
523549
+ if (typeof request === "string" && request.toLowerCase().endsWith(".node") && typeof originalResolveFilename === "function") {
523550
+ try {
523551
+ const resolvedRequest = originalResolveFilename.call(moduleBuiltin, request, parent, isMain);
523552
+ const cachedRequest = resolveNativeRuntimePath(resolvedRequest, { packageRoot: __dirname });
523553
+ if (cachedRequest !== resolvedRequest) return originalLoad.call(this, cachedRequest, parent, isMain);
523554
+ } catch {}
523555
+ }
523546
523556
  return originalLoad.call(this, request, parent, isMain);
523547
523557
  };
523548
523558
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.565",
3
+ "version": "9.1.567",
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": {
@@ -212,8 +212,11 @@ function gate(ctx, botUsername) {
212
212
  if (policy === void 0) return { action: "drop" };
213
213
  const groupAllowFrom = policy.allowFrom ?? [];
214
214
  if (groupAllowFrom.length > 0 && !groupAllowFrom.includes(senderId)) return { action: "drop" };
215
+ const ignored = matchesAny(ctx.text, access.ignorePatterns);
216
+ const mentioned = !ignored && isMentioned(ctx, botUsername, access.mentionPatterns);
217
+ if (policy.requireMention === true && !mentioned) return { action: "drop" };
215
218
  const mentionBypass = from.isBot !== true && (policy.alwaysAllowFrom ?? []).includes(senderId);
216
- const addressed = !matchesAny(ctx.text, access.ignorePatterns) && (mentionBypass || isMentioned(ctx, botUsername, access.mentionPatterns));
219
+ const addressed = mentioned || !ignored && mentionBypass;
217
220
  const targetedElsewhere = from.isBot === true && !addressed;
218
221
  return {
219
222
  action: "deliver",