@bike4mind/cli 0.12.1 → 0.14.0
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/bin/bike4mind-cli.mjs +13 -0
- package/dist/{ConfigStore-C3tokQej.mjs → ConfigStore-B9I7UHuG.mjs} +51 -2
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +13 -17
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +2 -2
- package/dist/commands/mcpCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +120 -5
- package/dist/index.mjs +4 -4
- package/dist/{package-CMkVxGGC.mjs → package-uvIC6spW.mjs} +1 -1
- package/dist/{tools-QQ6ibgPF.mjs → tools-BTPUXUNS.mjs} +248 -248
- package/dist/updateChecker-C8xsNY2L.mjs +218 -0
- package/package.json +8 -8
- package/dist/updateChecker-D67NPlS5.mjs +0 -117
package/bin/bike4mind-cli.mjs
CHANGED
|
@@ -387,6 +387,19 @@ if (isDev) {
|
|
|
387
387
|
} else {
|
|
388
388
|
// Production: run compiled JavaScript
|
|
389
389
|
try {
|
|
390
|
+
// Auto-update on launch (consent-first): when a newer version is available
|
|
391
|
+
// on a writable global prefix, install + re-exec into it BEFORE importing
|
|
392
|
+
// the code-split app — running an install while dist/index.mjs is loaded
|
|
393
|
+
// would crash it. On the default 'ask' preference this prompts the user
|
|
394
|
+
// (Update once / Always / Skip / Never); 'auto' installs silently, 'never'
|
|
395
|
+
// does nothing. Wrapped so the updater can never block launching the CLI.
|
|
396
|
+
try {
|
|
397
|
+
const { maybeAutoUpdateOnLaunch } = await import('../dist/commands/updateCommand.mjs');
|
|
398
|
+
await maybeAutoUpdateOnLaunch();
|
|
399
|
+
} catch {
|
|
400
|
+
// Updater is best-effort — fall through to the current version.
|
|
401
|
+
}
|
|
402
|
+
|
|
390
403
|
await import(join(__dirname, '../dist/index.mjs'));
|
|
391
404
|
} catch (error) {
|
|
392
405
|
console.error('Failed to start CLI:', error);
|
|
@@ -1493,6 +1493,27 @@ let SupportedFabFileMimeTypes = /* @__PURE__ */ function(SupportedFabFileMimeTyp
|
|
|
1493
1493
|
return SupportedFabFileMimeTypes;
|
|
1494
1494
|
}({});
|
|
1495
1495
|
/**
|
|
1496
|
+
* Canonical agent execution status tuple. Shared between the database model
|
|
1497
|
+
* (`AgentExecutionModel`), the client store (`useAgentExecutionStore`), and
|
|
1498
|
+
* wire schemas (`ChildExecutionSnapshotSchema`, etc.) so the value space can't
|
|
1499
|
+
* drift across boundaries. Previously declared in two places — moving it here
|
|
1500
|
+
* lets the Zod schemas type `status` as `z.enum(AGENT_EXECUTION_STATUSES)`
|
|
1501
|
+
* instead of `z.string()`, removing the runtime narrowing the client used to
|
|
1502
|
+
* compensate for the loose contract.
|
|
1503
|
+
*/
|
|
1504
|
+
const AGENT_EXECUTION_STATUSES = [
|
|
1505
|
+
"pending",
|
|
1506
|
+
"running",
|
|
1507
|
+
"continuing",
|
|
1508
|
+
"awaiting_permission",
|
|
1509
|
+
"awaiting_subagent",
|
|
1510
|
+
"awaiting_dag_children",
|
|
1511
|
+
"paused",
|
|
1512
|
+
"completed",
|
|
1513
|
+
"failed",
|
|
1514
|
+
"aborted"
|
|
1515
|
+
];
|
|
1516
|
+
/**
|
|
1496
1517
|
* ========================================
|
|
1497
1518
|
* Client to Server Actions
|
|
1498
1519
|
* ========================================
|
|
@@ -2690,11 +2711,36 @@ const PermissionRequestAction = z.object({
|
|
|
2690
2711
|
toolInput: z.unknown(),
|
|
2691
2712
|
iteration: z.number()
|
|
2692
2713
|
});
|
|
2714
|
+
/**
|
|
2715
|
+
* Persisted snapshot of a non-background child subagent execution. Replayed on
|
|
2716
|
+
* reconnect (#8695) so `SubagentStepNest` can re-render the nested iteration
|
|
2717
|
+
* trace under the parent's `delegate_to_agent` action step after a hard refresh
|
|
2718
|
+
* or once the parent run has terminated. Background children are excluded —
|
|
2719
|
+
* they surface via the header badge + completion toast (#8341), not inline
|
|
2720
|
+
* nesting.
|
|
2721
|
+
*
|
|
2722
|
+
* `agentName` is sourced from the child doc's `subagentConfig` (now persisted
|
|
2723
|
+
* at creation time for every subagent — see `agentExecutor.ts` `onStart`). A
|
|
2724
|
+
* generic fallback ("Subagent") is used for legacy docs without it. Empty
|
|
2725
|
+
* `steps` is valid for an in-flight in-process child whose terminal write
|
|
2726
|
+
* hasn't landed yet — the nest renders the agent header alone in that case.
|
|
2727
|
+
*/
|
|
2728
|
+
const ChildExecutionSnapshotSchema = z.object({
|
|
2729
|
+
executionId: z.string(),
|
|
2730
|
+
agentName: z.string(),
|
|
2731
|
+
model: z.string().optional(),
|
|
2732
|
+
status: z.enum(AGENT_EXECUTION_STATUSES),
|
|
2733
|
+
steps: z.array(AgentStepSchema),
|
|
2734
|
+
totalCredits: z.number().optional(),
|
|
2735
|
+
finalAnswer: z.string().optional(),
|
|
2736
|
+
error: z.string().optional(),
|
|
2737
|
+
isTimeout: z.boolean().optional()
|
|
2738
|
+
});
|
|
2693
2739
|
const ReconnectResultAction = z.object({
|
|
2694
2740
|
action: z.literal("reconnect_result"),
|
|
2695
2741
|
found: z.boolean(),
|
|
2696
2742
|
executionId: z.string().optional(),
|
|
2697
|
-
status: z.
|
|
2743
|
+
status: z.enum(AGENT_EXECUTION_STATUSES).optional(),
|
|
2698
2744
|
pendingPermission: z.object({
|
|
2699
2745
|
toolName: z.string(),
|
|
2700
2746
|
toolInput: z.unknown(),
|
|
@@ -2703,7 +2749,9 @@ const ReconnectResultAction = z.object({
|
|
|
2703
2749
|
totalCreditsUsed: z.number().optional(),
|
|
2704
2750
|
iterationCount: z.number().optional(),
|
|
2705
2751
|
steps: z.array(AgentStepSchema).optional(),
|
|
2706
|
-
stepsTruncated: z.boolean().optional()
|
|
2752
|
+
stepsTruncated: z.boolean().optional(),
|
|
2753
|
+
children: z.array(ChildExecutionSnapshotSchema).optional(),
|
|
2754
|
+
childrenTruncated: z.boolean().optional()
|
|
2707
2755
|
});
|
|
2708
2756
|
z.discriminatedUnion("action", [
|
|
2709
2757
|
DataSubscribeRequestAction,
|
|
@@ -10025,6 +10073,7 @@ const CliConfigSchema = z.object({
|
|
|
10025
10073
|
temperature: z.number(),
|
|
10026
10074
|
autoSave: z.boolean(),
|
|
10027
10075
|
autoCompact: z.boolean().optional().prefault(true),
|
|
10076
|
+
autoUpdate: z.boolean().optional(),
|
|
10028
10077
|
theme: z.enum(["light", "dark"]),
|
|
10029
10078
|
exportFormat: z.enum(["markdown", "json"]),
|
|
10030
10079
|
maxIterations: z.number().nullable().prefault(10),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
3
|
-
import {
|
|
2
|
+
import { t as version } from "../package-uvIC6spW.mjs";
|
|
3
|
+
import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-C8xsNY2L.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
|
-
import {
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
7
|
import { homedir } from "os";
|
|
8
8
|
import path from "path";
|
|
9
9
|
//#region src/commands/doctorCommand.ts
|
|
@@ -55,20 +55,16 @@ async function handleDoctorCommand() {
|
|
|
55
55
|
encoding: "utf-8",
|
|
56
56
|
timeout: 1e4
|
|
57
57
|
}).trim();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
status: "warn",
|
|
69
|
-
message: `${npmPrefix} (not writable — may need sudo for updates)`
|
|
70
|
-
});
|
|
71
|
-
}
|
|
58
|
+
if (await isNpmPrefixWritable(npmPrefix)) results.push({
|
|
59
|
+
name: "Global npm path",
|
|
60
|
+
status: "pass",
|
|
61
|
+
message: `${npmPrefix} (writable)`
|
|
62
|
+
});
|
|
63
|
+
else results.push({
|
|
64
|
+
name: "Global npm path",
|
|
65
|
+
status: "warn",
|
|
66
|
+
message: `${npmPrefix} (not writable — may need sudo for updates)`
|
|
67
|
+
});
|
|
72
68
|
} catch {
|
|
73
69
|
results.push({
|
|
74
70
|
name: "Global npm path",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, J as ReActAgent, N as loadContextFiles, P as PermissionManager, Q as SessionStore, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, X as CheckpointStore, Y as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, q as isReadOnlyTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-
|
|
3
|
-
import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-
|
|
2
|
+
import { C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, J as ReActAgent, N as loadContextFiles, P as PermissionManager, Q as SessionStore, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, X as CheckpointStore, Y as CustomCommandStore, _ as createAgentDelegateTool, b as createSkillTool, d as createFindDefinitionTool, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, k as McpManager, m as createCoordinateTaskTool, p as createWriteTodosTool, q as isReadOnlyTool, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, y as SubagentOrchestrator } from "../tools-BTPUXUNS.mjs";
|
|
3
|
+
import { n as logger, r as getApiUrl, t as ConfigStore } from "../ConfigStore-B9I7UHuG.mjs";
|
|
4
4
|
import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
|
|
5
5
|
import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
|
|
6
6
|
import { t as SandboxOrchestrator } from "../SandboxOrchestrator-BoINxbX4.mjs";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
3
|
-
import {
|
|
2
|
+
import { t as version } from "../package-uvIC6spW.mjs";
|
|
3
|
+
import { c as isNpmPrefixWritable, l as setAutoUpdatePreference, n as REEXEC_GUARD_ENV, o as forceCheckForUpdate, r as checkForUpdate, s as getAutoUpdatePreference, t as INSTALL_CMD, u as shouldAttemptAutoUpdate } from "../updateChecker-C8xsNY2L.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
|
-
import { execSync } from "child_process";
|
|
5
|
+
import { execSync, spawnSync } from "child_process";
|
|
6
|
+
import { createInterface } from "readline";
|
|
6
7
|
//#region src/commands/updateCommand.ts
|
|
7
8
|
/**
|
|
8
9
|
* External update command (b4m update)
|
|
9
10
|
* Checks for and installs CLI updates.
|
|
10
11
|
* Runs outside the interactive CLI session.
|
|
11
12
|
*/
|
|
12
|
-
const INSTALL_CMD = "npm install -g @bike4mind/cli@latest";
|
|
13
13
|
function runGlobalInstall() {
|
|
14
14
|
try {
|
|
15
15
|
execSync(INSTALL_CMD, {
|
|
@@ -25,6 +25,121 @@ function runGlobalInstall() {
|
|
|
25
25
|
return false;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Ask the user how to handle an available update (the `'ask'` preference).
|
|
30
|
+
* Plain readline (not Ink) because this runs in the bin bootstrap before the
|
|
31
|
+
* code-split app loads — the only window in which it's safe to install.
|
|
32
|
+
*
|
|
33
|
+
* Maps the [U/a/s/n] keys to a choice; an empty line defaults to `'update'`
|
|
34
|
+
* (capital `U` in the prompt is the default). The interface is fully torn down
|
|
35
|
+
* before returning so a Skip/Never fall-through leaves stdin clean for Ink.
|
|
36
|
+
*/
|
|
37
|
+
async function promptUpdateChoice(currentVersion, latestVersion) {
|
|
38
|
+
if (!process.stdin.isTTY) return "skip";
|
|
39
|
+
console.log(`\n\x1b[33m ⬆ Update available: v${currentVersion} → v${latestVersion}\x1b[0m\n`);
|
|
40
|
+
console.log(" How would you like to handle updates?");
|
|
41
|
+
console.log(" [U] Update once (install now)");
|
|
42
|
+
console.log(" [A] Always (auto-update silently from now on)");
|
|
43
|
+
console.log(" [S] Skip (not now — ask again next launch)");
|
|
44
|
+
console.log(" [N] Never (manual `b4m update` only)\n");
|
|
45
|
+
const rl = createInterface({
|
|
46
|
+
input: process.stdin,
|
|
47
|
+
output: process.stdout
|
|
48
|
+
});
|
|
49
|
+
let answer;
|
|
50
|
+
try {
|
|
51
|
+
answer = await new Promise((resolve) => {
|
|
52
|
+
rl.on("close", () => resolve(null));
|
|
53
|
+
rl.question(" Choose [U/a/s/n]: ", resolve);
|
|
54
|
+
});
|
|
55
|
+
} finally {
|
|
56
|
+
rl.close();
|
|
57
|
+
}
|
|
58
|
+
if (answer === null) return "skip";
|
|
59
|
+
switch (answer.trim().toLowerCase()) {
|
|
60
|
+
case "a": return "always";
|
|
61
|
+
case "s": return "skip";
|
|
62
|
+
case "n": return "never";
|
|
63
|
+
default: return "update";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install the latest version and re-exec into it so the session the user just
|
|
68
|
+
* opened runs the new code — zero version skew, no mid-session file-swap risk.
|
|
69
|
+
*
|
|
70
|
+
* spawnSync inherits the TTY so Ink renders normally in the child; the guard
|
|
71
|
+
* env prevents an update loop. npm install overwrites the global package in
|
|
72
|
+
* place, so re-running the same argv[1] bin path picks up the new code. On a
|
|
73
|
+
* successful spawn this never returns (it calls process.exit); it only returns
|
|
74
|
+
* when the install itself fails, so the caller falls through to the current
|
|
75
|
+
* version.
|
|
76
|
+
*/
|
|
77
|
+
function installAndReexec(currentVersion, latestVersion) {
|
|
78
|
+
console.log(`\x1b[33m ⬆ Updating v${currentVersion} → v${latestVersion}…\x1b[0m`);
|
|
79
|
+
if (!runGlobalInstall()) return;
|
|
80
|
+
const child = spawnSync(process.argv[0], process.argv.slice(1), {
|
|
81
|
+
stdio: "inherit",
|
|
82
|
+
env: {
|
|
83
|
+
...process.env,
|
|
84
|
+
[REEXEC_GUARD_ENV]: "1"
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (child.error) {
|
|
88
|
+
console.error(`\n Failed to launch the updated version: ${child.error.message}`);
|
|
89
|
+
console.error(" Re-run b4m to use the newly installed version.");
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
process.exit(child.status ?? (child.signal ? 1 : 0));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Auto-update on launch (Claude-Code-style), consent-first.
|
|
96
|
+
*
|
|
97
|
+
* Called from the bin bootstrap on the interactive path *before* the
|
|
98
|
+
* code-split app (`dist/index.mjs`) is imported — the only safe install window
|
|
99
|
+
* (running an install while dist chunks are loaded would crash them).
|
|
100
|
+
*
|
|
101
|
+
* Behaviour by `autoUpdate` preference once an update is available on a
|
|
102
|
+
* writable prefix:
|
|
103
|
+
* - `'auto'` → install silently and re-exec into the new version.
|
|
104
|
+
* - `'never'` → do nothing (the startup notify banner still informs).
|
|
105
|
+
* - `'ask'` → prompt the user: Update once / Always (persist `auto`) /
|
|
106
|
+
* Skip (ask again next launch) / Never (persist `never`).
|
|
107
|
+
*
|
|
108
|
+
* It is a safe no-op (returns without installing) when: already re-exec'd this
|
|
109
|
+
* launch, disabled via `B4M_AUTO_UPDATE=0`, not attached to a TTY, no update is
|
|
110
|
+
* available, the prefix needs sudo (notify banner informs instead), or the
|
|
111
|
+
* user declines the prompt. Any install/network failure is swallowed so the
|
|
112
|
+
* user is never blocked from launching the current version.
|
|
113
|
+
*/
|
|
114
|
+
async function maybeAutoUpdateOnLaunch() {
|
|
115
|
+
if (!shouldAttemptAutoUpdate({ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY) })) return;
|
|
116
|
+
const preference = await getAutoUpdatePreference();
|
|
117
|
+
if (preference === "never") return;
|
|
118
|
+
const currentVersion = version;
|
|
119
|
+
let result;
|
|
120
|
+
let timeoutHandle;
|
|
121
|
+
try {
|
|
122
|
+
result = await Promise.race([checkForUpdate(currentVersion), new Promise((resolve) => {
|
|
123
|
+
timeoutHandle = setTimeout(() => resolve(null), 3e3);
|
|
124
|
+
})]);
|
|
125
|
+
} catch {
|
|
126
|
+
return;
|
|
127
|
+
} finally {
|
|
128
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
129
|
+
}
|
|
130
|
+
if (!result?.updateAvailable) return;
|
|
131
|
+
if (!await isNpmPrefixWritable()) return;
|
|
132
|
+
if (preference === "ask") {
|
|
133
|
+
const choice = await promptUpdateChoice(currentVersion, result.latestVersion);
|
|
134
|
+
if (choice === "skip") return;
|
|
135
|
+
if (choice === "never") {
|
|
136
|
+
await setAutoUpdatePreference(false);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (choice === "always") await setAutoUpdatePreference(true);
|
|
140
|
+
}
|
|
141
|
+
installAndReexec(currentVersion, result.latestVersion);
|
|
142
|
+
}
|
|
28
143
|
async function handleUpdateCommand() {
|
|
29
144
|
const currentVersion = version;
|
|
30
145
|
console.log(`Current version: v${currentVersion}`);
|
|
@@ -56,4 +171,4 @@ async function handleUpdateCommand() {
|
|
|
56
171
|
else console.log("\nRipgrep restored.");
|
|
57
172
|
}
|
|
58
173
|
//#endregion
|
|
59
|
-
export { handleUpdateCommand };
|
|
174
|
+
export { handleUpdateCommand, maybeAutoUpdateOnLaunch, promptUpdateChoice };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as OAuthClient, A as substituteArguments, B as DEFAULT_THOROUGHNESS, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, H as registerFeatureModuleTools, I as ALWAYS_DENIED_FOR_AGENTS, J as ReActAgent, K as buildSkillsPromptSection, L as DEFAULT_AGENT_MODEL, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as SessionStore, R as DEFAULT_MAX_ITERATIONS, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, V as clearFeatureModuleTools, W as getPlanModeFilePath, X as CheckpointStore, Y as CustomCommandStore, Z as CommandHistoryStore, _ as createAgentDelegateTool, a as createBlockerTools, at as searchFiles, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as hasFileReferences, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as formatFileSize, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as searchCommands, o as formatBlockersOutput, ot as warmFileCache, p as createWriteTodosTool, q as isReadOnlyTool, r as formatReviewGatesOutput, rt as mergeCommands, s as createDecisionLogTool, t as createReviewGateStore, tt as processFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_RETRY_CONFIG } from "./tools-
|
|
2
|
+
import { $ as OAuthClient, A as substituteArguments, B as DEFAULT_THOROUGHNESS, C as WebSocketToolExecutor, D as ServerLlmBackend, E as WebSocketLlmBackend, F as generateCliTools, G as buildSystemPrompt, H as registerFeatureModuleTools, I as ALWAYS_DENIED_FOR_AGENTS, J as ReActAgent, K as buildSkillsPromptSection, L as DEFAULT_AGENT_MODEL, M as extractCompactInstructions, N as loadContextFiles, O as isTransientNetworkError, P as PermissionManager, Q as SessionStore, R as DEFAULT_MAX_ITERATIONS, S as ApiClient, T as FallbackLlmBackend, U as setWebSocketToolExecutor, V as clearFeatureModuleTools, W as getPlanModeFilePath, X as CheckpointStore, Y as CustomCommandStore, Z as CommandHistoryStore, _ as createAgentDelegateTool, a as createBlockerTools, at as searchFiles, b as createSkillTool, c as createDecisionStore, d as createFindDefinitionTool, et as hasFileReferences, f as createTodoStore, g as BackgroundAgentManager, h as createBackgroundAgentTools, i as createBlockerStore, it as formatFileSize, j as formatStep, k as McpManager, l as formatDecisionsOutput, m as createCoordinateTaskTool, n as createReviewGateTool, nt as searchCommands, o as formatBlockersOutput, ot as warmFileCache, p as createWriteTodosTool, q as isReadOnlyTool, r as formatReviewGatesOutput, rt as mergeCommands, s as createDecisionLogTool, t as createReviewGateStore, tt as processFileReferences, u as createGetFileStructureTool, v as AgentStore, w as WebSocketConnectionManager, x as parseAgentConfig, y as SubagentOrchestrator, z as DEFAULT_RETRY_CONFIG } from "./tools-BTPUXUNS.mjs";
|
|
3
3
|
import { n as useCliStore, t as selectActiveBackgroundAgents } from "./store-DV5s-qni.mjs";
|
|
4
|
-
import { Ut as validateJupyterKernelName, Wt as validateNotebookPath$1, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-
|
|
5
|
-
import { t as version } from "./package-
|
|
6
|
-
import {
|
|
4
|
+
import { Ut as validateJupyterKernelName, Wt as validateNotebookPath$1, g as CREDIT_DEDUCT_TRANSACTION_TYPES, i as getEnvironmentName, n as logger, r as getApiUrl, t as ConfigStore, v as ChatModels } from "./ConfigStore-B9I7UHuG.mjs";
|
|
5
|
+
import { t as version } from "./package-uvIC6spW.mjs";
|
|
6
|
+
import { r as checkForUpdate } from "./updateChecker-C8xsNY2L.mjs";
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
8
8
|
import { Box, Static, Text, render, useApp, useInput, usePaste, useStdout } from "ink";
|
|
9
9
|
import { execSync } from "child_process";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as sanitizeTelemetryError, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, Gt as buildRateLimitLogEntry, H as KnowledgeType, Ht as settingsMap, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as parseRateLimitHeaders, K as ModelBackend, Kt as extractSnippetMeta, L as ImageModels, Lt as isZodError, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as obfuscateApiKey, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, V as InviteType, Vt as secureParameters, W as MiscEvents, X as Permission, Y as OpenAIImageGenerationInput, Yt as CollectionType, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as isNearLimit, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as resolveNavigationIntents } from "./ConfigStore-
|
|
2
|
+
import { $ as ProjectEvents, A as GenerateImageToolCallSchema, At as dayjsConfig_default, B as InviteEvents, Bt as sanitizeTelemetryError, C as ElabsEvents, Ct as UnauthorizedError, D as ForbiddenError, Dt as VideoModels, E as FileEvents, Et as VideoGenerationUsageTransaction, F as ImageEditUsageTransaction, Ft as isGPTImage2Model, G as ModalEvents, Gt as buildRateLimitLogEntry, H as KnowledgeType, Ht as settingsMap, I as ImageGenerationUsageTransaction, It as isGPTImageModel, J as OpenAIEmbeddingModel, Jt as parseRateLimitHeaders, K as ModelBackend, Kt as extractSnippetMeta, L as ImageModels, Lt as isZodError, M as GenericCreditDeductTransaction, Mt as getDataLakeTags, N as HTTPError, Nt as getMcpProviderMetadata, O as FriendshipEvents, Ot as XAI_IMAGE_MODELS, P as HttpStatus, Pt as getViewById, Q as ProfileEvents, R as InboxEvents, Rt as obfuscateApiKey, S as DashboardParamsSchema, St as UiNavigationEvents, T as FeedbackEvents, Tt as VIDEO_SIZE_CONSTRAINTS, U as LLMEvents, V as InviteType, Vt as secureParameters, W as MiscEvents, X as Permission, Y as OpenAIImageGenerationInput, Yt as CollectionType, Z as PermissionDeniedError, _ as ChatCompletionCreateInputSchema, _t as TaskScheduleHandler, a as ALERT_THRESHOLDS, at as ReceivedCreditTransaction, b as CompletionApiUsageTransaction, bt as ToolUsageTransaction, c as ApiKeyScope, ct as ResearchModeParamsSchema, d as ArtifactTypeSchema, dt as ResearchTaskType, et as PromptIntentSchema, f as AuthEvents, ft as SessionEvents, gt as TagType, h as BadRequestError, ht as SupportedFabFileMimeTypes, it as RealtimeVoiceUsageTransaction, j as GenericCreditAddTransaction, jt as getAccessibleDataLakes, k as GEMINI_IMAGE_MODELS, kt as b4mLLMTools, l as ApiKeyType, lt as ResearchTaskExecutionType, m as BFL_SAFETY_TOLERANCE, mt as SubscriptionCreditTransaction, n as logger, nt as PurchaseTransaction, o as AiEvents, ot as RechartsChartTypeList, p as BFL_IMAGE_MODELS, pt as SpeechToTextUsageTransaction, q as NotFoundError, qt as isNearLimit, rt as QuestMasterParamsSchema, s as ApiKeyEvents, st as RegInviteEvents, t as ConfigStore, tt as PromptMetaZodSchema, u as AppFileEvents, ut as ResearchTaskPeriodicFrequencyType, v as ChatModels, vt as TextGenerationUsageTransaction, w as FavoriteDocumentType, wt as UnprocessableEntityError, x as CorruptedFileError, xt as TransferCreditTransaction, y as ClaudeArtifactMimeTypes, yt as TooManyRequestsError, z as InternalServerError, zt as resolveNavigationIntents } from "./ConfigStore-B9I7UHuG.mjs";
|
|
3
3
|
import { a as isUserLockedOut, c as userCanDisableMFA, d as userRequiresMFA, f as verifyBackupCode, i as getLockoutTimeRemaining, l as userEligibleForMFA, n as generateBackupCodes, o as recordFailedAttempt, p as verifyTOTPToken, r as generateTOTPSetup, s as shouldResetFailedAttempts, t as clearFailedAttempts, u as userHasMFAConfigured } from "./utils-PpNti-tY.mjs";
|
|
4
4
|
import { n as isPathAllowed, t as assertPathAllowed } from "./pathValidation-D8tjkQXE-1HwvsuYT.mjs";
|
|
5
|
-
import { t as version } from "./package-
|
|
5
|
+
import { t as version } from "./package-uvIC6spW.mjs";
|
|
6
6
|
import { execFile, execFileSync, spawn } from "child_process";
|
|
7
7
|
import crypto, { createHash, randomBytes } from "crypto";
|
|
8
8
|
import { existsSync, promises, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -2994,6 +2994,168 @@ function trimConversationHistory(messages, maxIterations, protectedPrefixCount)
|
|
|
2994
2994
|
const keepFrom = dynamicStart + nudgeIndices[nudgeIndices.length - maxIterations];
|
|
2995
2995
|
messages.splice(dynamicStart, keepFrom - dynamicStart);
|
|
2996
2996
|
}
|
|
2997
|
+
/**
|
|
2998
|
+
* Per-node failure policy.
|
|
2999
|
+
*
|
|
3000
|
+
* `cascade`: if this node fails, all transitive dependents are marked
|
|
3001
|
+
* `cascade_failed` and skipped. (Default.)
|
|
3002
|
+
*
|
|
3003
|
+
* `isolate`: if this node fails, dependents proceed; their dependency
|
|
3004
|
+
* context for this node is null/missing. Used for fan-out research nodes
|
|
3005
|
+
* where one branch failing shouldn't poison the synthesis.
|
|
3006
|
+
*/
|
|
3007
|
+
const FailurePolicySchema = z.enum(["cascade", "isolate"]);
|
|
3008
|
+
const DecomposedTaskSchema = z.object({
|
|
3009
|
+
id: z.string().min(1),
|
|
3010
|
+
description: z.string().min(1),
|
|
3011
|
+
agentType: z.enum([
|
|
3012
|
+
"explore",
|
|
3013
|
+
"plan",
|
|
3014
|
+
"general-purpose",
|
|
3015
|
+
"review",
|
|
3016
|
+
"test"
|
|
3017
|
+
]),
|
|
3018
|
+
dependsOn: z.array(z.string()).default([]),
|
|
3019
|
+
onFailure: FailurePolicySchema.default("cascade")
|
|
3020
|
+
});
|
|
3021
|
+
const DecomposeTaskInputSchema = z.object({ tasks: z.array(DecomposedTaskSchema).min(1, "At least one task is required") });
|
|
3022
|
+
/**
|
|
3023
|
+
* Validate the task graph and return the topological execution levels.
|
|
3024
|
+
*
|
|
3025
|
+
* - All dependency references must point to existing tasks
|
|
3026
|
+
* - No task can depend on itself
|
|
3027
|
+
* - No circular dependencies
|
|
3028
|
+
* - No duplicate task ids
|
|
3029
|
+
*
|
|
3030
|
+
* Throws on any violation. Returns levels (each level's tasks have no
|
|
3031
|
+
* inter-dependencies and can run concurrently).
|
|
3032
|
+
*/
|
|
3033
|
+
function validateAndSort(input) {
|
|
3034
|
+
const tasks = /* @__PURE__ */ new Map();
|
|
3035
|
+
for (const task of input.tasks) {
|
|
3036
|
+
if (tasks.has(task.id)) throw new Error(`Duplicate task id: "${task.id}"`);
|
|
3037
|
+
tasks.set(task.id, task);
|
|
3038
|
+
}
|
|
3039
|
+
for (const [taskId, task] of tasks) for (const depId of task.dependsOn) {
|
|
3040
|
+
if (!tasks.has(depId)) throw new Error(`Task "${taskId}" depends on unknown task "${depId}"`);
|
|
3041
|
+
if (depId === taskId) throw new Error(`Task "${taskId}" cannot depend on itself`);
|
|
3042
|
+
}
|
|
3043
|
+
detectCycles(tasks);
|
|
3044
|
+
return topologicalSort(tasks);
|
|
3045
|
+
}
|
|
3046
|
+
function detectCycles(tasks) {
|
|
3047
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3048
|
+
const inStack = /* @__PURE__ */ new Set();
|
|
3049
|
+
for (const taskId of tasks.keys()) {
|
|
3050
|
+
if (visited.has(taskId)) continue;
|
|
3051
|
+
const stack = [{
|
|
3052
|
+
id: taskId,
|
|
3053
|
+
phase: "enter"
|
|
3054
|
+
}];
|
|
3055
|
+
while (stack.length > 0) {
|
|
3056
|
+
const { id, phase } = stack.pop();
|
|
3057
|
+
if (phase === "exit") {
|
|
3058
|
+
inStack.delete(id);
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
3061
|
+
if (inStack.has(id)) throw new Error(`Circular dependency detected involving task "${id}"`);
|
|
3062
|
+
if (visited.has(id)) continue;
|
|
3063
|
+
visited.add(id);
|
|
3064
|
+
inStack.add(id);
|
|
3065
|
+
stack.push({
|
|
3066
|
+
id,
|
|
3067
|
+
phase: "exit"
|
|
3068
|
+
});
|
|
3069
|
+
const task = tasks.get(id);
|
|
3070
|
+
for (const depId of task.dependsOn) {
|
|
3071
|
+
if (inStack.has(depId)) throw new Error(`Circular dependency detected: "${id}" → "${depId}"`);
|
|
3072
|
+
if (!visited.has(depId)) stack.push({
|
|
3073
|
+
id: depId,
|
|
3074
|
+
phase: "enter"
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Kahn's algorithm — group tasks into execution levels.
|
|
3082
|
+
* Tasks within a level have no inter-dependencies.
|
|
3083
|
+
*/
|
|
3084
|
+
function topologicalSort(tasks) {
|
|
3085
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
3086
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
3087
|
+
for (const [taskId, task] of tasks) {
|
|
3088
|
+
inDegree.set(taskId, task.dependsOn.length);
|
|
3089
|
+
for (const depId of task.dependsOn) {
|
|
3090
|
+
const deps = dependents.get(depId) || [];
|
|
3091
|
+
deps.push(taskId);
|
|
3092
|
+
dependents.set(depId, deps);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
const levels = [];
|
|
3096
|
+
let currentLevel = [...tasks.keys()].filter((id) => inDegree.get(id) === 0);
|
|
3097
|
+
while (currentLevel.length > 0) {
|
|
3098
|
+
levels.push(currentLevel);
|
|
3099
|
+
const nextLevel = [];
|
|
3100
|
+
for (const taskId of currentLevel) for (const dependent of dependents.get(taskId) || []) {
|
|
3101
|
+
const newDegree = inDegree.get(dependent) - 1;
|
|
3102
|
+
inDegree.set(dependent, newDegree);
|
|
3103
|
+
if (newDegree === 0) nextLevel.push(dependent);
|
|
3104
|
+
}
|
|
3105
|
+
currentLevel = nextLevel;
|
|
3106
|
+
}
|
|
3107
|
+
return levels;
|
|
3108
|
+
}
|
|
3109
|
+
const DEFAULT_MAX_RESULT_CHARS = 800;
|
|
3110
|
+
/**
|
|
3111
|
+
* Build the final markdown summary + structured result for a DAG execution.
|
|
3112
|
+
*
|
|
3113
|
+
* Shared between the CLI's in-process executor and the web's Lambda-fan-out
|
|
3114
|
+
* executor so the synthesized tool_result that the parent agent sees has
|
|
3115
|
+
* consistent shape across surfaces.
|
|
3116
|
+
*/
|
|
3117
|
+
function buildPipelineResult(taskResults, options = {}) {
|
|
3118
|
+
const maxLen = options.maxResultChars ?? DEFAULT_MAX_RESULT_CHARS;
|
|
3119
|
+
const completed = taskResults.filter((t) => t.status === "completed");
|
|
3120
|
+
const failed = taskResults.filter((t) => t.status === "failed");
|
|
3121
|
+
const cascadeFailed = taskResults.filter((t) => t.status === "cascade_failed");
|
|
3122
|
+
const pending = taskResults.filter((t) => t.status === "pending" || t.status === "running");
|
|
3123
|
+
const success = failed.length === 0 && cascadeFailed.length === 0 && pending.length === 0;
|
|
3124
|
+
const summaryParts = [];
|
|
3125
|
+
if (completed.length > 0) {
|
|
3126
|
+
summaryParts.push(`## Completed Tasks (${completed.length}/${taskResults.length})\n`);
|
|
3127
|
+
for (const task of completed) {
|
|
3128
|
+
summaryParts.push(`### ${task.id}: ${task.description}\n`);
|
|
3129
|
+
summaryParts.push(`*Agent: ${task.agentType}*\n`);
|
|
3130
|
+
if (task.result) {
|
|
3131
|
+
const truncated = task.result.length > maxLen ? task.result.slice(0, maxLen) + "\n...(truncated)" : task.result;
|
|
3132
|
+
summaryParts.push(truncated);
|
|
3133
|
+
}
|
|
3134
|
+
summaryParts.push("");
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (failed.length > 0) {
|
|
3138
|
+
summaryParts.push(`## Failed Tasks (${failed.length})\n`);
|
|
3139
|
+
for (const task of failed) {
|
|
3140
|
+
summaryParts.push(`### ${task.id}: ${task.description}\n`);
|
|
3141
|
+
summaryParts.push(`*Agent: ${task.agentType}*\n`);
|
|
3142
|
+
summaryParts.push(`**Error:** ${task.error}\n`);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (cascadeFailed.length > 0) {
|
|
3146
|
+
summaryParts.push(`## Cascade-Failed Tasks (${cascadeFailed.length})\n`);
|
|
3147
|
+
for (const task of cascadeFailed) summaryParts.push(`- ${task.id}: ${task.description} — ${task.error}\n`);
|
|
3148
|
+
}
|
|
3149
|
+
if (pending.length > 0) {
|
|
3150
|
+
summaryParts.push(`## Skipped Tasks (${pending.length})\n`);
|
|
3151
|
+
for (const task of pending) summaryParts.push(`- ${task.id}: ${task.description}\n`);
|
|
3152
|
+
}
|
|
3153
|
+
return {
|
|
3154
|
+
success,
|
|
3155
|
+
taskResults,
|
|
3156
|
+
summary: summaryParts.join("\n")
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
2997
3159
|
String.raw`
|
|
2998
3160
|
const { parentPort } = require('node:worker_threads');
|
|
2999
3161
|
const vm = require('node:vm');
|
|
@@ -12071,8 +12233,8 @@ No markdown, no explanation, no code blocks — just the raw JSON object.`;
|
|
|
12071
12233
|
"Minimize: sum(error^2)"
|
|
12072
12234
|
].join("\n");
|
|
12073
12235
|
//#endregion
|
|
12074
|
-
//#region ../../b4m-core/services/dist/tools-
|
|
12075
|
-
async function performDeepResearch(context, params, config) {
|
|
12236
|
+
//#region ../../b4m-core/services/dist/tools-CTdKqJPY.mjs
|
|
12237
|
+
async function performDeepResearch(context, params, config = {}) {
|
|
12076
12238
|
const maxDepth = config.maxDepth || 7;
|
|
12077
12239
|
const duration = config.duration || 4.5;
|
|
12078
12240
|
const startTime = Date.now();
|
|
@@ -12400,7 +12562,7 @@ const deepResearchTool = {
|
|
|
12400
12562
|
toolFn: async (value) => {
|
|
12401
12563
|
const params = value;
|
|
12402
12564
|
await context.onStart?.("deep_research", params);
|
|
12403
|
-
const result = await performDeepResearch(context, { topic: params.topic }, config);
|
|
12565
|
+
const result = await performDeepResearch(context, { topic: params.topic }, config ?? {});
|
|
12404
12566
|
return JSON.stringify(result);
|
|
12405
12567
|
},
|
|
12406
12568
|
toolSchema: {
|
|
@@ -26154,249 +26316,6 @@ function createBackgroundAgentTools(manager) {
|
|
|
26154
26316
|
];
|
|
26155
26317
|
}
|
|
26156
26318
|
//#endregion
|
|
26157
|
-
//#region src/agents/TaskPipeline.ts
|
|
26158
|
-
/**
|
|
26159
|
-
* Schema for a single decomposed task in the pipeline
|
|
26160
|
-
*/
|
|
26161
|
-
const DecomposedTaskSchema = z.object({
|
|
26162
|
-
/** Unique identifier for this task within the pipeline */
|
|
26163
|
-
id: z.string().min(1),
|
|
26164
|
-
/** Human-readable description of what needs to be done */
|
|
26165
|
-
description: z.string().min(1),
|
|
26166
|
-
/** Which agent type should handle this task */
|
|
26167
|
-
agentType: z.enum([
|
|
26168
|
-
"explore",
|
|
26169
|
-
"plan",
|
|
26170
|
-
"general-purpose",
|
|
26171
|
-
"review",
|
|
26172
|
-
"test"
|
|
26173
|
-
]),
|
|
26174
|
-
/** IDs of tasks that must complete before this one can start */
|
|
26175
|
-
dependsOn: z.array(z.string()).default([])
|
|
26176
|
-
});
|
|
26177
|
-
/**
|
|
26178
|
-
* Schema for the decompose_task tool input
|
|
26179
|
-
*/
|
|
26180
|
-
const DecomposeTaskInputSchema = z.object({ tasks: z.array(DecomposedTaskSchema).min(1, "At least one task is required") });
|
|
26181
|
-
/**
|
|
26182
|
-
* TaskPipeline builds and executes a DAG of tasks with dependency ordering.
|
|
26183
|
-
*
|
|
26184
|
-
* Tasks are executed in topological order. Independent tasks at the same
|
|
26185
|
-
* level can be executed in parallel via the executor callback.
|
|
26186
|
-
*/
|
|
26187
|
-
var TaskPipeline = class {
|
|
26188
|
-
constructor(input) {
|
|
26189
|
-
this.tasks = /* @__PURE__ */ new Map();
|
|
26190
|
-
for (const task of input.tasks) this.tasks.set(task.id, {
|
|
26191
|
-
...task,
|
|
26192
|
-
status: "pending"
|
|
26193
|
-
});
|
|
26194
|
-
this.validate();
|
|
26195
|
-
this.executionOrder = this.topologicalSort();
|
|
26196
|
-
}
|
|
26197
|
-
/**
|
|
26198
|
-
* Get the number of tasks in the pipeline
|
|
26199
|
-
*/
|
|
26200
|
-
get size() {
|
|
26201
|
-
return this.tasks.size;
|
|
26202
|
-
}
|
|
26203
|
-
/**
|
|
26204
|
-
* Get the execution levels (groups of tasks that can run in parallel)
|
|
26205
|
-
*/
|
|
26206
|
-
getExecutionLevels() {
|
|
26207
|
-
return this.executionOrder;
|
|
26208
|
-
}
|
|
26209
|
-
/**
|
|
26210
|
-
* Check if this pipeline contains a single task (skip pipeline overhead)
|
|
26211
|
-
*/
|
|
26212
|
-
isSingleTask() {
|
|
26213
|
-
return this.tasks.size === 1;
|
|
26214
|
-
}
|
|
26215
|
-
/**
|
|
26216
|
-
* Get the single task (for fallback when pipeline has only one task)
|
|
26217
|
-
*/
|
|
26218
|
-
getSingleTask() {
|
|
26219
|
-
if (!this.isSingleTask()) throw new Error("Pipeline has more than one task");
|
|
26220
|
-
return [...this.tasks.values()][0];
|
|
26221
|
-
}
|
|
26222
|
-
/**
|
|
26223
|
-
* Execute all tasks in dependency order.
|
|
26224
|
-
*
|
|
26225
|
-
* Tasks within the same level are executed in parallel.
|
|
26226
|
-
* Each level must complete before the next level starts.
|
|
26227
|
-
* If a task fails, all transitive dependents are cascade-failed.
|
|
26228
|
-
*/
|
|
26229
|
-
async execute(executor) {
|
|
26230
|
-
const results = /* @__PURE__ */ new Map();
|
|
26231
|
-
const failedTaskIds = /* @__PURE__ */ new Set();
|
|
26232
|
-
for (const level of this.executionOrder) {
|
|
26233
|
-
const levelPromises = level.map(async (taskId) => {
|
|
26234
|
-
const task = this.tasks.get(taskId);
|
|
26235
|
-
const failedDep = task.dependsOn.find((depId) => failedTaskIds.has(depId));
|
|
26236
|
-
if (failedDep) {
|
|
26237
|
-
task.status = "cascade_failed";
|
|
26238
|
-
task.error = `Blocked by failed dependency "${failedDep}"`;
|
|
26239
|
-
failedTaskIds.add(taskId);
|
|
26240
|
-
return;
|
|
26241
|
-
}
|
|
26242
|
-
task.status = "running";
|
|
26243
|
-
const depResults = /* @__PURE__ */ new Map();
|
|
26244
|
-
for (const depId of task.dependsOn) {
|
|
26245
|
-
const depResult = results.get(depId);
|
|
26246
|
-
if (depResult) depResults.set(depId, depResult);
|
|
26247
|
-
}
|
|
26248
|
-
try {
|
|
26249
|
-
const result = await executor(task, depResults);
|
|
26250
|
-
task.status = "completed";
|
|
26251
|
-
task.result = result;
|
|
26252
|
-
results.set(taskId, result);
|
|
26253
|
-
} catch (error) {
|
|
26254
|
-
task.status = "failed";
|
|
26255
|
-
task.error = error instanceof Error ? error.message : String(error);
|
|
26256
|
-
failedTaskIds.add(taskId);
|
|
26257
|
-
}
|
|
26258
|
-
});
|
|
26259
|
-
await Promise.all(levelPromises);
|
|
26260
|
-
}
|
|
26261
|
-
return this.buildResult();
|
|
26262
|
-
}
|
|
26263
|
-
/**
|
|
26264
|
-
* Validate the task graph:
|
|
26265
|
-
* - All dependency references point to existing tasks
|
|
26266
|
-
* - No circular dependencies
|
|
26267
|
-
*/
|
|
26268
|
-
validate() {
|
|
26269
|
-
for (const [taskId, task] of this.tasks) for (const depId of task.dependsOn) {
|
|
26270
|
-
if (!this.tasks.has(depId)) throw new Error(`Task "${taskId}" depends on unknown task "${depId}"`);
|
|
26271
|
-
if (depId === taskId) throw new Error(`Task "${taskId}" cannot depend on itself`);
|
|
26272
|
-
}
|
|
26273
|
-
this.detectCycles();
|
|
26274
|
-
}
|
|
26275
|
-
/**
|
|
26276
|
-
* Detect cycles in the dependency graph using iterative DFS
|
|
26277
|
-
*/
|
|
26278
|
-
detectCycles() {
|
|
26279
|
-
const visited = /* @__PURE__ */ new Set();
|
|
26280
|
-
const inStack = /* @__PURE__ */ new Set();
|
|
26281
|
-
for (const taskId of this.tasks.keys()) {
|
|
26282
|
-
if (visited.has(taskId)) continue;
|
|
26283
|
-
const stack = [{
|
|
26284
|
-
id: taskId,
|
|
26285
|
-
phase: "enter"
|
|
26286
|
-
}];
|
|
26287
|
-
while (stack.length > 0) {
|
|
26288
|
-
const { id, phase } = stack.pop();
|
|
26289
|
-
if (phase === "exit") {
|
|
26290
|
-
inStack.delete(id);
|
|
26291
|
-
continue;
|
|
26292
|
-
}
|
|
26293
|
-
if (inStack.has(id)) throw new Error(`Circular dependency detected involving task "${id}"`);
|
|
26294
|
-
if (visited.has(id)) continue;
|
|
26295
|
-
visited.add(id);
|
|
26296
|
-
inStack.add(id);
|
|
26297
|
-
stack.push({
|
|
26298
|
-
id,
|
|
26299
|
-
phase: "exit"
|
|
26300
|
-
});
|
|
26301
|
-
const task = this.tasks.get(id);
|
|
26302
|
-
for (const depId of task.dependsOn) {
|
|
26303
|
-
if (inStack.has(depId)) throw new Error(`Circular dependency detected: "${id}" → "${depId}"`);
|
|
26304
|
-
if (!visited.has(depId)) stack.push({
|
|
26305
|
-
id: depId,
|
|
26306
|
-
phase: "enter"
|
|
26307
|
-
});
|
|
26308
|
-
}
|
|
26309
|
-
}
|
|
26310
|
-
}
|
|
26311
|
-
}
|
|
26312
|
-
/**
|
|
26313
|
-
* Topological sort that groups tasks into execution levels.
|
|
26314
|
-
*
|
|
26315
|
-
* Each level contains tasks whose dependencies are all in earlier levels.
|
|
26316
|
-
* Tasks within a level can be executed in parallel.
|
|
26317
|
-
*
|
|
26318
|
-
* Uses Kahn's algorithm (BFS-based) for level detection.
|
|
26319
|
-
*/
|
|
26320
|
-
topologicalSort() {
|
|
26321
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
26322
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
26323
|
-
for (const [taskId, task] of this.tasks) {
|
|
26324
|
-
inDegree.set(taskId, task.dependsOn.length);
|
|
26325
|
-
for (const depId of task.dependsOn) {
|
|
26326
|
-
const deps = dependents.get(depId) || [];
|
|
26327
|
-
deps.push(taskId);
|
|
26328
|
-
dependents.set(depId, deps);
|
|
26329
|
-
}
|
|
26330
|
-
}
|
|
26331
|
-
const levels = [];
|
|
26332
|
-
let currentLevel = [...this.tasks.keys()].filter((id) => inDegree.get(id) === 0);
|
|
26333
|
-
while (currentLevel.length > 0) {
|
|
26334
|
-
levels.push(currentLevel);
|
|
26335
|
-
const nextLevel = [];
|
|
26336
|
-
for (const taskId of currentLevel) for (const dependent of dependents.get(taskId) || []) {
|
|
26337
|
-
const newDegree = inDegree.get(dependent) - 1;
|
|
26338
|
-
inDegree.set(dependent, newDegree);
|
|
26339
|
-
if (newDegree === 0) nextLevel.push(dependent);
|
|
26340
|
-
}
|
|
26341
|
-
currentLevel = nextLevel;
|
|
26342
|
-
}
|
|
26343
|
-
return levels;
|
|
26344
|
-
}
|
|
26345
|
-
/**
|
|
26346
|
-
* Build the final execution result
|
|
26347
|
-
*/
|
|
26348
|
-
buildResult() {
|
|
26349
|
-
const taskResults = [...this.tasks.values()].map((task) => ({
|
|
26350
|
-
id: task.id,
|
|
26351
|
-
description: task.description,
|
|
26352
|
-
agentType: task.agentType,
|
|
26353
|
-
status: task.status,
|
|
26354
|
-
result: task.result,
|
|
26355
|
-
error: task.error
|
|
26356
|
-
}));
|
|
26357
|
-
const completed = taskResults.filter((t) => t.status === "completed");
|
|
26358
|
-
const failed = taskResults.filter((t) => t.status === "failed");
|
|
26359
|
-
const cascadeFailed = taskResults.filter((t) => t.status === "cascade_failed");
|
|
26360
|
-
const pending = taskResults.filter((t) => t.status === "pending");
|
|
26361
|
-
const success = failed.length === 0 && cascadeFailed.length === 0 && pending.length === 0;
|
|
26362
|
-
const summaryParts = [];
|
|
26363
|
-
if (completed.length > 0) {
|
|
26364
|
-
summaryParts.push(`## Completed Tasks (${completed.length}/${taskResults.length})\n`);
|
|
26365
|
-
for (const task of completed) {
|
|
26366
|
-
summaryParts.push(`### ${task.id}: ${task.description}\n`);
|
|
26367
|
-
summaryParts.push(`*Agent: ${task.agentType}*\n`);
|
|
26368
|
-
if (task.result) {
|
|
26369
|
-
const maxLen = 800;
|
|
26370
|
-
const truncated = task.result.length > maxLen ? task.result.slice(0, maxLen) + "\n...(truncated)" : task.result;
|
|
26371
|
-
summaryParts.push(truncated);
|
|
26372
|
-
}
|
|
26373
|
-
summaryParts.push("");
|
|
26374
|
-
}
|
|
26375
|
-
}
|
|
26376
|
-
if (failed.length > 0) {
|
|
26377
|
-
summaryParts.push(`## Failed Tasks (${failed.length})\n`);
|
|
26378
|
-
for (const task of failed) {
|
|
26379
|
-
summaryParts.push(`### ${task.id}: ${task.description}\n`);
|
|
26380
|
-
summaryParts.push(`*Agent: ${task.agentType}*\n`);
|
|
26381
|
-
summaryParts.push(`**Error:** ${task.error}\n`);
|
|
26382
|
-
}
|
|
26383
|
-
}
|
|
26384
|
-
if (cascadeFailed.length > 0) {
|
|
26385
|
-
summaryParts.push(`## Cascade-Failed Tasks (${cascadeFailed.length})\n`);
|
|
26386
|
-
for (const task of cascadeFailed) summaryParts.push(`- ${task.id}: ${task.description} — ${task.error}\n`);
|
|
26387
|
-
}
|
|
26388
|
-
if (pending.length > 0) {
|
|
26389
|
-
summaryParts.push(`## Skipped Tasks (${pending.length})\n`);
|
|
26390
|
-
for (const task of pending) summaryParts.push(`- ${task.id}: ${task.description}\n`);
|
|
26391
|
-
}
|
|
26392
|
-
return {
|
|
26393
|
-
success,
|
|
26394
|
-
taskResults,
|
|
26395
|
-
summary: summaryParts.join("\n")
|
|
26396
|
-
};
|
|
26397
|
-
}
|
|
26398
|
-
};
|
|
26399
|
-
//#endregion
|
|
26400
26319
|
//#region src/agents/decomposeTaskTool.ts
|
|
26401
26320
|
/**
|
|
26402
26321
|
* Create the decompose_task tool for the coordinator agent.
|
|
@@ -26499,6 +26418,87 @@ Tasks at the same dependency level run in parallel.
|
|
|
26499
26418
|
};
|
|
26500
26419
|
}
|
|
26501
26420
|
//#endregion
|
|
26421
|
+
//#region src/agents/TaskPipeline.ts
|
|
26422
|
+
/**
|
|
26423
|
+
* In-process DAG executor for the CLI.
|
|
26424
|
+
*
|
|
26425
|
+
* Validates the DAG via the shared validator, then runs each level
|
|
26426
|
+
* concurrently with `Promise.all`. The web's equivalent dispatches
|
|
26427
|
+
* each node to its own Lambda — see `coordinateTask.ts` for that path.
|
|
26428
|
+
*
|
|
26429
|
+
* Note: this executor honors `cascade` only; an `isolate` task that fails
|
|
26430
|
+
* still cascades in the CLI today. The web executor implements both.
|
|
26431
|
+
*/
|
|
26432
|
+
var TaskPipeline = class {
|
|
26433
|
+
constructor(input) {
|
|
26434
|
+
this.tasks = /* @__PURE__ */ new Map();
|
|
26435
|
+
for (const task of input.tasks) this.tasks.set(task.id, {
|
|
26436
|
+
...task,
|
|
26437
|
+
status: "pending"
|
|
26438
|
+
});
|
|
26439
|
+
this.executionOrder = validateAndSort(input);
|
|
26440
|
+
}
|
|
26441
|
+
get size() {
|
|
26442
|
+
return this.tasks.size;
|
|
26443
|
+
}
|
|
26444
|
+
getExecutionLevels() {
|
|
26445
|
+
return this.executionOrder;
|
|
26446
|
+
}
|
|
26447
|
+
isSingleTask() {
|
|
26448
|
+
return this.tasks.size === 1;
|
|
26449
|
+
}
|
|
26450
|
+
getSingleTask() {
|
|
26451
|
+
if (!this.isSingleTask()) throw new Error("Pipeline has more than one task");
|
|
26452
|
+
return [...this.tasks.values()][0];
|
|
26453
|
+
}
|
|
26454
|
+
/**
|
|
26455
|
+
* Execute all tasks in dependency order.
|
|
26456
|
+
* Tasks within the same level run in parallel; failed dependencies
|
|
26457
|
+
* cascade to dependents.
|
|
26458
|
+
*/
|
|
26459
|
+
async execute(executor) {
|
|
26460
|
+
const results = /* @__PURE__ */ new Map();
|
|
26461
|
+
const failedTaskIds = /* @__PURE__ */ new Set();
|
|
26462
|
+
for (const level of this.executionOrder) {
|
|
26463
|
+
const levelPromises = level.map(async (taskId) => {
|
|
26464
|
+
const task = this.tasks.get(taskId);
|
|
26465
|
+
const failedDep = task.dependsOn.find((depId) => failedTaskIds.has(depId));
|
|
26466
|
+
if (failedDep) {
|
|
26467
|
+
task.status = "cascade_failed";
|
|
26468
|
+
task.error = `Blocked by failed dependency "${failedDep}"`;
|
|
26469
|
+
failedTaskIds.add(taskId);
|
|
26470
|
+
return;
|
|
26471
|
+
}
|
|
26472
|
+
task.status = "running";
|
|
26473
|
+
const depResults = /* @__PURE__ */ new Map();
|
|
26474
|
+
for (const depId of task.dependsOn) {
|
|
26475
|
+
const depResult = results.get(depId);
|
|
26476
|
+
if (depResult) depResults.set(depId, depResult);
|
|
26477
|
+
}
|
|
26478
|
+
try {
|
|
26479
|
+
const result = await executor(task, depResults);
|
|
26480
|
+
task.status = "completed";
|
|
26481
|
+
task.result = result;
|
|
26482
|
+
results.set(taskId, result);
|
|
26483
|
+
} catch (error) {
|
|
26484
|
+
task.status = "failed";
|
|
26485
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
26486
|
+
failedTaskIds.add(taskId);
|
|
26487
|
+
}
|
|
26488
|
+
});
|
|
26489
|
+
await Promise.all(levelPromises);
|
|
26490
|
+
}
|
|
26491
|
+
return buildPipelineResult([...this.tasks.values()].map((t) => ({
|
|
26492
|
+
id: t.id,
|
|
26493
|
+
description: t.description,
|
|
26494
|
+
agentType: t.agentType,
|
|
26495
|
+
status: t.status,
|
|
26496
|
+
result: t.result,
|
|
26497
|
+
error: t.error
|
|
26498
|
+
})));
|
|
26499
|
+
}
|
|
26500
|
+
};
|
|
26501
|
+
//#endregion
|
|
26502
26502
|
//#region src/agents/SharedAgentContext.ts
|
|
26503
26503
|
/**
|
|
26504
26504
|
* SharedAgentContext — a namespaced key-value store for inter-agent communication.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { constants, promises } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import axios from "axios";
|
|
7
|
+
//#region src/utils/updateChecker.ts
|
|
8
|
+
/**
|
|
9
|
+
* Update checker utility for B4M CLI
|
|
10
|
+
* Checks the NPM registry for newer versions and caches results.
|
|
11
|
+
* Used by the startup banner, `b4m update`, and `b4m doctor` commands.
|
|
12
|
+
*/
|
|
13
|
+
const CACHE_FILE = path.join(homedir(), ".bike4mind", "update-check.json");
|
|
14
|
+
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
15
|
+
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@bike4mind/cli/latest";
|
|
16
|
+
const FETCH_TIMEOUT_MS = 5e3;
|
|
17
|
+
/**
|
|
18
|
+
* Canonical global-install command for the CLI.
|
|
19
|
+
* Single source of truth shared by the manual `b4m update` path and the
|
|
20
|
+
* auto-update-on-launch bootstrap so they can never drift.
|
|
21
|
+
*/
|
|
22
|
+
const INSTALL_CMD = "npm install -g @bike4mind/cli@latest";
|
|
23
|
+
/**
|
|
24
|
+
* Env var set on the re-exec'd child after an auto-update so it skips the
|
|
25
|
+
* update block and can't loop. Single source of truth for both the reader
|
|
26
|
+
* (shouldAttemptAutoUpdate) and the writer (maybeAutoUpdateOnLaunch).
|
|
27
|
+
*/
|
|
28
|
+
const REEXEC_GUARD_ENV = "B4M_UPDATED_REEXEC";
|
|
29
|
+
/**
|
|
30
|
+
* Check whether the global npm prefix is writable by the current user.
|
|
31
|
+
* When it isn't (e.g. Homebrew/system node), `npm install -g` needs sudo —
|
|
32
|
+
* which an unattended auto-updater cannot provide — so callers should fall
|
|
33
|
+
* back to a manual-update notice instead of attempting a silent install.
|
|
34
|
+
* Non-throwing — returns false on any error.
|
|
35
|
+
*
|
|
36
|
+
* Pass `prefix` to reuse an already-resolved `npm config get prefix` (each
|
|
37
|
+
* `execSync` is ~50-200ms); omit it and the prefix is resolved internally.
|
|
38
|
+
*/
|
|
39
|
+
async function isNpmPrefixWritable(prefix) {
|
|
40
|
+
try {
|
|
41
|
+
const resolved = prefix ?? execSync("npm config get prefix", {
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
timeout: 1e4
|
|
44
|
+
}).trim();
|
|
45
|
+
if (!resolved) return false;
|
|
46
|
+
await promises.access(resolved, constants.W_OK);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Cheap, synchronous pre-checks gating auto-update-on-launch. Excludes the
|
|
54
|
+
* async config-file and network lookups so it stays easy to unit-test.
|
|
55
|
+
* Returns false (skip the update) when already re-exec'd this launch, opted
|
|
56
|
+
* out via env, or not attached to an interactive TTY.
|
|
57
|
+
*/
|
|
58
|
+
function shouldAttemptAutoUpdate(opts) {
|
|
59
|
+
const env = opts.env ?? process.env;
|
|
60
|
+
if (env["B4M_UPDATED_REEXEC"] === "1") return false;
|
|
61
|
+
if (env.B4M_AUTO_UPDATE === "0") return false;
|
|
62
|
+
if (!opts.isTTY) return false;
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
const CONFIG_FILE = path.join(homedir(), ".bike4mind", "config.json");
|
|
66
|
+
/**
|
|
67
|
+
* Read the user's tri-state `autoUpdate` preference from the global config.
|
|
68
|
+
* Kept deliberately lightweight (no Zod / ConfigStore) because it runs in the
|
|
69
|
+
* bin bootstrap before the app loads. Defaults to `'ask'` (consent-first) when
|
|
70
|
+
* the file or flag is absent/unreadable.
|
|
71
|
+
*/
|
|
72
|
+
async function getAutoUpdatePreference() {
|
|
73
|
+
try {
|
|
74
|
+
const raw = await promises.readFile(CONFIG_FILE, "utf-8");
|
|
75
|
+
const value = JSON.parse(raw)?.preferences?.autoUpdate;
|
|
76
|
+
if (value === true) return "auto";
|
|
77
|
+
if (value === false) return "never";
|
|
78
|
+
return "ask";
|
|
79
|
+
} catch {
|
|
80
|
+
return "ask";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Persist the user's `autoUpdate` choice (`true` = always, `false` = never).
|
|
85
|
+
*
|
|
86
|
+
* Writes the **global** config file directly — the mirror of how
|
|
87
|
+
* `getAutoUpdatePreference()` reads it — rather than routing through
|
|
88
|
+
* `ConfigStore.save()`. `ConfigStore.load()` merges global → project → local,
|
|
89
|
+
* and `ConfigStore.save()` writes that merged result back to the global path;
|
|
90
|
+
* answering "Always"/"Never" inside a project would therefore bake the
|
|
91
|
+
* project's other overrides (model, theme, temperature, …) into the user's
|
|
92
|
+
* global config as a silent side effect. This call fires during the bin
|
|
93
|
+
* bootstrap any time an update is available, so that blast radius is
|
|
94
|
+
* unacceptable. A direct read-merge-write of only `preferences.autoUpdate`
|
|
95
|
+
* avoids it. If the on-disk file is schema-incomplete, the next
|
|
96
|
+
* `ConfigStore.load()` backfills defaults harmlessly — exactly as
|
|
97
|
+
* `getAutoUpdatePreference` already tolerates a partial file. Best-effort:
|
|
98
|
+
* a failure is swallowed (we simply ask again next launch rather than blocking).
|
|
99
|
+
*/
|
|
100
|
+
async function setAutoUpdatePreference(value) {
|
|
101
|
+
try {
|
|
102
|
+
let raw = {};
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(await promises.readFile(CONFIG_FILE, "utf-8"));
|
|
105
|
+
if (parsed && typeof parsed === "object") raw = parsed;
|
|
106
|
+
} catch {}
|
|
107
|
+
const prefs = raw.preferences && typeof raw.preferences === "object" ? raw.preferences : {};
|
|
108
|
+
raw.preferences = {
|
|
109
|
+
...prefs,
|
|
110
|
+
autoUpdate: value
|
|
111
|
+
};
|
|
112
|
+
await promises.mkdir(path.dirname(CONFIG_FILE), { recursive: true });
|
|
113
|
+
await promises.writeFile(CONFIG_FILE, JSON.stringify(raw, null, 2), "utf-8");
|
|
114
|
+
await promises.chmod(CONFIG_FILE, 384);
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compare two semver strings (MAJOR.MINOR.PATCH).
|
|
119
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
120
|
+
*/
|
|
121
|
+
function compareSemver(a, b) {
|
|
122
|
+
const partsA = a.split(".").map(Number);
|
|
123
|
+
const partsB = b.split(".").map(Number);
|
|
124
|
+
for (let i = 0; i < 3; i++) {
|
|
125
|
+
const segA = partsA[i] ?? 0;
|
|
126
|
+
const segB = partsB[i] ?? 0;
|
|
127
|
+
if (segA < segB) return -1;
|
|
128
|
+
if (segA > segB) return 1;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Fetch the latest published version from the NPM registry.
|
|
134
|
+
* Returns the version string or null on any error.
|
|
135
|
+
*/
|
|
136
|
+
async function fetchLatestVersion() {
|
|
137
|
+
try {
|
|
138
|
+
const version = (await axios.get(NPM_REGISTRY_URL, {
|
|
139
|
+
timeout: FETCH_TIMEOUT_MS,
|
|
140
|
+
headers: { Accept: "application/json" }
|
|
141
|
+
})).data?.version;
|
|
142
|
+
return typeof version === "string" ? version : null;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Read the cached update check result.
|
|
149
|
+
*/
|
|
150
|
+
async function readCache() {
|
|
151
|
+
try {
|
|
152
|
+
const data = await promises.readFile(CACHE_FILE, "utf-8");
|
|
153
|
+
const parsed = JSON.parse(data);
|
|
154
|
+
if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string" && typeof parsed.currentVersion === "string") return parsed;
|
|
155
|
+
return null;
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Write the update check result to cache.
|
|
162
|
+
*/
|
|
163
|
+
async function writeCache(cache) {
|
|
164
|
+
try {
|
|
165
|
+
await promises.mkdir(path.dirname(CACHE_FILE), { recursive: true });
|
|
166
|
+
await promises.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
167
|
+
} catch {}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check for updates using cache when fresh.
|
|
171
|
+
* Non-throwing — returns null on any error.
|
|
172
|
+
*/
|
|
173
|
+
async function checkForUpdate(currentVersion) {
|
|
174
|
+
try {
|
|
175
|
+
const cache = await readCache();
|
|
176
|
+
if (cache && cache.currentVersion === currentVersion) {
|
|
177
|
+
if (Date.now() - new Date(cache.lastChecked).getTime() < CACHE_TTL_MS) return {
|
|
178
|
+
currentVersion,
|
|
179
|
+
latestVersion: cache.latestVersion,
|
|
180
|
+
updateAvailable: compareSemver(cache.latestVersion, currentVersion) > 0
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const latestVersion = await fetchLatestVersion();
|
|
184
|
+
if (!latestVersion) return null;
|
|
185
|
+
await writeCache({
|
|
186
|
+
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
latestVersion,
|
|
188
|
+
currentVersion
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
currentVersion,
|
|
192
|
+
latestVersion,
|
|
193
|
+
updateAvailable: compareSemver(latestVersion, currentVersion) > 0
|
|
194
|
+
};
|
|
195
|
+
} catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Force-check for updates (ignores cache).
|
|
201
|
+
* Used by `b4m update` and `b4m doctor`.
|
|
202
|
+
*/
|
|
203
|
+
async function forceCheckForUpdate(currentVersion) {
|
|
204
|
+
const latestVersion = await fetchLatestVersion();
|
|
205
|
+
if (!latestVersion) return null;
|
|
206
|
+
await writeCache({
|
|
207
|
+
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
latestVersion,
|
|
209
|
+
currentVersion
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
currentVersion,
|
|
213
|
+
latestVersion,
|
|
214
|
+
updateAvailable: compareSemver(latestVersion, currentVersion) > 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { fetchLatestVersion as a, isNpmPrefixWritable as c, compareSemver as i, setAutoUpdatePreference as l, REEXEC_GUARD_ENV as n, forceCheckForUpdate as o, checkForUpdate as r, getAutoUpdatePreference as s, INSTALL_CMD as t, shouldAttemptAutoUpdate as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bike4mind/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Interactive CLI tool for Bike4Mind with ReAct agents",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -107,8 +107,8 @@
|
|
|
107
107
|
"zod": "^4.4.3",
|
|
108
108
|
"zod-validation-error": "^5.0.0",
|
|
109
109
|
"zustand": "^5.0.13",
|
|
110
|
-
"@bike4mind/fab-pipeline": "0.2.
|
|
111
|
-
"@bike4mind/llm-adapters": "0.3.
|
|
110
|
+
"@bike4mind/fab-pipeline": "0.2.10",
|
|
111
|
+
"@bike4mind/llm-adapters": "0.3.5",
|
|
112
112
|
"@bike4mind/observability": "0.1.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"tsx": "^4.22.3",
|
|
125
125
|
"typescript": "^5.9.3",
|
|
126
126
|
"vitest": "^4.1.7",
|
|
127
|
-
"@bike4mind/agents": "0.
|
|
128
|
-
"@bike4mind/common": "2.107.
|
|
129
|
-
"@bike4mind/mcp": "1.37.
|
|
130
|
-
"@bike4mind/services": "
|
|
131
|
-
"@bike4mind/utils": "2.23.
|
|
127
|
+
"@bike4mind/agents": "0.13.0",
|
|
128
|
+
"@bike4mind/common": "2.107.1",
|
|
129
|
+
"@bike4mind/mcp": "1.37.23",
|
|
130
|
+
"@bike4mind/services": "0.0.0-changeset-release-main-20260609021710",
|
|
131
|
+
"@bike4mind/utils": "2.23.11"
|
|
132
132
|
},
|
|
133
133
|
"optionalDependencies": {
|
|
134
134
|
"@vscode/ripgrep": "^1.18.0"
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { promises } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import axios from "axios";
|
|
6
|
-
//#region src/utils/updateChecker.ts
|
|
7
|
-
/**
|
|
8
|
-
* Update checker utility for B4M CLI
|
|
9
|
-
* Checks the NPM registry for newer versions and caches results.
|
|
10
|
-
* Used by the startup banner, `b4m update`, and `b4m doctor` commands.
|
|
11
|
-
*/
|
|
12
|
-
const CACHE_FILE = path.join(homedir(), ".bike4mind", "update-check.json");
|
|
13
|
-
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
14
|
-
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@bike4mind/cli/latest";
|
|
15
|
-
const FETCH_TIMEOUT_MS = 5e3;
|
|
16
|
-
/**
|
|
17
|
-
* Compare two semver strings (MAJOR.MINOR.PATCH).
|
|
18
|
-
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
19
|
-
*/
|
|
20
|
-
function compareSemver(a, b) {
|
|
21
|
-
const partsA = a.split(".").map(Number);
|
|
22
|
-
const partsB = b.split(".").map(Number);
|
|
23
|
-
for (let i = 0; i < 3; i++) {
|
|
24
|
-
const segA = partsA[i] ?? 0;
|
|
25
|
-
const segB = partsB[i] ?? 0;
|
|
26
|
-
if (segA < segB) return -1;
|
|
27
|
-
if (segA > segB) return 1;
|
|
28
|
-
}
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Fetch the latest published version from the NPM registry.
|
|
33
|
-
* Returns the version string or null on any error.
|
|
34
|
-
*/
|
|
35
|
-
async function fetchLatestVersion() {
|
|
36
|
-
try {
|
|
37
|
-
const version = (await axios.get(NPM_REGISTRY_URL, {
|
|
38
|
-
timeout: FETCH_TIMEOUT_MS,
|
|
39
|
-
headers: { Accept: "application/json" }
|
|
40
|
-
})).data?.version;
|
|
41
|
-
return typeof version === "string" ? version : null;
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Read the cached update check result.
|
|
48
|
-
*/
|
|
49
|
-
async function readCache() {
|
|
50
|
-
try {
|
|
51
|
-
const data = await promises.readFile(CACHE_FILE, "utf-8");
|
|
52
|
-
const parsed = JSON.parse(data);
|
|
53
|
-
if (parsed && typeof parsed.lastChecked === "string" && typeof parsed.latestVersion === "string" && typeof parsed.currentVersion === "string") return parsed;
|
|
54
|
-
return null;
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Write the update check result to cache.
|
|
61
|
-
*/
|
|
62
|
-
async function writeCache(cache) {
|
|
63
|
-
try {
|
|
64
|
-
await promises.mkdir(path.dirname(CACHE_FILE), { recursive: true });
|
|
65
|
-
await promises.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
|
|
66
|
-
} catch {}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Check for updates using cache when fresh.
|
|
70
|
-
* Non-throwing — returns null on any error.
|
|
71
|
-
*/
|
|
72
|
-
async function checkForUpdate(currentVersion) {
|
|
73
|
-
try {
|
|
74
|
-
const cache = await readCache();
|
|
75
|
-
if (cache && cache.currentVersion === currentVersion) {
|
|
76
|
-
if (Date.now() - new Date(cache.lastChecked).getTime() < CACHE_TTL_MS) return {
|
|
77
|
-
currentVersion,
|
|
78
|
-
latestVersion: cache.latestVersion,
|
|
79
|
-
updateAvailable: compareSemver(cache.latestVersion, currentVersion) > 0
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const latestVersion = await fetchLatestVersion();
|
|
83
|
-
if (!latestVersion) return null;
|
|
84
|
-
await writeCache({
|
|
85
|
-
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
86
|
-
latestVersion,
|
|
87
|
-
currentVersion
|
|
88
|
-
});
|
|
89
|
-
return {
|
|
90
|
-
currentVersion,
|
|
91
|
-
latestVersion,
|
|
92
|
-
updateAvailable: compareSemver(latestVersion, currentVersion) > 0
|
|
93
|
-
};
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Force-check for updates (ignores cache).
|
|
100
|
-
* Used by `b4m update` and `b4m doctor`.
|
|
101
|
-
*/
|
|
102
|
-
async function forceCheckForUpdate(currentVersion) {
|
|
103
|
-
const latestVersion = await fetchLatestVersion();
|
|
104
|
-
if (!latestVersion) return null;
|
|
105
|
-
await writeCache({
|
|
106
|
-
lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
107
|
-
latestVersion,
|
|
108
|
-
currentVersion
|
|
109
|
-
});
|
|
110
|
-
return {
|
|
111
|
-
currentVersion,
|
|
112
|
-
latestVersion,
|
|
113
|
-
updateAvailable: compareSemver(latestVersion, currentVersion) > 0
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
//#endregion
|
|
117
|
-
export { forceCheckForUpdate as i, compareSemver as n, fetchLatestVersion as r, checkForUpdate as t };
|