@bike4mind/cli 3.1.0 → 3.3.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/dist/{AgentHistoryStore-O2gKB5YG.mjs → AgentHistoryStore-CLidbOHG.mjs} +6214 -5711
- package/dist/{ConfigStore-RHqyXcSS.mjs → ConfigStore-DUATzLxB.mjs} +897 -345
- package/dist/{WorkItemsClient-cmw7WYBy.mjs → WorkItemsClient-BSoOblGp.mjs} +1 -1
- package/dist/{buildAgent-C0jmyDL4.mjs → buildAgent-DyZWK0LW.mjs} +39 -216
- package/dist/commands/acpCommand.mjs +25 -17
- package/dist/commands/apiCommand.mjs +1 -1
- package/dist/commands/doctorCommand.mjs +1 -1
- package/dist/commands/envCommand.mjs +1 -1
- package/dist/commands/headlessCommand.mjs +73 -41
- package/dist/commands/mcpCommand.mjs +3 -3
- package/dist/commands/pluginCommand.mjs +1 -1
- package/dist/commands/updateCommand.mjs +1 -1
- package/dist/{dist-B1bGf-yn.mjs → dist-DPP7UmK9.mjs} +2 -2
- package/dist/index.mjs +41 -19
- package/dist/{package-DsG8Tgvs.mjs → package-DYPVrada.mjs} +1 -1
- package/dist/{serve-CAfCQose.mjs → serve-w6v3rZiR.mjs} +2 -2
- package/package.json +11 -11
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Pt as tokenEstimateMultiplier } from "./ConfigStore-DUATzLxB.mjs";
|
|
3
3
|
import { v4 } from "uuid";
|
|
4
4
|
import { get_encoding } from "tiktoken";
|
|
5
5
|
//#region ../../b4m-core/utils/dist/calibratedTokenizer.mjs
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { F as generateCliTools, G as setWebSocketToolExecutor, J as DeferredToolRegistry, U as wrapTools, V as buildSystemPrompt, Y as ReActAgent, a as loadProjectContext, c as createSseBackend, i as buildProjectAgentStore, l as ServerLlmBackend, n as BackgroundAgentManager, o as McpManager, q as getPlanModeFilePath, r as SubagentOrchestrator, t as AgentHistoryStore, ut as Logger } from "./AgentHistoryStore-CLidbOHG.mjs";
|
|
3
|
+
import { A as ARTIFACT_REMOVED_PLACEHOLDER, Ct as isUserInitiatedAbort, I as CONTEXT_WINDOW_SAFETY_BUFFER_TOKENS, Mt as stripToolArtifactMarkup, R as ChatModels, Y as ModelBackend, _t as getQuestErrorCode, b as logger, et as PermissionDeniedError, ft as createThinkMarkerEscaper, jt as stripDeliveredArtifactBlocks, k as ARTIFACT_DELIVERED_PLACEHOLDER, mt as filterToolArtifactMarkup, pt as escapeThinkMarkers, q as MODEL_INFO_FIELD_GROUP_OF, yt as hasDeliverablePinnedArtifact, z as CompletionReasoningEffortSchema } from "./ConfigStore-DUATzLxB.mjs";
|
|
4
4
|
import "crypto";
|
|
5
5
|
import { v4 } from "uuid";
|
|
6
|
-
import { z as z$1 } from "zod";
|
|
7
6
|
import "axios";
|
|
8
7
|
import "@aws-sdk/client-bedrock-runtime";
|
|
9
8
|
import "openai";
|
|
@@ -367,7 +366,8 @@ var AnthropicCachingAdapter = class {
|
|
|
367
366
|
const messagesParam = modifiedParams.messages;
|
|
368
367
|
if (strategy.cacheConversationHistory && Array.isArray(messagesParam) && messagesParam.length > 0) {
|
|
369
368
|
const messages = [...messagesParam];
|
|
370
|
-
|
|
369
|
+
let anchorIndex = messages.length - 1 - (strategy.historyCacheExcludeTrailingCount ?? 0);
|
|
370
|
+
while (anchorIndex >= 0 && messages[anchorIndex].role === "system") anchorIndex--;
|
|
371
371
|
if (anchorIndex >= 0) {
|
|
372
372
|
const anchorMsg = messages[anchorIndex];
|
|
373
373
|
const msgContent = anchorMsg.content;
|
|
@@ -394,12 +394,17 @@ var AnthropicCachingAdapter = class {
|
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
const tools = modifiedParams.tools;
|
|
397
|
-
|
|
397
|
+
const markIndex = Array.isArray(tools) ? tools.findLastIndex((t) => {
|
|
398
|
+
const tool = t;
|
|
399
|
+
const isServerTool = typeof tool.type === "string" && !("input_schema" in tool);
|
|
400
|
+
return tool.defer_loading !== true && !isServerTool;
|
|
401
|
+
}) : -1;
|
|
402
|
+
if (strategy.cacheTools && Array.isArray(tools) && markIndex >= 0) {
|
|
398
403
|
const toolsCopy = [...tools];
|
|
399
|
-
const
|
|
400
|
-
if (claim("tools", hasMarker(
|
|
401
|
-
toolsCopy[
|
|
402
|
-
...
|
|
404
|
+
const markTool = toolsCopy[markIndex];
|
|
405
|
+
if (claim("tools", hasMarker(markTool))) {
|
|
406
|
+
toolsCopy[markIndex] = {
|
|
407
|
+
...markTool,
|
|
403
408
|
cache_control: cacheControl
|
|
404
409
|
};
|
|
405
410
|
modifiedParams.tools = toolsCopy;
|
|
@@ -665,6 +670,7 @@ var NoOpCachingAdapter = class {
|
|
|
665
670
|
ModelBackend.Anthropic, new AnthropicCachingAdapter(), ModelBackend.OpenAI, new OpenAICachingAdapter(), ModelBackend.Gemini, new GeminiCachingAdapter(), ModelBackend.Bedrock, new AnthropicCachingAdapter(), ModelBackend.XAI, new XAICachingAdapter(), ModelBackend.Kimi, new KimiCachingAdapter(), ModelBackend.DeepSeek, new DeepSeekCachingAdapter(), ModelBackend.Ollama, new NoOpCachingAdapter(), ModelBackend.BFL, new NoOpCachingAdapter(), ModelBackend.VoyageAI, new NoOpCachingAdapter(), ModelBackend.AWS, new NoOpCachingAdapter(), ModelBackend.LocalImage, new NoOpCachingAdapter();
|
|
666
671
|
ChatModels.KIMI_K2_THINKING_BEDROCK, ChatModels.KIMI_K2_5_BEDROCK, ChatModels.DEEPSEEK_R1_BEDROCK, ChatModels.DEEPSEEK_FLASH, ChatModels.DEEPSEEK_V4_PRO, ChatModels.KIMI_K3;
|
|
667
672
|
ChatModels.CLAUDE_4_7_OPUS, ChatModels.CLAUDE_4_7_OPUS_BEDROCK, ChatModels.CLAUDE_4_8_OPUS, ChatModels.CLAUDE_4_8_OPUS_BEDROCK, ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK;
|
|
673
|
+
ChatModels.CLAUDE_5_SONNET, ChatModels.CLAUDE_5_SONNET_BEDROCK, ChatModels.CLAUDE_5_5_SONNET_BEDROCK, ChatModels.CLAUDE_FABLE_5, ChatModels.CLAUDE_FABLE_5_BEDROCK, ChatModels.CLAUDE_FABLE_5_1_BEDROCK, ChatModels.CLAUDE_5_OPUS, ChatModels.CLAUDE_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_OPUS, ChatModels.CLAUDE_5_5_OPUS_BEDROCK, ChatModels.CLAUDE_5_5_HAIKU, ChatModels.CLAUDE_5_5_HAIKU_BEDROCK;
|
|
668
674
|
new Logger();
|
|
669
675
|
ChatModels.CLAUDE_4_5_SONNET, ChatModels.CLAUDE_4_1_OPUS, ChatModels.CLAUDE_4_5_HAIKU, ChatModels.CLAUDE_4_5_OPUS, ChatModels.CLAUDE_4_6_SONNET, ChatModels.CLAUDE_4_6_OPUS;
|
|
670
676
|
ChatModels.CLAUDE_4_5_SONNET_BEDROCK, ChatModels.CLAUDE_4_5_HAIKU_BEDROCK, ChatModels.CLAUDE_4_5_OPUS_BEDROCK, ChatModels.CLAUDE_4_6_SONNET_BEDROCK, ChatModels.CLAUDE_4_6_OPUS_BEDROCK;
|
|
@@ -753,6 +759,19 @@ ChatModels.KIMI_K2_7_CODE, ChatModels.KIMI_K2_7_CODE_HIGHSPEED, ChatModels.KIMI_
|
|
|
753
759
|
ChatModels.KIMI_K2_7_CODE, ChatModels.KIMI_K2_7_CODE_HIGHSPEED;
|
|
754
760
|
ChatModels.KIMI_K2_7_CODE, ChatModels.KIMI_K2_7_CODE_HIGHSPEED, ChatModels.KIMI_K2_6;
|
|
755
761
|
ChatModels.KIMI_K3, ChatModels.KIMI_K2_7_CODE, ChatModels.KIMI_K2_7_CODE_HIGHSPEED, ChatModels.KIMI_K2_6, ChatModels.KIMI_K2_5;
|
|
762
|
+
/**
|
|
763
|
+
* Ollama's native /api/chat requires tool-call arguments as an object and rejects a JSON string
|
|
764
|
+
* with a 400. Unparseable or non-object input is kept under `_raw` rather than dropped.
|
|
765
|
+
*/
|
|
766
|
+
function toOllamaToolArguments(args) {
|
|
767
|
+
if (args !== null && typeof args === "object" && !Array.isArray(args)) return args;
|
|
768
|
+
if (typeof args !== "string" || args.trim() === "") return {};
|
|
769
|
+
try {
|
|
770
|
+
const parsed = JSON.parse(args);
|
|
771
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
772
|
+
} catch {}
|
|
773
|
+
return { _raw: args };
|
|
774
|
+
}
|
|
756
775
|
var OllamaBackend = class OllamaBackend {
|
|
757
776
|
_host;
|
|
758
777
|
_api;
|
|
@@ -1294,18 +1313,12 @@ var OllamaBackend = class OllamaBackend {
|
|
|
1294
1313
|
};
|
|
1295
1314
|
}
|
|
1296
1315
|
pushToolMessages(messages, tool, result, _thinkingBlocks) {
|
|
1297
|
-
let argumentsObj;
|
|
1298
|
-
try {
|
|
1299
|
-
argumentsObj = JSON.parse(tool.parameters);
|
|
1300
|
-
} catch {
|
|
1301
|
-
argumentsObj = { _raw: tool.parameters };
|
|
1302
|
-
}
|
|
1303
1316
|
messages.push({
|
|
1304
1317
|
content: "",
|
|
1305
1318
|
role: "assistant",
|
|
1306
1319
|
tool_calls: [{ function: {
|
|
1307
1320
|
name: tool.name,
|
|
1308
|
-
arguments:
|
|
1321
|
+
arguments: toOllamaToolArguments(tool.parameters)
|
|
1309
1322
|
} }]
|
|
1310
1323
|
});
|
|
1311
1324
|
messages.push({
|
|
@@ -1357,7 +1370,13 @@ var OllamaBackend = class OllamaBackend {
|
|
|
1357
1370
|
mapped.content = texts.join("\n");
|
|
1358
1371
|
if (images.length > 0) mapped.images = images;
|
|
1359
1372
|
} else mapped.content = msg.content != null ? String(msg.content) : "";
|
|
1360
|
-
if (Array.isArray(raw.tool_calls)) mapped.tool_calls = raw.tool_calls
|
|
1373
|
+
if (Array.isArray(raw.tool_calls)) mapped.tool_calls = raw.tool_calls.map((call) => ({
|
|
1374
|
+
...call,
|
|
1375
|
+
function: {
|
|
1376
|
+
...call.function,
|
|
1377
|
+
arguments: toOllamaToolArguments(call.function?.arguments)
|
|
1378
|
+
}
|
|
1379
|
+
}));
|
|
1361
1380
|
if (typeof raw.tool_name === "string") mapped.tool_name = raw.tool_name;
|
|
1362
1381
|
return mapped;
|
|
1363
1382
|
});
|
|
@@ -1379,7 +1398,7 @@ ChatModels.O1_PREVIEW, ChatModels.O1_MINI, ChatModels.O1, ChatModels.O3_MINI, Ch
|
|
|
1379
1398
|
ChatModels.GPT5, ChatModels.GPT5_MINI, ChatModels.GPT5_NANO, ChatModels.GPT5_CHAT_LATEST;
|
|
1380
1399
|
ChatModels.GPT5_1, ChatModels.GPT5_1_CHAT_LATEST;
|
|
1381
1400
|
ChatModels.GPT5_2, ChatModels.GPT5_2_CHAT_LATEST;
|
|
1382
|
-
ChatModels.GPT5_4, ChatModels.GPT5_4_MINI, ChatModels.GPT5_4_NANO;
|
|
1401
|
+
ChatModels.GPT5_4, ChatModels.GPT5_4_MINI, ChatModels.GPT5_4_MINI_2026_03_17, ChatModels.GPT5_4_NANO;
|
|
1383
1402
|
ChatModels.GPT5_5;
|
|
1384
1403
|
ChatModels.GPT5_6_SOL, ChatModels.GPT5_6_LUNA, ChatModels.GPT5_6_TERRA;
|
|
1385
1404
|
ModelBackend.Bedrock, ModelBackend.AWS, ModelBackend.OpenAI, ModelBackend.Anthropic, ModelBackend.Gemini, ModelBackend.Ollama, ModelBackend.BFL, ModelBackend.XAI, ModelBackend.Kimi, ModelBackend.DeepSeek, ModelBackend.LocalImage, ModelBackend.VoyageAI;
|
|
@@ -1430,203 +1449,7 @@ var NotifyingLlmBackend = class {
|
|
|
1430
1449
|
};
|
|
1431
1450
|
//#endregion
|
|
1432
1451
|
//#region src/tools/deferredToolRegistry.ts
|
|
1433
|
-
|
|
1434
|
-
* Registry of tool schemas that are NOT loaded into the model's initial tool
|
|
1435
|
-
* list. The model sees only the names (via the system prompt directory) and
|
|
1436
|
-
* must call the `tool_search` meta-tool to load schemas on demand.
|
|
1437
|
-
*
|
|
1438
|
-
* This mirrors Claude Code's deferred-tool pattern. The win is large for
|
|
1439
|
-
* heavy MCP integrations (e.g. 41 GitHub MCP tools at ~250-350 tokens of
|
|
1440
|
-
* JSONSchema each = ~10-15k tokens per turn that's now ~1-1.5k of names).
|
|
1441
|
-
*/
|
|
1442
|
-
var DeferredToolRegistry = class {
|
|
1443
|
-
constructor() {
|
|
1444
|
-
this.byName = /* @__PURE__ */ new Map();
|
|
1445
|
-
this.directoryNames = [];
|
|
1446
|
-
}
|
|
1447
|
-
/** Replace registry contents with the supplied tools. Idempotent. */
|
|
1448
|
-
register(tools) {
|
|
1449
|
-
this.byName.clear();
|
|
1450
|
-
for (const tool of tools) this.byName.set(tool.toolSchema.name, tool);
|
|
1451
|
-
this.directoryNames = Object.freeze([...this.byName.keys()].sort());
|
|
1452
|
-
logger.debug(`[DeferredToolRegistry] Registered ${tools.length} deferred tool(s)`);
|
|
1453
|
-
}
|
|
1454
|
-
clear() {
|
|
1455
|
-
this.byName.clear();
|
|
1456
|
-
this.directoryNames = Object.freeze([]);
|
|
1457
|
-
}
|
|
1458
|
-
size() {
|
|
1459
|
-
return this.byName.size;
|
|
1460
|
-
}
|
|
1461
|
-
has(name) {
|
|
1462
|
-
return this.byName.has(name);
|
|
1463
|
-
}
|
|
1464
|
-
get(name) {
|
|
1465
|
-
return this.byName.get(name);
|
|
1466
|
-
}
|
|
1467
|
-
getAll() {
|
|
1468
|
-
return Array.from(this.byName.values());
|
|
1469
|
-
}
|
|
1470
|
-
/** Return tools whose names appear in the supplied list, in input order. */
|
|
1471
|
-
getByNames(names) {
|
|
1472
|
-
const found = [];
|
|
1473
|
-
for (const name of names) {
|
|
1474
|
-
const tool = this.byName.get(name);
|
|
1475
|
-
if (tool) found.push(tool);
|
|
1476
|
-
}
|
|
1477
|
-
return found;
|
|
1478
|
-
}
|
|
1479
|
-
/**
|
|
1480
|
-
* Rank-search deferred tools by query terms. Name matches outrank
|
|
1481
|
-
* description matches; exact substring on name wins ties.
|
|
1482
|
-
*/
|
|
1483
|
-
searchByKeywords(query, maxResults) {
|
|
1484
|
-
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
|
|
1485
|
-
if (terms.length === 0) return [];
|
|
1486
|
-
const scored = [];
|
|
1487
|
-
for (const tool of this.byName.values()) {
|
|
1488
|
-
const name = tool.toolSchema.name.toLowerCase();
|
|
1489
|
-
const desc = (tool.toolSchema.description || "").toLowerCase();
|
|
1490
|
-
let score = 0;
|
|
1491
|
-
for (const term of terms) {
|
|
1492
|
-
if (name.includes(term)) score += 10;
|
|
1493
|
-
if (desc.includes(term)) score += 1;
|
|
1494
|
-
}
|
|
1495
|
-
if (score > 0) scored.push({
|
|
1496
|
-
tool,
|
|
1497
|
-
score
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
scored.sort((a, b) => b.score - a.score);
|
|
1501
|
-
return scored.slice(0, maxResults).map((s) => s.tool);
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Directory entries rendered into the cache-stamped system-prompt reminder.
|
|
1505
|
-
* Returns the frozen snapshot captured at `register()`, NOT live `byName`
|
|
1506
|
-
* keys, so loading a tool mid-session can never change a byte of the cached
|
|
1507
|
-
* system block (issue #213). A loaded tool remaining listed here is
|
|
1508
|
-
* harmless: re-selecting it via `tool_search` is an idempotent no-op.
|
|
1509
|
-
*/
|
|
1510
|
-
getDirectoryNames() {
|
|
1511
|
-
return [...this.directoryNames];
|
|
1512
|
-
}
|
|
1513
|
-
};
|
|
1514
|
-
const deferredToolRegistry = new DeferredToolRegistry();
|
|
1515
|
-
//#endregion
|
|
1516
|
-
//#region src/tools/toolSearchTool.ts
|
|
1517
|
-
/**
|
|
1518
|
-
* Default number of tools returned for a keyword search. Matches Claude
|
|
1519
|
-
* Code's ToolSearch convention. 5 keeps the response payload small while
|
|
1520
|
-
* surfacing enough alternatives for the model to refine its query.
|
|
1521
|
-
*/
|
|
1522
|
-
const DEFAULT_MAX_RESULTS = 5;
|
|
1523
|
-
/**
|
|
1524
|
-
* Runtime validation for tool_search params. The LLM produces these
|
|
1525
|
-
* values, so we validate at this boundary rather than trusting the
|
|
1526
|
-
* shape. Coerces `max_results` from string->number for models that emit
|
|
1527
|
-
* numeric-looking strings.
|
|
1528
|
-
*/
|
|
1529
|
-
const ToolSearchParamsSchema = z$1.object({
|
|
1530
|
-
query: z$1.string().min(1, "query must be a non-empty string"),
|
|
1531
|
-
max_results: z$1.coerce.number().int().min(1).max(20).optional()
|
|
1532
|
-
});
|
|
1533
|
-
/**
|
|
1534
|
-
* Parse the query string. Two forms:
|
|
1535
|
-
* - `select:name1,name2,...` - exact-name selection
|
|
1536
|
-
* - free text - keyword search across name + description
|
|
1537
|
-
*/
|
|
1538
|
-
function parseQuery(query) {
|
|
1539
|
-
const trimmed = query.trim();
|
|
1540
|
-
const selectMatch = trimmed.match(/^select:(.+)$/i);
|
|
1541
|
-
if (selectMatch) return {
|
|
1542
|
-
mode: "select",
|
|
1543
|
-
names: selectMatch[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0)
|
|
1544
|
-
};
|
|
1545
|
-
return {
|
|
1546
|
-
mode: "search",
|
|
1547
|
-
text: trimmed
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* Format the loaded-tools response. Mirrors Claude Code's convention:
|
|
1552
|
-
* one <function>{...}</function> line per matched tool. The model has
|
|
1553
|
-
* already seen this format in its tool-registration system messages, so
|
|
1554
|
-
* it parses without additional explanation.
|
|
1555
|
-
*
|
|
1556
|
-
* Note: the schemas are *also* injected into context.tools by the caller,
|
|
1557
|
-
* so on the next iteration the model gets them as native tool definitions.
|
|
1558
|
-
* The text response here is for in-turn awareness and audit trail.
|
|
1559
|
-
*/
|
|
1560
|
-
function renderToolsBlock(tools) {
|
|
1561
|
-
if (tools.length === 0) return "";
|
|
1562
|
-
return `<functions>\n${tools.map((tool) => {
|
|
1563
|
-
const schema = {
|
|
1564
|
-
description: tool.toolSchema.description,
|
|
1565
|
-
name: tool.toolSchema.name,
|
|
1566
|
-
parameters: tool.toolSchema.parameters
|
|
1567
|
-
};
|
|
1568
|
-
return `<function>${JSON.stringify(schema)}</function>`;
|
|
1569
|
-
}).join("\n")}\n</functions>`;
|
|
1570
|
-
}
|
|
1571
|
-
/**
|
|
1572
|
-
* Build the tool_search meta-tool. The returned tool has a closure over
|
|
1573
|
-
* the supplied `toolListAccessor`, which it uses to push newly-resolved
|
|
1574
|
-
* tool schemas into the live agent context.
|
|
1575
|
-
*
|
|
1576
|
-
* Idempotent: re-loading a tool that's already in the context is a no-op.
|
|
1577
|
-
*/
|
|
1578
|
-
function createToolSearchTool(toolListAccessor) {
|
|
1579
|
-
return {
|
|
1580
|
-
toolSchema: {
|
|
1581
|
-
name: "tool_search",
|
|
1582
|
-
description: "Fetches full schema definitions for deferred tools so they can be called. Deferred tools appear by name only in a system reminder; their parameter schemas are NOT loaded by default. Use this tool to load schemas on demand. Query forms: 'select:name1,name2' for exact selection, or free-text keywords to search by name and description. Once a tool's schema is returned, it becomes callable in subsequent turns.",
|
|
1583
|
-
parameters: {
|
|
1584
|
-
type: "object",
|
|
1585
|
-
properties: {
|
|
1586
|
-
query: {
|
|
1587
|
-
type: "string",
|
|
1588
|
-
description: "Either 'select:<comma-separated names>' to fetch specific tools, or free-text keywords (e.g. 'github pull request') to rank-search deferred tools."
|
|
1589
|
-
},
|
|
1590
|
-
max_results: {
|
|
1591
|
-
type: "number",
|
|
1592
|
-
description: `Maximum number of tools to return for keyword search. Defaults to ${DEFAULT_MAX_RESULTS}. Ignored for 'select:' queries.`
|
|
1593
|
-
}
|
|
1594
|
-
},
|
|
1595
|
-
required: ["query"]
|
|
1596
|
-
}
|
|
1597
|
-
},
|
|
1598
|
-
toolFn: async (params) => {
|
|
1599
|
-
const parsedParams = ToolSearchParamsSchema.safeParse(params ?? {});
|
|
1600
|
-
if (!parsedParams.success) {
|
|
1601
|
-
const issue = parsedParams.error.issues[0];
|
|
1602
|
-
return `tool_search: invalid parameters — ${issue.path.join(".") || "params"}: ${issue.message}`;
|
|
1603
|
-
}
|
|
1604
|
-
const { query, max_results } = parsedParams.data;
|
|
1605
|
-
const parsed = parseQuery(query);
|
|
1606
|
-
let matched;
|
|
1607
|
-
let unmatched = [];
|
|
1608
|
-
if (parsed.mode === "select") {
|
|
1609
|
-
matched = deferredToolRegistry.getByNames(parsed.names);
|
|
1610
|
-
const foundNames = new Set(matched.map((t) => t.toolSchema.name));
|
|
1611
|
-
unmatched = parsed.names.filter((n) => !foundNames.has(n));
|
|
1612
|
-
} else {
|
|
1613
|
-
const max = max_results ?? DEFAULT_MAX_RESULTS;
|
|
1614
|
-
matched = deferredToolRegistry.searchByKeywords(parsed.text, max);
|
|
1615
|
-
}
|
|
1616
|
-
if (matched.length === 0) return parsed.mode === "select" ? `tool_search: no deferred tools matched ${parsed.names.join(", ")}. Use a free-text query to search.` : `tool_search: no deferred tools matched query "${parsed.text}".`;
|
|
1617
|
-
const liveTools = toolListAccessor();
|
|
1618
|
-
const liveNames = new Set(liveTools.map((t) => t.toolSchema.name));
|
|
1619
|
-
let added = 0;
|
|
1620
|
-
for (const tool of matched) if (!liveNames.has(tool.toolSchema.name)) {
|
|
1621
|
-
liveTools.push(tool);
|
|
1622
|
-
added++;
|
|
1623
|
-
}
|
|
1624
|
-
logger.debug(`[tool_search] query="${query}" matched=${matched.length} added=${added} alreadyLoaded=${matched.length - added}`);
|
|
1625
|
-
const block = renderToolsBlock(matched);
|
|
1626
|
-
return `${`Loaded ${added} new tool schema(s)${added < matched.length ? ` (${matched.length - added} already loaded)` : ""}. These are now callable in your next message.${unmatched.length > 0 ? `\n\nNot found: ${unmatched.join(", ")}` : ""}`}\n\n${block}`;
|
|
1627
|
-
}
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1452
|
+
const deferredToolRegistry = new DeferredToolRegistry({ logger });
|
|
1630
1453
|
//#endregion
|
|
1631
1454
|
//#region src/llm/MultiLlmBackend.ts
|
|
1632
1455
|
/**
|
|
@@ -2166,4 +1989,4 @@ function buildAgent(input) {
|
|
|
2166
1989
|
};
|
|
2167
1990
|
}
|
|
2168
1991
|
//#endregion
|
|
2169
|
-
export {
|
|
1992
|
+
export { NotifyingLlmBackend as a, deferredToolRegistry as i, buildSupportingStores as n, buildLlmBackend as r, buildAgent as t };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool,
|
|
3
|
-
import {
|
|
4
|
-
import { a as
|
|
5
|
-
import { a as ApiClient } from "../dist-
|
|
2
|
+
import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, G as setWebSocketToolExecutor, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, X as createToolSearchTool, b as createGetFileStructureTool, j as FallbackLlmBackend, k as createBackgroundAgentTools, nt as CheckpointStore, rt as SessionStore, s as buildSandbox, w as createWriteTodosTool } from "../AgentHistoryStore-CLidbOHG.mjs";
|
|
3
|
+
import { b as logger, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-DUATzLxB.mjs";
|
|
4
|
+
import { a as NotifyingLlmBackend, i as deferredToolRegistry, n as buildSupportingStores, r as buildLlmBackend, t as buildAgent } from "../buildAgent-DyZWK0LW.mjs";
|
|
5
|
+
import { a as ApiClient } from "../dist-DPP7UmK9.mjs";
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
7
|
import { existsSync, realpathSync, statSync } from "fs";
|
|
8
8
|
import { isAbsolute } from "path";
|
|
@@ -92,18 +92,19 @@ function truncate(text, max) {
|
|
|
92
92
|
const PERMISSION_OPTION_ALLOW_ONCE = "allow-once";
|
|
93
93
|
const PERMISSION_OPTION_ALLOW_ALWAYS = "allow-always";
|
|
94
94
|
const PERMISSION_OPTION_REJECT = "deny";
|
|
95
|
-
function buildPermissionOptions() {
|
|
95
|
+
function buildPermissionOptions(options = {}) {
|
|
96
|
+
const { allowAlways = true } = options;
|
|
96
97
|
return [
|
|
97
98
|
{
|
|
98
99
|
optionId: PERMISSION_OPTION_ALLOW_ONCE,
|
|
99
100
|
name: "Allow once",
|
|
100
101
|
kind: "allow_once"
|
|
101
102
|
},
|
|
102
|
-
{
|
|
103
|
+
...allowAlways ? [{
|
|
103
104
|
optionId: PERMISSION_OPTION_ALLOW_ALWAYS,
|
|
104
105
|
name: "Always allow",
|
|
105
106
|
kind: "allow_always"
|
|
106
|
-
},
|
|
107
|
+
}] : [],
|
|
107
108
|
{
|
|
108
109
|
optionId: PERMISSION_OPTION_REJECT,
|
|
109
110
|
name: "Reject",
|
|
@@ -271,12 +272,13 @@ var AcpServer = class {
|
|
|
271
272
|
this.stackPromise = null;
|
|
272
273
|
this.stack = null;
|
|
273
274
|
this.activeTurn = null;
|
|
274
|
-
this.promptFn = async (toolName, args, preview) => {
|
|
275
|
+
this.promptFn = async (toolName, args, preview, kind) => {
|
|
276
|
+
const isSandboxEscalation = kind === "sandbox-escalation";
|
|
275
277
|
const turn = this.activeTurn;
|
|
276
278
|
if (!turn) return { action: "deny" };
|
|
277
279
|
const toolCall = {
|
|
278
280
|
toolCallId: `perm-${randomUUID()}`,
|
|
279
|
-
title: toolCallTitle(toolName, args)
|
|
281
|
+
title: `${isSandboxEscalation ? "Run outside sandbox: " : ""}${toolCallTitle(toolName, args)}`,
|
|
280
282
|
kind: toolKind(toolName),
|
|
281
283
|
status: "pending",
|
|
282
284
|
rawInput: args,
|
|
@@ -294,13 +296,15 @@ var AcpServer = class {
|
|
|
294
296
|
turn.signal.addEventListener("abort", onClose);
|
|
295
297
|
this.connectionSignal.addEventListener("abort", onClose);
|
|
296
298
|
try {
|
|
297
|
-
|
|
299
|
+
const action = await Promise.race([turn.client.request(methods.client.session.requestPermission, {
|
|
298
300
|
sessionId: turn.sessionId,
|
|
299
301
|
toolCall,
|
|
300
|
-
options: buildPermissionOptions()
|
|
302
|
+
options: buildPermissionOptions({ allowAlways: !isSandboxEscalation })
|
|
301
303
|
}, { cancellationSignal: decisionController.signal }).then((res) => permissionResponseFromOutcome(res?.outcome)).catch(() => "deny"), new Promise((resolve) => {
|
|
302
304
|
decisionController.signal.addEventListener("abort", () => resolve("deny"), { once: true });
|
|
303
|
-
})])
|
|
305
|
+
})]);
|
|
306
|
+
if (isSandboxEscalation && action === "allow-always") return { action: "deny" };
|
|
307
|
+
return { action };
|
|
304
308
|
} finally {
|
|
305
309
|
clearTimeout(timer);
|
|
306
310
|
turn.signal.removeEventListener("abort", onClose);
|
|
@@ -424,7 +428,7 @@ var AcpServer = class {
|
|
|
424
428
|
result = await stack.agent.run(userText, {
|
|
425
429
|
signal: abortController.signal,
|
|
426
430
|
previousMessages: session.history,
|
|
427
|
-
isReadOnlyTool,
|
|
431
|
+
isReadOnlyTool: (toolName) => stack.permissionManager.catalog.isReadOnly(toolName),
|
|
428
432
|
maxHistoryIterations: 4
|
|
429
433
|
});
|
|
430
434
|
} catch (error) {
|
|
@@ -594,7 +598,7 @@ var AcpServer = class {
|
|
|
594
598
|
tokenGetter,
|
|
595
599
|
startupLog
|
|
596
600
|
});
|
|
597
|
-
const permissionManager = new PermissionManager(config.trustedTools ?? [],
|
|
601
|
+
const permissionManager = new PermissionManager(new ToolCatalog(), config.trustedTools ?? [], config.tools.disabled ?? []);
|
|
598
602
|
const checkpointStore = new CheckpointStore(this.configStore.getProjectConfigDir() ?? process.cwd());
|
|
599
603
|
const stackSessionId = randomUUID();
|
|
600
604
|
const { sandboxOrchestrator } = await buildSandbox({
|
|
@@ -627,9 +631,13 @@ var AcpServer = class {
|
|
|
627
631
|
const llmWithFallback = config.fallbackModels && config.fallbackModels.length > 0 ? new FallbackLlmBackend(llm, config.fallbackModels, (from, to, error) => logger.debug(`[acp] Model "${from}" failed (${error.message}); falling back to "${to}"`)) : llm;
|
|
628
632
|
const notifyingLlm = new NotifyingLlmBackend(llmWithFallback, backgroundManager);
|
|
629
633
|
const agentToolsRef = { current: null };
|
|
630
|
-
const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool(
|
|
631
|
-
|
|
632
|
-
|
|
634
|
+
const toolSearchTool = deferredToolRegistry.size() > 0 ? createToolSearchTool({
|
|
635
|
+
registry: deferredToolRegistry,
|
|
636
|
+
logger,
|
|
637
|
+
toolListAccessor: () => {
|
|
638
|
+
if (!agentToolsRef.current) throw new Error("tool_search invoked before agent context was wired");
|
|
639
|
+
return agentToolsRef.current;
|
|
640
|
+
}
|
|
633
641
|
}) : null;
|
|
634
642
|
const cliTools = this.buildCliTools({
|
|
635
643
|
config,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { m as resolveApiEndpoint, t as ConfigStore, y as parseApiUrl } from "../ConfigStore-DUATzLxB.mjs";
|
|
3
3
|
//#region src/commands/apiCommand.ts
|
|
4
4
|
/**
|
|
5
5
|
* External API config command (--api-url / --reset-api)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as version } from "../package-
|
|
2
|
+
import { t as version } from "../package-DYPVrada.mjs";
|
|
3
3
|
import { c as isNpmPrefixWritable, i as fetchLatestVersion, r as compareSemver } from "../updateChecker-CCLVVdH0.mjs";
|
|
4
4
|
import { t as checkRipgrep } from "../ripgrepCheck-BmkyTK2i.mjs";
|
|
5
5
|
import { existsSync } from "fs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-
|
|
5
|
-
import { a as ApiClient } from "../dist-
|
|
2
|
+
import { $ as CustomCommandStore, A as createAgentDelegateTool, C as createTodoStore, D as createCoordinateTaskTool, F as generateCliTools, G as setWebSocketToolExecutor, K as classifyCommandRisk, N as PermissionManager, O as createResumeAgentTool, P as buildCliToolList, Q as RemoteSkillSource, S as createFindDefinitionTool, T as createSkillTool, U as wrapTools, V as buildSystemPrompt, Y as ReActAgent, a as loadProjectContext, b as createGetFileStructureTool, c as createSseBackend, i as buildProjectAgentStore, j as FallbackLlmBackend, k as createBackgroundAgentTools, n as BackgroundAgentManager, nt as CheckpointStore, o as McpManager, r as SubagentOrchestrator, rt as SessionStore, s as buildSandbox, t as AgentHistoryStore, w as createWriteTodosTool, x as createWorkItemTools } from "../AgentHistoryStore-CLidbOHG.mjs";
|
|
3
|
+
import { a as commandTextOf, b as logger, c as toolCategory, i as ToolCatalog, p as requireApiUrl, t as ConfigStore } from "../ConfigStore-DUATzLxB.mjs";
|
|
4
|
+
import { r as reconstructTurnBlocks, t as WorkItemsClient } from "../WorkItemsClient-BSoOblGp.mjs";
|
|
5
|
+
import { a as ApiClient } from "../dist-DPP7UmK9.mjs";
|
|
6
6
|
import { randomBytes } from "crypto";
|
|
7
7
|
import { v4 } from "uuid";
|
|
8
8
|
import { readFile } from "fs/promises";
|
|
@@ -24,7 +24,7 @@ import { readFile } from "fs/promises";
|
|
|
24
24
|
* field or a new event type (backward-compatible). Consumers should reject a
|
|
25
25
|
* MAJOR they do not understand.
|
|
26
26
|
*/
|
|
27
|
-
const HEADLESS_SCHEMA_VERSION = "1.
|
|
27
|
+
const HEADLESS_SCHEMA_VERSION = "1.1.0";
|
|
28
28
|
/** Serialize an event with the protocol envelope stamped on. Exposed for tests. */
|
|
29
29
|
function stampEvent(runId, event) {
|
|
30
30
|
return {
|
|
@@ -87,15 +87,15 @@ function parseStringArray(raw, inputType) {
|
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
89
|
* Classify the risk of a single tool invocation for the permission protocol.
|
|
90
|
-
* Shell-like tools are classified from their actual
|
|
91
|
-
* shared command-risk tokenizer
|
|
92
|
-
*
|
|
93
|
-
*
|
|
90
|
+
* Shell-like tools (a descriptor commandArg) are classified from their actual
|
|
91
|
+
* command text, reusing the shared command-risk tokenizer, exactly as the
|
|
92
|
+
* gateway's command-risk stage does; every other tool falls back to a level
|
|
93
|
+
* derived from its permission category (auto_approve -> low, prompt_always ->
|
|
94
|
+
* high, else medium). Never throws: a classifier failure fails closed at `high`.
|
|
94
95
|
*/
|
|
95
|
-
function classifyToolRisk(
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (typeof command === "string") try {
|
|
96
|
+
function classifyToolRisk(descriptor, args) {
|
|
97
|
+
const command = commandTextOf(descriptor, args);
|
|
98
|
+
if (command !== void 0) try {
|
|
99
99
|
return classifyCommandRisk(command);
|
|
100
100
|
} catch {
|
|
101
101
|
return {
|
|
@@ -103,6 +103,7 @@ function classifyToolRisk(toolName, args, category) {
|
|
|
103
103
|
reasons: ["command risk analysis failed (fail closed)"]
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
const category = toolCategory(descriptor);
|
|
106
107
|
return {
|
|
107
108
|
level: category === "auto_approve" ? "low" : category === "prompt_always" ? "high" : "medium",
|
|
108
109
|
reasons: []
|
|
@@ -171,9 +172,21 @@ function evaluatePermissionPolicy(policy, toolName, riskLevel) {
|
|
|
171
172
|
reason: `policy default (${policy.defaultAction})`
|
|
172
173
|
};
|
|
173
174
|
}
|
|
175
|
+
/** The permission_request event for a gated tool call; `kind` tells a consumer what is being asked. */
|
|
176
|
+
function buildPermissionRequestEvent(toolName, risk, kind = "tool") {
|
|
177
|
+
return {
|
|
178
|
+
type: "permission_request",
|
|
179
|
+
toolName,
|
|
180
|
+
risk,
|
|
181
|
+
kind
|
|
182
|
+
};
|
|
183
|
+
}
|
|
174
184
|
/**
|
|
175
185
|
* Resolve a headless permission decision from the fixed precedence ladder:
|
|
176
186
|
* --dangerously-skip-permissions -> allow-once (explicit blanket override)
|
|
187
|
+
* a sandbox-escalation request -> deny (running outside the OS sandbox is its own
|
|
188
|
+
* decision; no policy allow-list or risk threshold
|
|
189
|
+
* approves it)
|
|
177
190
|
* a directory-grant request -> deny (widening the filesystem allow-list is
|
|
178
191
|
* its own decision; headless has no human to
|
|
179
192
|
* grant it, so fail closed - never inherit the
|
|
@@ -183,11 +196,15 @@ function evaluatePermissionPolicy(policy, toolName, riskLevel) {
|
|
|
183
196
|
* Extracted from the headless prompt closure so the ladder is unit-testable.
|
|
184
197
|
*/
|
|
185
198
|
function resolveHeadlessPermissionDecision(params) {
|
|
186
|
-
const { dangerouslySkipPermissions, isDirectoryGrant, toolName, riskLevel, permissionPolicy } = params;
|
|
199
|
+
const { dangerouslySkipPermissions, isDirectoryGrant, isSandboxEscalation, toolName, riskLevel, permissionPolicy } = params;
|
|
187
200
|
if (dangerouslySkipPermissions) return {
|
|
188
201
|
action: "allow-once",
|
|
189
202
|
reason: "dangerously-skip-permissions"
|
|
190
203
|
};
|
|
204
|
+
if (isSandboxEscalation) return {
|
|
205
|
+
action: "deny",
|
|
206
|
+
reason: "running outside the sandbox not approved in headless mode"
|
|
207
|
+
};
|
|
191
208
|
if (isDirectoryGrant) return {
|
|
192
209
|
action: "deny",
|
|
193
210
|
reason: "directory access not granted in headless mode"
|
|
@@ -205,6 +222,39 @@ function resolveHeadlessPermissionDecision(params) {
|
|
|
205
222
|
};
|
|
206
223
|
}
|
|
207
224
|
//#endregion
|
|
225
|
+
//#region src/commands/headlessPermissionPrompt.ts
|
|
226
|
+
/**
|
|
227
|
+
* The headless permission prompt. Every gated tool surfaces a structured permission_request +
|
|
228
|
+
* permission_decision pair on the stream (so decisions are never silent), then resolves the
|
|
229
|
+
* decision from resolveHeadlessPermissionDecision's precedence ladder.
|
|
230
|
+
*/
|
|
231
|
+
function createHeadlessPromptFn(deps) {
|
|
232
|
+
const { catalog, dangerouslySkipPermissions, permissionPolicy, emit, warn } = deps;
|
|
233
|
+
return (toolName, args, _preview, kind) => {
|
|
234
|
+
const risk = classifyToolRisk(catalog.resolve(toolName), args);
|
|
235
|
+
emit?.(buildPermissionRequestEvent(toolName, risk, kind));
|
|
236
|
+
const decision = resolveHeadlessPermissionDecision({
|
|
237
|
+
dangerouslySkipPermissions,
|
|
238
|
+
isDirectoryGrant: kind === "directory-grant",
|
|
239
|
+
isSandboxEscalation: kind === "sandbox-escalation",
|
|
240
|
+
toolName,
|
|
241
|
+
riskLevel: risk.level,
|
|
242
|
+
permissionPolicy
|
|
243
|
+
});
|
|
244
|
+
if (emit) emit({
|
|
245
|
+
type: "permission_decision",
|
|
246
|
+
toolName,
|
|
247
|
+
action: decision.action,
|
|
248
|
+
reason: decision.reason,
|
|
249
|
+
risk: risk.level,
|
|
250
|
+
kind: kind ?? "tool"
|
|
251
|
+
});
|
|
252
|
+
else if (decision.action === "deny") warn(kind === "sandbox-escalation" ? `Warning: Tool "${toolName}" was denied by the OS sandbox, and retrying it outside the sandbox needs approval that headless mode cannot grant. Use --dangerously-skip-permissions to allow it.\n` : `Warning: Tool "${toolName}" requires permission and was denied (${decision.reason}). Grant it via --permission-policy, or --dangerously-skip-permissions to allow all tools.\n`);
|
|
253
|
+
if (decision.action === "allow-once") logger.debug(`[headless] Auto-allowing tool: ${toolName}`);
|
|
254
|
+
return Promise.resolve({ action: decision.action });
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
208
258
|
//#region src/commands/headlessCommand.ts
|
|
209
259
|
/**
|
|
210
260
|
* Headless/programmatic mode command (b4m -p "query")
|
|
@@ -319,34 +369,16 @@ async function handleHeadlessCommand(options) {
|
|
|
319
369
|
}
|
|
320
370
|
};
|
|
321
371
|
await logger.initialize(session.id);
|
|
322
|
-
const permissionManager = new PermissionManager(config.trustedTools ?? [],
|
|
372
|
+
const permissionManager = new PermissionManager(new ToolCatalog(), config.trustedTools ?? [], config.tools.disabled);
|
|
323
373
|
const emit = createHeadlessEmitter(runId, (line) => process.stdout.write(line));
|
|
324
374
|
const streaming = outputFormat === "stream-json";
|
|
325
|
-
const promptFn = (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const decision = resolveHeadlessPermissionDecision({
|
|
333
|
-
dangerouslySkipPermissions,
|
|
334
|
-
isDirectoryGrant: kind === "directory-grant",
|
|
335
|
-
toolName,
|
|
336
|
-
riskLevel: risk.level,
|
|
337
|
-
permissionPolicy
|
|
338
|
-
});
|
|
339
|
-
if (streaming) emit({
|
|
340
|
-
type: "permission_decision",
|
|
341
|
-
toolName,
|
|
342
|
-
action: decision.action,
|
|
343
|
-
reason: decision.reason,
|
|
344
|
-
risk: risk.level
|
|
345
|
-
});
|
|
346
|
-
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`);
|
|
347
|
-
if (decision.action === "allow-once") logger.debug(`[headless] Auto-allowing tool: ${toolName}`);
|
|
348
|
-
return Promise.resolve({ action: decision.action });
|
|
349
|
-
};
|
|
375
|
+
const promptFn = createHeadlessPromptFn({
|
|
376
|
+
catalog: permissionManager.catalog,
|
|
377
|
+
dangerouslySkipPermissions,
|
|
378
|
+
permissionPolicy,
|
|
379
|
+
emit: streaming ? emit : null,
|
|
380
|
+
warn: (message) => process.stderr.write(message)
|
|
381
|
+
});
|
|
350
382
|
const userQuestionFn = (_payload) => {
|
|
351
383
|
process.stderr.write("Warning: Agent requested user input; headless mode cannot respond interactively. Answering with empty response.\n");
|
|
352
384
|
return Promise.resolve({ answers: [] });
|
|
@@ -482,7 +514,7 @@ async function handleHeadlessCommand(options) {
|
|
|
482
514
|
try {
|
|
483
515
|
result = await agent.run(fullPrompt, {
|
|
484
516
|
parallelExecution: config.preferences.enableParallelToolExecution === true,
|
|
485
|
-
isReadOnlyTool,
|
|
517
|
+
isReadOnlyTool: (toolName) => permissionManager.catalog.isReadOnly(toolName),
|
|
486
518
|
maxHistoryIterations: 4
|
|
487
519
|
});
|
|
488
520
|
} finally {
|