@bike4mind/cli 0.18.5 → 0.20.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/LICENSE +1 -1
- package/README.md +204 -35
- package/bin/bike4mind-cli.mjs +137 -24
- package/bin/hearth-hook.mjs +292 -0
- package/dist/AgentHistoryStore-C8uUKjjC.mjs +35512 -0
- package/dist/ApiClient-B_CQrUiF.mjs +277 -0
- package/dist/{ConfigStore-D39UqFnY.mjs → ConfigStore-DD3DcC3-.mjs} +6199 -3954
- package/dist/{ImageStore-BVmEG1xc.mjs → ImageStore-kVo-oHoS.mjs} +2 -2
- package/dist/PluginStore-DwvOJ-G3.mjs +206 -0
- package/dist/ProxyManager-Bqr7Lmsd.mjs +3 -0
- package/dist/{ProxyManager-CV94yZUW.mjs → ProxyManager-C5H0pUyK.mjs} +2 -2
- package/dist/{SandboxOrchestrator-BS6gALNq.mjs → SandboxOrchestrator-BFPVpmB5.mjs} +1 -1
- package/dist/{SandboxOrchestrator-BoINxbX4.mjs → SandboxOrchestrator-C8uleDn2.mjs} +7 -7
- package/dist/ShellSessionManager-6o8KZzl1-vrbPAUTq.mjs +252 -0
- package/dist/{ViolationLogStore-B-plqJfn.mjs → ViolationLogStore-byEhxa2A.mjs} +1 -1
- package/dist/WorkItemsClient-Cow6nXx7.mjs +382 -0
- package/dist/{bashExecute-B1N1lMOS-TZVDbcQ4.mjs → bashExecute-CrdPpBqk-DCATrE-D.mjs} +116 -16
- package/dist/buildAgent-mVuXU_H4.mjs +824 -0
- package/dist/commands/acpCommand.mjs +798 -0
- package/dist/commands/apiCommand.mjs +14 -16
- package/dist/commands/doctorCommand.mjs +5 -5
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +272 -76
- package/dist/commands/mcpCommand.mjs +14 -1
- package/dist/commands/pluginCommand.mjs +232 -0
- package/dist/commands/updateCommand.mjs +10 -9
- package/dist/{grepSearch-DJs-cubo-Bm0Y8oS3.mjs → grepSearch-BaYUfIYs-C-fxWc9G.mjs} +3 -3
- package/dist/index.mjs +3281 -2322
- package/dist/{package-I_v_WFUn.mjs → package-BqKSCbso.mjs} +1 -1
- package/dist/serve-CuF0I5en.mjs +772 -0
- package/dist/store-BG3e54c8.mjs +3 -0
- package/dist/{store-DV5s-qni.mjs → store-CvjTpQPs.mjs} +70 -3
- package/dist/{terminalSetup-BbJt04ZG.mjs → terminalSetup-DjXAwpDy.mjs} +2 -3
- package/dist/{treeSitterEngine-BRbQ9b7I.mjs → treeSitterEngine-QBE3YkmG.mjs} +51 -1
- package/dist/{updateChecker-C8xsNY2L.mjs → updateChecker-CQW8bxo6.mjs} +10 -10
- package/package.json +48 -43
- package/dist/BackgroundAgentManager-D-xsWd3C.mjs +0 -27303
- package/dist/ProxyManager-ByuAHFMq.mjs +0 -3
- package/dist/store-DgzCTRkN.mjs +0 -3
- package/dist/utils-Cdktpk_k.mjs +0 -158
- package/dist/utils-DEizxshI.mjs +0 -3
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { l as resolveApiEndpoint, s as parseApiUrl, t as ConfigStore } from "../ConfigStore-DD3DcC3-.mjs";
|
|
3
3
|
//#region src/commands/apiCommand.ts
|
|
4
4
|
/**
|
|
5
5
|
* External API config command (--api-url / --reset-api)
|
|
6
6
|
* Runs outside the interactive CLI session, before any auth flow.
|
|
7
7
|
*
|
|
8
|
-
* - `--reset-api`: clears customUrl, falling back to the
|
|
8
|
+
* - `--reset-api`: clears customUrl, falling back to whatever the CLI resolves
|
|
9
|
+
* without one (build-time default, the source-mode local dev server, or - for
|
|
10
|
+
* a published unbranded fork - nothing, in which case `b4m` prompts for one)
|
|
9
11
|
* - `--api-url <url>`: sets a custom API URL (e.g. http://localhost:3000)
|
|
10
12
|
*
|
|
11
13
|
* Both clear auth tokens because they're bound to the old origin, and both
|
|
@@ -14,20 +16,13 @@ import { a as getDefaultApiUrl, t as ConfigStore } from "../ConfigStore-D39UqFnY
|
|
|
14
16
|
async function handleApiCommand(options) {
|
|
15
17
|
const configStore = new ConfigStore();
|
|
16
18
|
if (options.mode === "set") {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
parsed = new URL(url);
|
|
21
|
-
} catch {
|
|
22
|
-
console.error(`❌ Invalid URL: ${url}`);
|
|
23
|
-
console.error(" Example: --api-url http://localhost:3000");
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
27
|
-
console.error(`❌ Only http:// and https:// URLs are supported (got ${parsed.protocol}//)`);
|
|
19
|
+
const result = parseApiUrl(options.url);
|
|
20
|
+
if ("error" in result) {
|
|
21
|
+
console.error(`❌ ${result.error}`);
|
|
28
22
|
console.error(" Example: --api-url http://localhost:3000");
|
|
29
23
|
process.exit(1);
|
|
30
24
|
}
|
|
25
|
+
const { url } = result;
|
|
31
26
|
await configStore.setCustomApiUrl(url);
|
|
32
27
|
await configStore.clearAuthTokens();
|
|
33
28
|
console.log(`\n✅ API URL set to ${url}`);
|
|
@@ -37,10 +32,13 @@ async function handleApiCommand(options) {
|
|
|
37
32
|
}
|
|
38
33
|
await configStore.setCustomApiUrl(null);
|
|
39
34
|
await configStore.clearAuthTokens();
|
|
40
|
-
const
|
|
41
|
-
console.log(
|
|
35
|
+
const endpoint = resolveApiEndpoint();
|
|
36
|
+
console.log("\n✅ Custom API URL cleared");
|
|
42
37
|
console.log("🔓 Authentication cleared");
|
|
43
|
-
|
|
38
|
+
if (endpoint.status === "configured") {
|
|
39
|
+
console.log(`🌍 The CLI will now use ${endpoint.url}`);
|
|
40
|
+
console.log("💡 Run `b4m` to authenticate.\n");
|
|
41
|
+
} else console.log("💡 Run `b4m` and you'll be prompted to choose a backend.\n");
|
|
44
42
|
}
|
|
45
43
|
//#endregion
|
|
46
44
|
export { handleApiCommand };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
3
|
-
import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-
|
|
2
|
+
import { t as version } from "../package-BqKSCbso.mjs";
|
|
3
|
+
import { a as fetchLatestVersion, c as isNpmPrefixWritable, i as compareSemver } from "../updateChecker-CQW8bxo6.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { execSync } from "child_process";
|
|
6
6
|
import { existsSync } from "fs";
|
|
@@ -17,15 +17,15 @@ async function handleDoctorCommand() {
|
|
|
17
17
|
console.log("Running diagnostics...\n");
|
|
18
18
|
const results = [];
|
|
19
19
|
const nodeVersion = process.version;
|
|
20
|
-
if (parseInt(nodeVersion.slice(1).split(".")[0], 10) >=
|
|
20
|
+
if (parseInt(nodeVersion.slice(1).split(".")[0], 10) >= 24) results.push({
|
|
21
21
|
name: "Node.js version",
|
|
22
22
|
status: "pass",
|
|
23
|
-
message: `${nodeVersion} (>=
|
|
23
|
+
message: `${nodeVersion} (>= 24 required)`
|
|
24
24
|
});
|
|
25
25
|
else results.push({
|
|
26
26
|
name: "Node.js version",
|
|
27
27
|
status: "fail",
|
|
28
|
-
message: `${nodeVersion} (>=
|
|
28
|
+
message: `${nodeVersion} (>= 24 required, please upgrade)`
|
|
29
29
|
});
|
|
30
30
|
const currentVersion = version;
|
|
31
31
|
const latestVersion = await fetchLatestVersion();
|
|
@@ -1,12 +1,180 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { $ as RemoteSkillSource, A as FallbackLlmBackend, B as classifyCommandRisk, C as createWriteTodosTool, D as createResumeAgentTool, E as createCoordinateTaskTool, F as loadContextFiles, I as PermissionManager, J as setWebSocketToolExecutor, L as generateCliTools, M as ReActAgent, O as createBackgroundAgentTools, Q as isReadOnlyTool, S as createTodoStore, X as buildSystemPrompt, a as McpManager, b as createWorkItemTools, et as CustomCommandStore, i as AgentStore, k as createAgentDelegateTool, n as BackgroundAgentManager, nt as SessionStore, o as createSseBackend, r as SubagentOrchestrator, t as AgentHistoryStore, tt as CheckpointStore, w as createSkillTool, x as createFindDefinitionTool, y as createGetFileStructureTool, z as SHELL_LIKE_TOOL_COMMAND_FIELDS } from "../AgentHistoryStore-C8uUKjjC.mjs";
|
|
3
|
+
import { c as requireApiUrl, n as logger, t as ConfigStore } from "../ConfigStore-DD3DcC3-.mjs";
|
|
4
|
+
import { t as ApiClient } from "../ApiClient-B_CQrUiF.mjs";
|
|
4
5
|
import { t as DEFAULT_SANDBOX_CONFIG } from "../types-LyRNHOiS.mjs";
|
|
6
|
+
import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-Cow6nXx7.mjs";
|
|
5
7
|
import { t as createSandboxRuntime } from "../SandboxRuntimeAdapter-ChGlxSGQ.mjs";
|
|
6
|
-
import { t as SandboxOrchestrator } from "../SandboxOrchestrator-
|
|
7
|
-
import { t as ProxyManager } from "../ProxyManager-
|
|
8
|
+
import { t as SandboxOrchestrator } from "../SandboxOrchestrator-C8uleDn2.mjs";
|
|
9
|
+
import { t as ProxyManager } from "../ProxyManager-C5H0pUyK.mjs";
|
|
8
10
|
import { randomBytes } from "crypto";
|
|
9
11
|
import { v4 } from "uuid";
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
//#region src/commands/headlessProtocol.ts
|
|
14
|
+
/**
|
|
15
|
+
* Headless stream-JSON protocol contract.
|
|
16
|
+
*
|
|
17
|
+
* `b4m -p "..." --output-format stream-json` emits one JSON object per line
|
|
18
|
+
* (NDJSON). This module owns that wire contract so CI and other tooling can
|
|
19
|
+
* depend on it: the schema version, the event shapes, and (later milestones)
|
|
20
|
+
* the permission protocol and strict input validation.
|
|
21
|
+
*
|
|
22
|
+
* Human-readable spec: packages/cli/docs/headless-protocol.md. Keep the two in
|
|
23
|
+
* sync - every event type and field documented there must be produced here.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Protocol schema version (semver). Bump the MAJOR when removing/renaming a
|
|
27
|
+
* field or event type (breaking change); bump the MINOR when adding an optional
|
|
28
|
+
* field or a new event type (backward-compatible). Consumers should reject a
|
|
29
|
+
* MAJOR they do not understand.
|
|
30
|
+
*/
|
|
31
|
+
const HEADLESS_SCHEMA_VERSION = "1.0.0";
|
|
32
|
+
/** Serialize an event with the protocol envelope stamped on. Exposed for tests. */
|
|
33
|
+
function stampEvent(runId, event) {
|
|
34
|
+
return {
|
|
35
|
+
...event,
|
|
36
|
+
schemaVersion: HEADLESS_SCHEMA_VERSION,
|
|
37
|
+
runId
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build an NDJSON emitter that stamps schemaVersion + runId onto every event.
|
|
42
|
+
* `write` receives one serialized line including its trailing newline.
|
|
43
|
+
*/
|
|
44
|
+
function createHeadlessEmitter(runId, write) {
|
|
45
|
+
return (event) => {
|
|
46
|
+
write(JSON.stringify(stampEvent(runId, event)) + "\n");
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Raised when a structured headless input is malformed or carries a field
|
|
51
|
+
* outside its allowlist. Headless input fails loud on protocol drift rather
|
|
52
|
+
* than silently ignoring unknown fields.
|
|
53
|
+
*/
|
|
54
|
+
var HeadlessInputError = class extends Error {
|
|
55
|
+
constructor(message) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "HeadlessInputError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Decode a JSON string to a plain object and reject any key outside
|
|
62
|
+
* `allowedKeys`. `inputType` names the input in error messages. Throws
|
|
63
|
+
* HeadlessInputError on invalid JSON, a non-object payload, or an unknown key.
|
|
64
|
+
*/
|
|
65
|
+
function parseStrictObject(raw, allowedKeys, inputType) {
|
|
66
|
+
let decoded;
|
|
67
|
+
try {
|
|
68
|
+
decoded = JSON.parse(raw);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
throw new HeadlessInputError(`${inputType}: invalid JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
71
|
+
}
|
|
72
|
+
if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded)) throw new HeadlessInputError(`${inputType}: expected a JSON object`);
|
|
73
|
+
const allowed = new Set(allowedKeys);
|
|
74
|
+
const unknownKeys = Object.keys(decoded).filter((k) => !allowed.has(k));
|
|
75
|
+
if (unknownKeys.length > 0) throw new HeadlessInputError(`${inputType}: unknown field(s): ${unknownKeys.join(", ")}. Allowed: ${allowedKeys.join(", ")}`);
|
|
76
|
+
return decoded;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decode a JSON string to an array of strings, rejecting any other shape.
|
|
80
|
+
* Used for list-shaped inputs such as the B4M_ADDITIONAL_DIRS bridge.
|
|
81
|
+
*/
|
|
82
|
+
function parseStringArray(raw, inputType) {
|
|
83
|
+
let decoded;
|
|
84
|
+
try {
|
|
85
|
+
decoded = JSON.parse(raw);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
throw new HeadlessInputError(`${inputType}: invalid JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(decoded) || decoded.some((v) => typeof v !== "string")) throw new HeadlessInputError(`${inputType}: expected a JSON array of strings`);
|
|
90
|
+
return decoded;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Classify the risk of a single tool invocation for the permission protocol.
|
|
94
|
+
* Shell-like tools are classified from their actual command text (reusing the
|
|
95
|
+
* shared command-risk tokenizer); every other tool falls back to a level derived
|
|
96
|
+
* from its permission category (auto_approve -> low, prompt_always -> high, else
|
|
97
|
+
* medium). Never throws: a classifier failure fails closed at `high`.
|
|
98
|
+
*/
|
|
99
|
+
function classifyToolRisk(toolName, args, category) {
|
|
100
|
+
const field = SHELL_LIKE_TOOL_COMMAND_FIELDS[toolName];
|
|
101
|
+
const command = field && args !== null && typeof args === "object" ? args[field] : void 0;
|
|
102
|
+
if (typeof command === "string") try {
|
|
103
|
+
return classifyCommandRisk(command);
|
|
104
|
+
} catch {
|
|
105
|
+
return {
|
|
106
|
+
level: "high",
|
|
107
|
+
reasons: ["command risk analysis failed (fail closed)"]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
level: category === "auto_approve" ? "low" : category === "prompt_always" ? "high" : "medium",
|
|
112
|
+
reasons: []
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const HEADLESS_PERMISSION_POLICY_KEYS = [
|
|
116
|
+
"allow",
|
|
117
|
+
"deny",
|
|
118
|
+
"maxAutoAllowRisk",
|
|
119
|
+
"defaultAction"
|
|
120
|
+
];
|
|
121
|
+
const RISK_RANK = {
|
|
122
|
+
low: 0,
|
|
123
|
+
medium: 1,
|
|
124
|
+
high: 2
|
|
125
|
+
};
|
|
126
|
+
function readOptionalStringArray(value, label) {
|
|
127
|
+
if (value === void 0) return [];
|
|
128
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) throw new HeadlessInputError(`${label} must be an array of strings`);
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Parse and strictly validate a permission policy from its JSON text. Rejects
|
|
133
|
+
* unknown fields and malformed values with HeadlessInputError. Missing lists
|
|
134
|
+
* default to empty and a missing defaultAction defaults to 'deny'.
|
|
135
|
+
*/
|
|
136
|
+
function parsePermissionPolicy(raw) {
|
|
137
|
+
const obj = parseStrictObject(raw, HEADLESS_PERMISSION_POLICY_KEYS, "permission policy");
|
|
138
|
+
const allow = readOptionalStringArray(obj.allow, "permission policy: allow");
|
|
139
|
+
const deny = readOptionalStringArray(obj.deny, "permission policy: deny");
|
|
140
|
+
let maxAutoAllowRisk;
|
|
141
|
+
if (obj.maxAutoAllowRisk !== void 0) {
|
|
142
|
+
if (obj.maxAutoAllowRisk !== "low" && obj.maxAutoAllowRisk !== "medium" && obj.maxAutoAllowRisk !== "high") throw new HeadlessInputError("permission policy: maxAutoAllowRisk must be one of low|medium|high");
|
|
143
|
+
maxAutoAllowRisk = obj.maxAutoAllowRisk;
|
|
144
|
+
}
|
|
145
|
+
let defaultAction = "deny";
|
|
146
|
+
if (obj.defaultAction !== void 0) {
|
|
147
|
+
if (obj.defaultAction !== "allow" && obj.defaultAction !== "deny") throw new HeadlessInputError("permission policy: defaultAction must be one of allow|deny");
|
|
148
|
+
defaultAction = obj.defaultAction;
|
|
149
|
+
}
|
|
150
|
+
if (maxAutoAllowRisk !== void 0 && defaultAction === "allow") throw new HeadlessInputError("permission policy: maxAutoAllowRisk has no effect with defaultAction 'allow' (tools above the threshold are allowed anyway); set defaultAction to 'deny' or remove maxAutoAllowRisk");
|
|
151
|
+
return {
|
|
152
|
+
allow,
|
|
153
|
+
deny,
|
|
154
|
+
maxAutoAllowRisk,
|
|
155
|
+
defaultAction
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Evaluate a policy against a tool and its classified risk. Never throws. */
|
|
159
|
+
function evaluatePermissionPolicy(policy, toolName, riskLevel) {
|
|
160
|
+
if (policy.deny.includes(toolName)) return {
|
|
161
|
+
action: "deny",
|
|
162
|
+
reason: "tool in policy deny list"
|
|
163
|
+
};
|
|
164
|
+
if (policy.allow.includes(toolName)) return {
|
|
165
|
+
action: "allow",
|
|
166
|
+
reason: "tool in policy allow list"
|
|
167
|
+
};
|
|
168
|
+
if (policy.maxAutoAllowRisk && RISK_RANK[riskLevel] <= RISK_RANK[policy.maxAutoAllowRisk]) return {
|
|
169
|
+
action: "allow",
|
|
170
|
+
reason: `risk ${riskLevel} <= maxAutoAllowRisk ${policy.maxAutoAllowRisk}`
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
action: policy.defaultAction,
|
|
174
|
+
reason: `policy default (${policy.defaultAction})`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
10
178
|
//#region src/commands/headlessCommand.ts
|
|
11
179
|
/**
|
|
12
180
|
* Headless/programmatic mode command (b4m -p "query")
|
|
@@ -32,22 +200,33 @@ const silentLogger = {
|
|
|
32
200
|
debug: () => {}
|
|
33
201
|
};
|
|
34
202
|
async function handleHeadlessCommand(options) {
|
|
35
|
-
const { prompt, outputFormat, dangerouslySkipPermissions, addDirs } = options;
|
|
203
|
+
const { prompt, outputFormat, dangerouslySkipPermissions, addDirs, permissionPolicyPath } = options;
|
|
36
204
|
logger.setVerbose(options.verbose);
|
|
37
205
|
const stdinContent = await readStdin();
|
|
38
206
|
const fullPrompt = stdinContent ? `${prompt}\n\n<stdin>\n${stdinContent}\n</stdin>` : prompt;
|
|
39
207
|
const configStore = new ConfigStore();
|
|
40
208
|
const sessionStore = new SessionStore();
|
|
41
209
|
const customCommandStore = new CustomCommandStore();
|
|
210
|
+
const runId = v4();
|
|
42
211
|
try {
|
|
43
212
|
const config = await configStore.load();
|
|
44
213
|
const configDirs = await configStore.getAdditionalDirectories();
|
|
45
|
-
const flagDirs = process.env.B4M_ADDITIONAL_DIRS ?
|
|
46
|
-
const additionalDirectories = [
|
|
214
|
+
const flagDirs = process.env.B4M_ADDITIONAL_DIRS ? parseStringArray(process.env.B4M_ADDITIONAL_DIRS, "B4M_ADDITIONAL_DIRS") : [];
|
|
215
|
+
const additionalDirectories = [.../* @__PURE__ */ new Set([
|
|
47
216
|
...configDirs,
|
|
48
217
|
...flagDirs,
|
|
49
218
|
...addDirs
|
|
50
219
|
])];
|
|
220
|
+
let permissionPolicy = null;
|
|
221
|
+
if (permissionPolicyPath) {
|
|
222
|
+
let policyRaw;
|
|
223
|
+
try {
|
|
224
|
+
policyRaw = await readFile(permissionPolicyPath, "utf-8");
|
|
225
|
+
} catch (e) {
|
|
226
|
+
throw new Error(`Cannot read permission policy at "${permissionPolicyPath}": ${e instanceof Error ? e.message : String(e)}`);
|
|
227
|
+
}
|
|
228
|
+
permissionPolicy = parsePermissionPolicy(policyRaw);
|
|
229
|
+
}
|
|
51
230
|
try {
|
|
52
231
|
await customCommandStore.loadCommands();
|
|
53
232
|
} catch {}
|
|
@@ -61,45 +240,22 @@ async function handleHeadlessCommand(options) {
|
|
|
61
240
|
process.stderr.write("Error: Authentication token expired. Run `b4m /login` to re-authenticate.\n");
|
|
62
241
|
process.exit(1);
|
|
63
242
|
}
|
|
64
|
-
|
|
243
|
+
let apiBaseURL;
|
|
244
|
+
try {
|
|
245
|
+
apiBaseURL = requireApiUrl(config.apiConfig);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
const apiClient = new ApiClient(apiBaseURL, configStore);
|
|
65
251
|
if (process.env.B4M_NO_REMOTE_SKILLS !== "1" && config.preferences.enableRemoteSkills !== false) try {
|
|
66
252
|
customCommandStore.setRemoteSource(new RemoteSkillSource(apiClient));
|
|
67
253
|
await customCommandStore.mergeRemoteCommands();
|
|
68
254
|
} catch {}
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let llm;
|
|
74
|
-
let completionsUrl;
|
|
75
|
-
try {
|
|
76
|
-
const serverConfig = await apiClient.get("/api/settings/serverConfig");
|
|
77
|
-
const wsUrl = serverConfig?.websocketUrl;
|
|
78
|
-
const wsCompletionUrl = serverConfig?.wsCompletionUrl;
|
|
79
|
-
completionsUrl = serverConfig?.completionsUrl;
|
|
80
|
-
if (wsUrl && wsCompletionUrl) {
|
|
81
|
-
wsManager = new WebSocketConnectionManager(wsUrl, tokenGetter);
|
|
82
|
-
await wsManager.connect();
|
|
83
|
-
setWebSocketToolExecutor(new WebSocketToolExecutor(wsManager, tokenGetter));
|
|
84
|
-
llm = new WebSocketLlmBackend({
|
|
85
|
-
wsManager,
|
|
86
|
-
apiClient,
|
|
87
|
-
model: config.defaultModel,
|
|
88
|
-
tokenGetter,
|
|
89
|
-
wsCompletionUrl
|
|
90
|
-
});
|
|
91
|
-
logger.debug("[headless] Using WebSocket transport");
|
|
92
|
-
} else throw new Error("No websocketUrl or wsCompletionUrl in server config");
|
|
93
|
-
} catch {
|
|
94
|
-
wsManager = null;
|
|
95
|
-
setWebSocketToolExecutor(null);
|
|
96
|
-
llm = new ServerLlmBackend({
|
|
97
|
-
apiClient,
|
|
98
|
-
model: config.defaultModel,
|
|
99
|
-
completionsUrl
|
|
100
|
-
});
|
|
101
|
-
logger.debug("[headless] Using SSE transport fallback");
|
|
102
|
-
}
|
|
255
|
+
const { llm } = await createSseBackend({
|
|
256
|
+
apiClient,
|
|
257
|
+
model: config.defaultModel
|
|
258
|
+
});
|
|
103
259
|
const models = await llm.getModelInfo();
|
|
104
260
|
if (models.length === 0) throw new Error("No models available from server.");
|
|
105
261
|
const modelInfo = models.find((m) => m.id === config.defaultModel) ?? models[0];
|
|
@@ -122,22 +278,51 @@ async function handleHeadlessCommand(options) {
|
|
|
122
278
|
};
|
|
123
279
|
await logger.initialize(session.id);
|
|
124
280
|
const permissionManager = new PermissionManager(config.trustedTools ?? [], void 0, config.tools.disabled);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
281
|
+
const emit = createHeadlessEmitter(runId, (line) => process.stdout.write(line));
|
|
282
|
+
const streaming = outputFormat === "stream-json";
|
|
283
|
+
const promptFn = (toolName, args, _preview) => {
|
|
284
|
+
const risk = classifyToolRisk(toolName, args, permissionManager.getCategory(toolName));
|
|
285
|
+
if (streaming) emit({
|
|
286
|
+
type: "permission_request",
|
|
287
|
+
toolName,
|
|
288
|
+
risk
|
|
289
|
+
});
|
|
290
|
+
let decision;
|
|
291
|
+
if (dangerouslySkipPermissions) decision = {
|
|
292
|
+
action: "allow-once",
|
|
293
|
+
reason: "dangerously-skip-permissions"
|
|
294
|
+
};
|
|
295
|
+
else if (permissionPolicy) {
|
|
296
|
+
const verdict = evaluatePermissionPolicy(permissionPolicy, toolName, risk.level);
|
|
297
|
+
decision = {
|
|
298
|
+
action: verdict.action === "allow" ? "allow-once" : "deny",
|
|
299
|
+
reason: verdict.reason
|
|
300
|
+
};
|
|
301
|
+
} else decision = {
|
|
302
|
+
action: "deny",
|
|
303
|
+
reason: "no permission policy; default deny"
|
|
304
|
+
};
|
|
305
|
+
if (streaming) emit({
|
|
306
|
+
type: "permission_decision",
|
|
307
|
+
toolName,
|
|
308
|
+
action: decision.action,
|
|
309
|
+
reason: decision.reason,
|
|
310
|
+
risk: risk.level
|
|
311
|
+
});
|
|
312
|
+
else if (decision.action === "deny") process.stderr.write(`Warning: Tool "${toolName}" requires permission and was denied (${decision.reason}). Grant it via --permission-policy, or --dangerously-skip-permissions to allow all tools.\n`);
|
|
313
|
+
if (decision.action === "allow-once") logger.debug(`[headless] Auto-allowing tool: ${toolName}`);
|
|
314
|
+
return Promise.resolve({ action: decision.action });
|
|
132
315
|
};
|
|
133
316
|
const userQuestionFn = (_payload) => {
|
|
134
317
|
process.stderr.write("Warning: Agent requested user input; headless mode cannot respond interactively. Answering with empty response.\n");
|
|
135
318
|
return Promise.resolve({ answers: [] });
|
|
136
319
|
};
|
|
137
320
|
const sandboxConfig = config.sandbox ?? DEFAULT_SANDBOX_CONFIG;
|
|
138
|
-
const
|
|
321
|
+
const checkpointProjectDir = configStore.getProjectConfigDir() ?? process.cwd();
|
|
322
|
+
const checkpointStore = new CheckpointStore(checkpointProjectDir);
|
|
139
323
|
const [sandboxRuntime] = await Promise.all([createSandboxRuntime(), checkpointStore.init(session.id).catch(() => {})]);
|
|
140
|
-
const
|
|
324
|
+
const proxyManager = new ProxyManager(sandboxConfig.network);
|
|
325
|
+
const sandboxOrchestrator = new SandboxOrchestrator(sandboxConfig, sandboxRuntime, proxyManager);
|
|
141
326
|
permissionManager.setSandboxState(sandboxConfig.mode, sandboxOrchestrator.isActive());
|
|
142
327
|
const agentContext = {
|
|
143
328
|
currentAgent: null,
|
|
@@ -154,6 +339,7 @@ async function handleHeadlessCommand(options) {
|
|
|
154
339
|
loadContextFiles(projectConfigDir)
|
|
155
340
|
]);
|
|
156
341
|
const mcpTools = mcpManager.getTools();
|
|
342
|
+
const historyStore = new AgentHistoryStore(config.preferences.subagentHistoryTtlMs ?? 36e5);
|
|
157
343
|
const orchestrator = new SubagentOrchestrator({
|
|
158
344
|
userId: config.userId,
|
|
159
345
|
llm,
|
|
@@ -166,14 +352,18 @@ async function handleHeadlessCommand(options) {
|
|
|
166
352
|
customCommandStore,
|
|
167
353
|
enableParallelToolExecution: config.preferences.enableParallelToolExecution === true,
|
|
168
354
|
showUserQuestion: userQuestionFn,
|
|
169
|
-
checkpointStore
|
|
355
|
+
checkpointStore,
|
|
356
|
+
historyStore
|
|
170
357
|
});
|
|
171
358
|
const backgroundManager = new BackgroundAgentManager(orchestrator);
|
|
172
359
|
const agentDelegateTool = createAgentDelegateTool(orchestrator, agentStore, session.id, backgroundManager);
|
|
173
360
|
const backgroundTools = createBackgroundAgentTools(backgroundManager);
|
|
174
|
-
const
|
|
361
|
+
const resumeAgentTool = createResumeAgentTool(orchestrator, historyStore, backgroundManager);
|
|
362
|
+
const todoStore = createTodoStore();
|
|
363
|
+
const writeTodosTool = createWriteTodosTool(todoStore);
|
|
175
364
|
const findDefinitionTool = createFindDefinitionTool();
|
|
176
365
|
const getFileStructureTool = createGetFileStructureTool();
|
|
366
|
+
const workItemTools = config.preferences.enableWorkItemTools ? createWorkItemTools(new WorkItemsClient(apiClient)) : [];
|
|
177
367
|
const enableSkillTool = config.preferences.enableSkillTool !== false;
|
|
178
368
|
const skillTool = enableSkillTool ? createSkillTool({
|
|
179
369
|
customCommandStore,
|
|
@@ -183,9 +373,11 @@ async function handleHeadlessCommand(options) {
|
|
|
183
373
|
const cliTools = [
|
|
184
374
|
agentDelegateTool,
|
|
185
375
|
...backgroundTools,
|
|
376
|
+
resumeAgentTool,
|
|
186
377
|
writeTodosTool,
|
|
187
378
|
findDefinitionTool,
|
|
188
|
-
getFileStructureTool
|
|
379
|
+
getFileStructureTool,
|
|
380
|
+
...workItemTools
|
|
189
381
|
];
|
|
190
382
|
if (skillTool) cliTools.push(skillTool);
|
|
191
383
|
if (config.preferences.enableCoordinatorMode === true) {
|
|
@@ -219,16 +411,15 @@ async function handleHeadlessCommand(options) {
|
|
|
219
411
|
});
|
|
220
412
|
agentContext.currentAgent = agent;
|
|
221
413
|
agent.observationQueue = agentContext.observationQueue;
|
|
222
|
-
if (
|
|
223
|
-
const emitNdjson = (obj) => process.stdout.write(JSON.stringify(obj) + "\n");
|
|
414
|
+
if (streaming) {
|
|
224
415
|
agent.on("thought", (step) => {
|
|
225
|
-
|
|
416
|
+
emit({
|
|
226
417
|
type: "thought",
|
|
227
418
|
content: step.content
|
|
228
419
|
});
|
|
229
420
|
});
|
|
230
421
|
agent.on("action", (step) => {
|
|
231
|
-
|
|
422
|
+
emit({
|
|
232
423
|
type: "action",
|
|
233
424
|
content: step.content,
|
|
234
425
|
toolName: step.metadata?.toolName,
|
|
@@ -236,7 +427,7 @@ async function handleHeadlessCommand(options) {
|
|
|
236
427
|
});
|
|
237
428
|
});
|
|
238
429
|
agent.on("observation", (step) => {
|
|
239
|
-
|
|
430
|
+
emit({
|
|
240
431
|
type: "observation",
|
|
241
432
|
content: step.content,
|
|
242
433
|
toolName: step.metadata?.toolName
|
|
@@ -255,6 +446,7 @@ async function handleHeadlessCommand(options) {
|
|
|
255
446
|
} finally {
|
|
256
447
|
backgroundManager.setCurrentTurn(null);
|
|
257
448
|
}
|
|
449
|
+
const richContent = reconstructTurnBlocks(result.steps, result.finalAnswer);
|
|
258
450
|
const finalSession = {
|
|
259
451
|
...session,
|
|
260
452
|
messages: [{
|
|
@@ -266,6 +458,7 @@ async function handleHeadlessCommand(options) {
|
|
|
266
458
|
id: v4(),
|
|
267
459
|
role: "assistant",
|
|
268
460
|
content: result.finalAnswer,
|
|
461
|
+
...richContent ? { richContent } : {},
|
|
269
462
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
463
|
}],
|
|
271
464
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -282,6 +475,8 @@ async function handleHeadlessCommand(options) {
|
|
|
282
475
|
break;
|
|
283
476
|
case "json": {
|
|
284
477
|
const jsonResult = {
|
|
478
|
+
schemaVersion: HEADLESS_SCHEMA_VERSION,
|
|
479
|
+
runId,
|
|
285
480
|
result: result.finalAnswer,
|
|
286
481
|
steps: result.steps.map((s) => ({
|
|
287
482
|
type: s.type,
|
|
@@ -300,32 +495,33 @@ async function handleHeadlessCommand(options) {
|
|
|
300
495
|
process.stdout.write(JSON.stringify(jsonResult, null, 2) + "\n");
|
|
301
496
|
break;
|
|
302
497
|
}
|
|
303
|
-
case "stream-json":
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}) + "\n");
|
|
315
|
-
break;
|
|
498
|
+
case "stream-json": emit({
|
|
499
|
+
type: "result",
|
|
500
|
+
content: result.finalAnswer,
|
|
501
|
+
tokenUsage: {
|
|
502
|
+
totalTokens: result.completionInfo.totalTokens,
|
|
503
|
+
inputTokens: result.completionInfo.totalInputTokens,
|
|
504
|
+
outputTokens: result.completionInfo.totalOutputTokens
|
|
505
|
+
},
|
|
506
|
+
iterations: result.completionInfo.iterations,
|
|
507
|
+
toolCalls: result.completionInfo.toolCalls
|
|
508
|
+
});
|
|
316
509
|
}
|
|
317
510
|
await mcpManager.disconnect().catch(() => {});
|
|
318
|
-
if (wsManager) wsManager.disconnect();
|
|
319
511
|
setWebSocketToolExecutor(null);
|
|
320
512
|
agent.removeAllListeners();
|
|
321
513
|
process.exit(0);
|
|
322
514
|
} catch (error) {
|
|
323
515
|
const message = error instanceof Error ? error.message : String(error);
|
|
324
|
-
if (outputFormat === "json") process.stdout.write(JSON.stringify({
|
|
325
|
-
|
|
326
|
-
|
|
516
|
+
if (outputFormat === "json") process.stdout.write(JSON.stringify({
|
|
517
|
+
schemaVersion: HEADLESS_SCHEMA_VERSION,
|
|
518
|
+
runId,
|
|
327
519
|
error: message
|
|
328
520
|
}) + "\n");
|
|
521
|
+
else if (outputFormat === "stream-json") createHeadlessEmitter(runId, (line) => process.stdout.write(line))({
|
|
522
|
+
type: "error",
|
|
523
|
+
error: message
|
|
524
|
+
});
|
|
329
525
|
else process.stderr.write(`Error: ${message}\n`);
|
|
330
526
|
process.exit(1);
|
|
331
527
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as ConfigStore } from "../ConfigStore-
|
|
2
|
+
import { t as ConfigStore } from "../ConfigStore-DD3DcC3-.mjs";
|
|
3
|
+
import { t as version } from "../package-BqKSCbso.mjs";
|
|
3
4
|
//#region src/commands/mcpCommand.ts
|
|
4
5
|
/**
|
|
5
6
|
* External MCP commands (b4m mcp list, b4m mcp add, etc.)
|
|
@@ -68,6 +69,17 @@ async function handleMcpCommand(subcommand, argv) {
|
|
|
68
69
|
}
|
|
69
70
|
await handleDisable(config, argv.name, configStore);
|
|
70
71
|
break;
|
|
72
|
+
case "serve": {
|
|
73
|
+
const { handleMcpServeCommand } = await import("../serve-CuF0I5en.mjs");
|
|
74
|
+
await handleMcpServeCommand({
|
|
75
|
+
http: Boolean(argv.http),
|
|
76
|
+
port: typeof argv.port === "number" ? argv.port : void 0,
|
|
77
|
+
apiKey: typeof argv.apiKey === "string" ? argv.apiKey : void 0,
|
|
78
|
+
apiUrl: typeof argv.apiUrl === "string" ? argv.apiUrl : void 0,
|
|
79
|
+
version
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
71
83
|
default:
|
|
72
84
|
console.error(`❌ Unknown MCP subcommand: ${subcommand}`);
|
|
73
85
|
console.error("");
|
|
@@ -77,6 +89,7 @@ async function handleMcpCommand(subcommand, argv) {
|
|
|
77
89
|
console.error(" b4m mcp remove <name> - Remove an MCP server");
|
|
78
90
|
console.error(" b4m mcp enable <name> - Enable an MCP server");
|
|
79
91
|
console.error(" b4m mcp disable <name> - Disable an MCP server");
|
|
92
|
+
console.error(" b4m mcp serve [--http] [--port] - Run Bike4Mind as an MCP server");
|
|
80
93
|
process.exit(1);
|
|
81
94
|
}
|
|
82
95
|
}
|