@botconnector/bccli 0.4.0 → 0.4.2
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/README.en.md +13 -2
- package/README.md +13 -2
- package/dist/abort.d.ts +5 -0
- package/dist/{acp-ATIL4JOZ.js → acp-UYC6APR5.js} +30 -6
- package/dist/acp.d.ts +28 -0
- package/dist/agent.d.ts +147 -0
- package/dist/agents.d.ts +4 -0
- package/dist/args-NRTZWWEV.js +13 -0
- package/dist/args.d.ts +53 -0
- package/dist/budget.d.ts +23 -0
- package/dist/checkpoints.d.ts +35 -0
- package/dist/{chunk-6M5X2KZE.js → chunk-266SY3MC.js} +1 -1
- package/dist/{chunk-7F3IWBPQ.js → chunk-3I5BW3ZT.js} +20 -9
- package/dist/{chunk-UZWJQF67.js → chunk-3WHI4LN3.js} +2 -1
- package/dist/{chunk-PUN4HMAG.js → chunk-5YJ3ZQVR.js} +73 -10
- package/dist/{chunk-L7QI6JN2.js → chunk-6M55UPES.js} +7 -7
- package/dist/{chunk-KEMYIGCP.js → chunk-6UO5GX55.js} +26 -12
- package/dist/{chunk-TLPFQIS2.js → chunk-C64KM37J.js} +36 -6
- package/dist/{chunk-JL5ZCSP2.js → chunk-H7XJDKJY.js} +4 -4
- package/dist/{chunk-SPRNLS7K.js → chunk-HQW4BROS.js} +4 -5
- package/dist/{chunk-XBO5GACQ.js → chunk-IYR5PYA3.js} +4 -4
- package/dist/{chunk-45SDLMTV.js → chunk-JXM6REER.js} +1 -1
- package/dist/{chunk-BLOYFIAD.js → chunk-KPTML72Q.js} +1 -1
- package/dist/{chunk-BQ2IGPBR.js → chunk-NQOGVP3I.js} +2 -2
- package/dist/{chunk-QZKLZELZ.js → chunk-OK2USAXX.js} +28 -4
- package/dist/{chunk-TVALKHFB.js → chunk-R72OV7RF.js} +421 -108
- package/dist/{chunk-LACUWBWS.js → chunk-RX4UQX43.js} +25 -10
- package/dist/{chunk-QDQU6VY2.js → chunk-SVD4LHJC.js} +73 -3
- package/dist/{chunk-BNA74HPN.js → chunk-TIMSYZIR.js} +7 -7
- package/dist/{chunk-O7M3TGIW.js → chunk-WFZKJY3R.js} +43 -4
- package/dist/{chunk-M7CALSP3.js → chunk-ZEPTUKNW.js} +120 -32
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +61 -45
- package/dist/clipboardImage.d.ts +17 -0
- package/dist/commands.d.ts +16 -0
- package/dist/{completion-HPPFDAPB.js → completion-D2BSBTQO.js} +1 -1
- package/dist/completion.d.ts +14 -0
- package/dist/{config-COPNMMV6.js → config-ENDRMOTP.js} +3 -3
- package/dist/{config-RZ5JONMX.js → config-LAINORU5.js} +8 -4
- package/dist/config.d.ts +99 -0
- package/dist/context.d.ts +13 -0
- package/dist/contextWindow.d.ts +3 -0
- package/dist/diff.d.ts +7 -0
- package/dist/exit.d.ts +9 -0
- package/dist/extensions.d.ts +34 -0
- package/dist/{ghBotCli-IBCZIWT5.js → ghBotCli-TSLRORJJ.js} +21 -20
- package/dist/ghBotCli.d.ts +9 -0
- package/dist/ghBotDispatch.d.ts +68 -0
- package/dist/ghBotRunner.d.ts +82 -0
- package/dist/ghBotServe.d.ts +21 -0
- package/dist/ghBotWebhook.d.ts +45 -0
- package/dist/hooks.d.ts +27 -0
- package/dist/i18n/id.d.ts +1 -0
- package/dist/i18n.d.ts +16 -0
- package/dist/{integrationCli-P75R2VSC.js → integrationCli-HXCSEWH2.js} +2 -2
- package/dist/integrationCli.d.ts +9 -0
- package/dist/keybinds.d.ts +22 -0
- package/dist/{login-SYIQP46J.js → login-RYESFZG6.js} +4 -4
- package/dist/login.d.ts +19 -0
- package/dist/lsp.d.ts +50 -0
- package/dist/mcp/catalog.d.ts +14 -0
- package/dist/mcp/config.d.ts +36 -0
- package/dist/mcp/manager.d.ts +52 -0
- package/dist/mcp/oauth.d.ts +82 -0
- package/dist/{mcpCli-QUV5SD64.js → mcpCli-SETTCL3K.js} +6 -6
- package/dist/mcpCli.d.ts +3 -0
- package/dist/mcpServer/serve.d.ts +8 -0
- package/dist/mcpServer/server.d.ts +19 -0
- package/dist/models.d.ts +13 -0
- package/dist/{modelsCli-TRRB5LX4.js → modelsCli-OP7CDZC7.js} +6 -6
- package/dist/modelsCli.d.ts +15 -0
- package/dist/npmRunner.d.ts +21 -0
- package/dist/{oauthLogin-6FWQABJ7.js → oauthLogin-XWY7OHCG.js} +1 -1
- package/dist/oauthLogin.d.ts +47 -0
- package/dist/permissions.d.ts +30 -0
- package/dist/pluginSpec.d.ts +25 -0
- package/dist/plugins.d.ts +45 -0
- package/dist/{pluginsCli-GNW4TPVV.js → pluginsCli-ZQNKWZHJ.js} +2 -2
- package/dist/pluginsCli.d.ts +16 -0
- package/dist/{prCli-5IMV3VNQ.js → prCli-6PUWTMTP.js} +20 -4
- package/dist/prCli.d.ts +46 -0
- package/dist/presets.d.ts +8 -0
- package/dist/print.d.ts +16 -0
- package/dist/provider.d.ts +145 -0
- package/dist/providerCli-5OZI2GQQ.js +15 -0
- package/dist/providerCli.d.ts +17 -0
- package/dist/providers.d.ts +6 -0
- package/dist/reasoning.d.ts +12 -0
- package/dist/repomap.d.ts +10 -0
- package/dist/sdk.d.ts +61 -0
- package/dist/sdk.js +829 -191
- package/dist/{serve-JG7RLIUD.js → serve-4J7HA4JO.js} +24 -18
- package/dist/{serve-6QLBJUYW.js → serve-K5AH4MGO.js} +3 -3
- package/dist/serve.d.ts +37 -0
- package/dist/serveHeartbeat.d.ts +14 -0
- package/dist/serveProtocol.d.ts +47 -0
- package/dist/session-YQXCCKXI.js +12 -0
- package/dist/session.d.ts +33 -0
- package/dist/setup-S4AJT6XE.js +24 -0
- package/dist/setup.d.ts +74 -0
- package/dist/slash/copy.d.ts +1 -0
- package/dist/slash/diff.d.ts +1 -0
- package/dist/slash/doctor.d.ts +18 -0
- package/dist/slash/export.d.ts +2 -0
- package/dist/slash/info.d.ts +46 -0
- package/dist/slash/memory.d.ts +10 -0
- package/dist/{statsCli-CF3R7ATL.js → statsCli-XR6BEZQQ.js} +2 -2
- package/dist/statsCli.d.ts +62 -0
- package/dist/textToolCalls.d.ts +6 -0
- package/dist/thinking.d.ts +17 -0
- package/dist/tokenBudget.d.ts +22 -0
- package/dist/tools/bash.d.ts +52 -0
- package/dist/tools/diagnostics.d.ts +5 -0
- package/dist/tools/edit.d.ts +12 -0
- package/dist/tools/fetch.d.ts +41 -0
- package/dist/tools/glob.d.ts +5 -0
- package/dist/tools/globSafety.d.ts +7 -0
- package/dist/tools/grep.d.ts +25 -0
- package/dist/tools/ignore.d.ts +2 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/memory.d.ts +13 -0
- package/dist/tools/paths.d.ts +4 -0
- package/dist/tools/plan.d.ts +10 -0
- package/dist/tools/read.d.ts +16 -0
- package/dist/tools/repomap.d.ts +2 -0
- package/dist/tools/sandbox.d.ts +12 -0
- package/dist/tools/shell.d.ts +18 -0
- package/dist/tools/skill.d.ts +3 -0
- package/dist/tools/task.d.ts +28 -0
- package/dist/tools/todo.d.ts +13 -0
- package/dist/tools/types.d.ts +52 -0
- package/dist/tools/websearch.d.ts +9 -0
- package/dist/tools/write.d.ts +5 -0
- package/dist/{transferCli-TK2GSVRK.js → transferCli-R2KEIWQR.js} +8 -6
- package/dist/transferCli.d.ts +23 -0
- package/dist/ui/App.d.ts +7 -0
- package/dist/ui/DiffView.d.ts +3 -0
- package/dist/ui/EditableText.d.ts +7 -0
- package/dist/ui/LinePrompt.d.ts +6 -0
- package/dist/ui/ListPicker.d.ts +11 -0
- package/dist/ui/Markdown.d.ts +8 -0
- package/dist/ui/McpMenu.d.ts +11 -0
- package/dist/ui/ModelPicker.d.ts +6 -0
- package/dist/ui/PermissionPrompt.d.ts +5 -0
- package/dist/ui/PlanApproval.d.ts +5 -0
- package/dist/ui/PromptInput.d.ts +18 -0
- package/dist/ui/ProviderMenu.d.ts +10 -0
- package/dist/ui/Spinner.d.ts +4 -0
- package/dist/ui/StatusBar.d.ts +9 -0
- package/dist/ui/TodoList.d.ts +4 -0
- package/dist/ui/ToolBlock.d.ts +14 -0
- package/dist/ui/complete.d.ts +6 -0
- package/dist/ui/focusReport.d.ts +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/lineEdit.d.ts +42 -0
- package/dist/ui/replay.d.ts +4 -0
- package/dist/ui/table.d.ts +20 -0
- package/dist/ui/theme.d.ts +5 -0
- package/dist/ui/thinkingMouse.d.ts +33 -0
- package/dist/ui/transcript.d.ts +55 -0
- package/dist/ui/vim.d.ts +26 -0
- package/dist/{ui-B3CJ6TGE.js → ui-36ZEK65V.js} +179 -54
- package/dist/{update-K5IQ2MYR.js → update-FSS7X5WV.js} +2 -2
- package/dist/update.d.ts +30 -0
- package/dist/verify.d.ts +16 -0
- package/dist/version.d.ts +1 -0
- package/dist/webBudget.d.ts +25 -0
- package/dist/worktree.d.ts +12 -0
- package/package.json +26 -13
- package/dist/args-J5ONVGIF.js +0 -13
- package/dist/providerCli-FAOZHOKW.js +0 -15
- package/dist/session-KBAQ4BZ5.js +0 -12
- package/dist/setup-FJAWDWV4.js +0 -24
package/dist/sdk.js
CHANGED
|
@@ -500,7 +500,8 @@ Opsi:
|
|
|
500
500
|
"gh is not available ({error}); install GitHub CLI first": "gh tidak tersedia ({error}); pasang GitHub CLI dulu",
|
|
501
501
|
"gh pr checkout {n} failed (exit {code})": "gh pr checkout {n} gagal (exit {code})",
|
|
502
502
|
"checked out PR #{n}": "checkout PR #{n} selesai",
|
|
503
|
-
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya"
|
|
503
|
+
"--allow-all: the PR task runs every tool without asking": "--allow-all: tugas PR menjalankan semua alat tanpa bertanya",
|
|
504
|
+
"view or change configuration settings": "lihat atau ubah pengaturan konfigurasi"
|
|
504
505
|
};
|
|
505
506
|
|
|
506
507
|
// src/i18n.ts
|
|
@@ -689,8 +690,11 @@ function loadConfig(cwd, env = process.env) {
|
|
|
689
690
|
const project = readJsonConfig(join(cwd, ".bccli", "config.json"));
|
|
690
691
|
const known = { ...DEFAULT_CONFIG.providers, ...global.providers };
|
|
691
692
|
const fromProject = untrustedProviders(project, known);
|
|
693
|
+
const rawFallbacks = project.fallbackModels ?? global.fallbackModels;
|
|
694
|
+
const fallbackModels = Array.isArray(rawFallbacks) ? rawFallbacks.filter((m) => typeof m === "string" && m.includes("/")) : void 0;
|
|
692
695
|
return {
|
|
693
696
|
model: project.model ?? global.model ?? DEFAULT_CONFIG.model,
|
|
697
|
+
...fallbackModels?.length ? { fallbackModels } : {},
|
|
694
698
|
permissionMode: global.permissionMode ?? DEFAULT_CONFIG.permissionMode,
|
|
695
699
|
reasoning: global.reasoning ?? DEFAULT_CONFIG.reasoning,
|
|
696
700
|
thinkingVisible: global.thinkingVisible === true,
|
|
@@ -715,21 +719,46 @@ function loadConfig(cwd, env = process.env) {
|
|
|
715
719
|
allow: [...global.allow ?? []]
|
|
716
720
|
};
|
|
717
721
|
}
|
|
722
|
+
function writeFileAtomic(path, data, mode) {
|
|
723
|
+
const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
724
|
+
try {
|
|
725
|
+
writeFileSync(tmp, data, mode === void 0 ? void 0 : { mode });
|
|
726
|
+
renameSync(tmp, path);
|
|
727
|
+
if (mode !== void 0) chmodSync(path, mode);
|
|
728
|
+
} catch (error) {
|
|
729
|
+
try {
|
|
730
|
+
rmSync(tmp, { force: true });
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function quarantineCorruptFile(path, error) {
|
|
737
|
+
const stamp = Date.now();
|
|
738
|
+
let dest = `${path}.corrupt-${stamp}`;
|
|
739
|
+
for (let n = 1; existsSync(dest); n++) dest = `${path}.corrupt-${stamp}-${n}`;
|
|
740
|
+
try {
|
|
741
|
+
renameSync(path, dest);
|
|
742
|
+
} catch {
|
|
743
|
+
return void 0;
|
|
744
|
+
}
|
|
745
|
+
console.error(`${path} is not valid JSON (${error.message}); moved it to ${dest} instead of overwriting it.`);
|
|
746
|
+
return dest;
|
|
747
|
+
}
|
|
718
748
|
function readCredentials(env = process.env) {
|
|
719
749
|
const path = join(bccliHome(env), "credentials");
|
|
720
750
|
if (!existsSync(path)) return {};
|
|
721
751
|
try {
|
|
722
752
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
723
|
-
} catch {
|
|
753
|
+
} catch (error) {
|
|
754
|
+
quarantineCorruptFile(path, error);
|
|
724
755
|
return {};
|
|
725
756
|
}
|
|
726
757
|
}
|
|
727
758
|
function saveCredential(providerId, key, env = process.env) {
|
|
728
759
|
const home = bccliHome(env);
|
|
729
760
|
mkdirSync(home, { recursive: true });
|
|
730
|
-
|
|
731
|
-
writeFileSync(path, JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), { mode: 384 });
|
|
732
|
-
chmodSync(path, 384);
|
|
761
|
+
writeFileAtomic(join(home, "credentials"), JSON.stringify({ ...readCredentials(env), [providerId]: key }, null, 2), 384);
|
|
733
762
|
}
|
|
734
763
|
function resolveModel(config, modelRef, env = process.env) {
|
|
735
764
|
const slash = modelRef.indexOf("/");
|
|
@@ -749,7 +778,7 @@ function resolveModel(config, modelRef, env = process.env) {
|
|
|
749
778
|
}
|
|
750
779
|
|
|
751
780
|
// src/permissions.ts
|
|
752
|
-
import { isAbsolute, relative, resolve
|
|
781
|
+
import { isAbsolute, relative, resolve } from "path";
|
|
753
782
|
var MODE_ORDER = ["default", "acceptEdits", "plan", "allowAll"];
|
|
754
783
|
var ALWAYS_ASK_COMMANDS = /* @__PURE__ */ new Set(["sudo", "sh", "bash", "zsh", "su"]);
|
|
755
784
|
function isAlwaysAsk(segment) {
|
|
@@ -765,14 +794,51 @@ function isAlwaysAsk(segment) {
|
|
|
765
794
|
}
|
|
766
795
|
return false;
|
|
767
796
|
}
|
|
797
|
+
var RUNNER_COMMANDS = /* @__PURE__ */ new Set([
|
|
798
|
+
"npm",
|
|
799
|
+
"npx",
|
|
800
|
+
"pnpm",
|
|
801
|
+
"pnpx",
|
|
802
|
+
"yarn",
|
|
803
|
+
"bun",
|
|
804
|
+
"bunx",
|
|
805
|
+
"deno",
|
|
806
|
+
"node",
|
|
807
|
+
"tsx",
|
|
808
|
+
"ts-node",
|
|
809
|
+
"python",
|
|
810
|
+
"python3",
|
|
811
|
+
"py",
|
|
812
|
+
"uv",
|
|
813
|
+
"uvx",
|
|
814
|
+
"pip",
|
|
815
|
+
"pip3",
|
|
816
|
+
"pipx",
|
|
817
|
+
"pytest",
|
|
818
|
+
"make",
|
|
819
|
+
"cargo",
|
|
820
|
+
"go",
|
|
821
|
+
"dotnet",
|
|
822
|
+
"java",
|
|
823
|
+
"ruby",
|
|
824
|
+
"bundle",
|
|
825
|
+
"rake",
|
|
826
|
+
"gradle",
|
|
827
|
+
"mvn",
|
|
828
|
+
"php",
|
|
829
|
+
"composer"
|
|
830
|
+
]);
|
|
768
831
|
function commandKey(segment) {
|
|
769
832
|
const words2 = segment.trim().split(/\s+/);
|
|
833
|
+
if (RUNNER_COMMANDS.has(words2[0] ?? "") && words2[2] && !words2[2].startsWith("-")) return words2.slice(0, 3).join(" ");
|
|
770
834
|
return words2[1] ? `${words2[0]} ${words2[1]}` : words2[0];
|
|
771
835
|
}
|
|
772
836
|
function requiresSensitiveToolApproval(req) {
|
|
773
837
|
const target = req.target.trim();
|
|
774
838
|
if (req.kind === "read" || req.kind === "edit") {
|
|
775
|
-
return /(?:^|[/\\])(?:\.env(
|
|
839
|
+
return /(?:^|[\s/\\])(?:\.env(?:\.[^\s/\\]+)?|\.ssh|\.aws|\.config[/\\]gcloud|\.npmrc|\.netrc|\.git-credentials|(?:secrets?|credentials?)(?:\.[a-z0-9_-]+)?)(?:[\s/\\]|$)/i.test(
|
|
840
|
+
target
|
|
841
|
+
) || /(?:id_rsa|id_ed25519|\.pem|\.p12|\.key)(?:\s|$)/i.test(target);
|
|
776
842
|
}
|
|
777
843
|
if (req.kind === "bash") {
|
|
778
844
|
return /\b(?:rm\s+-[a-z]*r[a-z]*f[a-z]*\s|rm\s+-[a-z]*f[a-z]*r[a-z]*\s|mkfs(?:\.|\s)|wipefs\b|shred\s|git\s+push\s+(?:--force|-f)\b|docker\s+(?:system\s+prune|volume\s+rm)|kubectl\s+delete\b|drop\s+(?:database|table)|npm\s+publish\b)/i.test(target) || /\b(?:deploy-service\.sh|aws\s+secretsmanager|cat\s+[^;&|]*\.env\b)/i.test(target);
|
|
@@ -797,7 +863,8 @@ var Permissions = class {
|
|
|
797
863
|
editInProject(target) {
|
|
798
864
|
const rel = relative(this.cwd, resolve(this.cwd, target));
|
|
799
865
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
800
|
-
|
|
866
|
+
const parts = rel.split(/[/\\]/).map((p) => p.toLowerCase());
|
|
867
|
+
return !parts.includes(".git");
|
|
801
868
|
}
|
|
802
869
|
/** Rules that "[a] allow for this session" would add; undefined when it must always be asked. */
|
|
803
870
|
rulesFor(req) {
|
|
@@ -815,7 +882,7 @@ var Permissions = class {
|
|
|
815
882
|
if (/`|\$\(|[<>]|^\s*[({]/.test(req.target)) return void 0;
|
|
816
883
|
const segments = req.target.split(/&&|\|\||[;&|\n]/).map((s) => s.trim()).filter(Boolean);
|
|
817
884
|
if (!segments.length) return void 0;
|
|
818
|
-
if (segments.some(isAlwaysAsk)) return void 0;
|
|
885
|
+
if (segments.some((s) => /^[({]/.test(s) || isAlwaysAsk(s))) return void 0;
|
|
819
886
|
return [...new Set(segments.map((s) => `bash(${commandKey(s)})`))];
|
|
820
887
|
}
|
|
821
888
|
if (req.kind === "mcp") return [`mcp(${req.tool})`];
|
|
@@ -893,9 +960,12 @@ var LOOPBACK_BIND = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
|
|
|
893
960
|
|
|
894
961
|
// src/args.ts
|
|
895
962
|
function parseCliArgs(argv) {
|
|
896
|
-
const expanded = argv.map(
|
|
897
|
-
|
|
898
|
-
|
|
963
|
+
const expanded = argv.map((a, i) => {
|
|
964
|
+
const nextIsFlagOrEnd = i + 1 >= argv.length || argv[i + 1].startsWith("-");
|
|
965
|
+
if (a === "--models" && nextIsFlagOrEnd) return "--models=all";
|
|
966
|
+
if (a === "--project" && nextIsFlagOrEnd) return "--project=";
|
|
967
|
+
return a;
|
|
968
|
+
});
|
|
899
969
|
const { values, positionals } = parseArgs({
|
|
900
970
|
args: expanded,
|
|
901
971
|
allowPositionals: true,
|
|
@@ -927,7 +997,8 @@ function parseCliArgs(argv) {
|
|
|
927
997
|
tools: { type: "string" },
|
|
928
998
|
models: { type: "string" },
|
|
929
999
|
project: { type: "string" },
|
|
930
|
-
repo: { type: "string" }
|
|
1000
|
+
repo: { type: "string" },
|
|
1001
|
+
"fallback-model": { type: "string" }
|
|
931
1002
|
}
|
|
932
1003
|
});
|
|
933
1004
|
const reasoning = values.reasoning;
|
|
@@ -1001,7 +1072,8 @@ function parseCliArgs(argv) {
|
|
|
1001
1072
|
tools,
|
|
1002
1073
|
models,
|
|
1003
1074
|
project: values.project,
|
|
1004
|
-
repo: values.repo
|
|
1075
|
+
repo: values.repo,
|
|
1076
|
+
fallbackModel: values["fallback-model"]
|
|
1005
1077
|
};
|
|
1006
1078
|
}
|
|
1007
1079
|
|
|
@@ -1093,14 +1165,14 @@ function detectRepetition(text) {
|
|
|
1093
1165
|
return -1;
|
|
1094
1166
|
}
|
|
1095
1167
|
function sleep(ms, signal) {
|
|
1096
|
-
return new Promise((
|
|
1168
|
+
return new Promise((resolve5, reject) => {
|
|
1097
1169
|
const onAbort = () => {
|
|
1098
1170
|
clearTimeout(timer);
|
|
1099
1171
|
reject(signal?.reason);
|
|
1100
1172
|
};
|
|
1101
1173
|
const timer = setTimeout(() => {
|
|
1102
1174
|
signal?.removeEventListener("abort", onAbort);
|
|
1103
|
-
|
|
1175
|
+
resolve5();
|
|
1104
1176
|
}, ms);
|
|
1105
1177
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1106
1178
|
});
|
|
@@ -1170,7 +1242,7 @@ async function readStream(res, onText, onThinking) {
|
|
|
1170
1242
|
}
|
|
1171
1243
|
current2 = target;
|
|
1172
1244
|
if (tc.id) target.id = tc.id;
|
|
1173
|
-
if (tc.function?.name &&
|
|
1245
|
+
if (tc.function?.name && target.name !== tc.function.name) target.name += tc.function.name;
|
|
1174
1246
|
if (tc.function?.arguments) target.arguments += tc.function.arguments;
|
|
1175
1247
|
}
|
|
1176
1248
|
};
|
|
@@ -1377,7 +1449,16 @@ function responsesFinishReason(items, toolCallCount, incompleteReason) {
|
|
|
1377
1449
|
function hasPendingProgrammaticReplay(messages) {
|
|
1378
1450
|
let pending = false;
|
|
1379
1451
|
for (const message of messages) {
|
|
1452
|
+
if (message.role === "user" && typeof message.content === "string") {
|
|
1453
|
+
pending = false;
|
|
1454
|
+
continue;
|
|
1455
|
+
}
|
|
1456
|
+
if (message.role === "tool" && message.content === "Cancelled by the user.") {
|
|
1457
|
+
pending = false;
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1380
1460
|
if (message.role !== "assistant") continue;
|
|
1461
|
+
if (message.content) pending = false;
|
|
1381
1462
|
for (const item of message.responses_output_items ?? []) {
|
|
1382
1463
|
if (item.type === "program") pending = true;
|
|
1383
1464
|
else if (item.type === "message" && pending) pending = false;
|
|
@@ -1451,7 +1532,7 @@ async function readResponsesStream(res, onText, onThinking) {
|
|
|
1451
1532
|
calls.set(index, {
|
|
1452
1533
|
id: String(event.item.call_id || current2?.id || event.item.id || `call_${index}`),
|
|
1453
1534
|
name: String(event.item.name || current2?.name || ""),
|
|
1454
|
-
arguments: String(event.item.arguments
|
|
1535
|
+
arguments: String(event.item.arguments || current2?.arguments || ""),
|
|
1455
1536
|
...programCaller(event.item) ?? current2?.caller ? { caller: programCaller(event.item) ?? current2?.caller } : {}
|
|
1456
1537
|
});
|
|
1457
1538
|
}
|
|
@@ -1687,7 +1768,7 @@ async function readResponsesWebSocketTurn(events, ws, onText, onThinking, signal
|
|
|
1687
1768
|
calls.set(index, {
|
|
1688
1769
|
id: String(event.item.call_id || current2?.id || event.item.id || `call_${index}`),
|
|
1689
1770
|
name: String(event.item.name || current2?.name || ""),
|
|
1690
|
-
arguments: String(event.item.arguments
|
|
1771
|
+
arguments: String(event.item.arguments || current2?.arguments || ""),
|
|
1691
1772
|
...programCaller(event.item) ?? current2?.caller ? { caller: programCaller(event.item) ?? current2?.caller } : {}
|
|
1692
1773
|
});
|
|
1693
1774
|
}
|
|
@@ -1748,7 +1829,7 @@ function createProvider(options) {
|
|
|
1748
1829
|
const isBotConnectorCloud = /^https:\/\/api\.botconnector\.id\/v1\/?$/.test(baseURL) && apiKey?.startsWith("bc_live_");
|
|
1749
1830
|
if (isBotConnectorCloud) {
|
|
1750
1831
|
headers["x-botconnector-client"] = "bccli";
|
|
1751
|
-
headers["x-botconnector-client-version"] = "0.4.
|
|
1832
|
+
headers["x-botconnector-client-version"] = "0.4.2";
|
|
1752
1833
|
}
|
|
1753
1834
|
const useNativeResponses = isBotConnectorCloud && providerId === "bc-cloud" && model === "gpt-6-luna";
|
|
1754
1835
|
const useResponsesWebSocket = useNativeResponses && Boolean(apiKey) && (fetchOverride === void 0 || responsesWebSocketFactory !== void 0);
|
|
@@ -1779,7 +1860,7 @@ function createProvider(options) {
|
|
|
1779
1860
|
baseURL: baseURL.replace(/\/+$/, ""),
|
|
1780
1861
|
defaultHeaders: {
|
|
1781
1862
|
"x-botconnector-client": "bccli",
|
|
1782
|
-
"x-botconnector-client-version": "0.4.
|
|
1863
|
+
"x-botconnector-client-version": "0.4.2"
|
|
1783
1864
|
}
|
|
1784
1865
|
});
|
|
1785
1866
|
lunaWs = responsesWebSocketFactory ? responsesWebSocketFactory(client) : new ResponsesWS(client, { reconnect: null });
|
|
@@ -1842,8 +1923,13 @@ function createProvider(options) {
|
|
|
1842
1923
|
} catch (error) {
|
|
1843
1924
|
const err = error instanceof ProviderError ? error : new ProviderError(error.message);
|
|
1844
1925
|
const lostPrevious = err.code === "previous_response_not_found";
|
|
1926
|
+
const handshakeStatus = /unexpected server response:\s*(\d{3})\b/i.exec(err.message);
|
|
1927
|
+
const handshakeRejected = Boolean(handshakeStatus && Number(handshakeStatus[1]) >= 400);
|
|
1845
1928
|
const connectionLost = /websocket|connection closed|socket|network/i.test(err.message) || err.code === "websocket_connection_limit_reached";
|
|
1846
1929
|
resetLunaWebSocket();
|
|
1930
|
+
if (!emittedText && !signal?.aborted && handshakeRejected) {
|
|
1931
|
+
throw new ProviderError(err.message, Number(handshakeStatus?.[1]), "bc_ws_http_fallback");
|
|
1932
|
+
}
|
|
1847
1933
|
if (attempt === 0 && !emittedText && !signal?.aborted && (plan.incremental || lostPrevious || connectionLost)) {
|
|
1848
1934
|
continue;
|
|
1849
1935
|
}
|
|
@@ -1857,8 +1943,13 @@ function createProvider(options) {
|
|
|
1857
1943
|
}
|
|
1858
1944
|
async function post(path, body, signal) {
|
|
1859
1945
|
let lastError;
|
|
1946
|
+
let retryAfterWaitMs;
|
|
1860
1947
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
1861
|
-
if (attempt > 0)
|
|
1948
|
+
if (attempt > 0) {
|
|
1949
|
+
const delay = retryAfterWaitMs ?? retryDelayMs * 2 ** (attempt - 1);
|
|
1950
|
+
retryAfterWaitMs = void 0;
|
|
1951
|
+
await sleep(delay, signal);
|
|
1952
|
+
}
|
|
1862
1953
|
let res;
|
|
1863
1954
|
try {
|
|
1864
1955
|
res = await doFetch(`${baseURL}${path}`, { method: "POST", headers, body: JSON.stringify(body), signal });
|
|
@@ -1868,9 +1959,15 @@ function createProvider(options) {
|
|
|
1868
1959
|
continue;
|
|
1869
1960
|
}
|
|
1870
1961
|
if (res.ok) return res;
|
|
1962
|
+
const retryHeader = res.headers.get("retry-after");
|
|
1963
|
+
if (retryHeader) {
|
|
1964
|
+
const secs = Number(retryHeader);
|
|
1965
|
+
if (Number.isFinite(secs) && secs > 0 && secs <= 30) retryAfterWaitMs = Math.ceil(secs * 1e3);
|
|
1966
|
+
}
|
|
1871
1967
|
const text = await res.text().catch(() => "");
|
|
1872
1968
|
lastError = new ProviderError(t("{status} from {url}: {message}", { status: res.status, url: baseURL, message: errorMessage(text) }), res.status);
|
|
1873
|
-
|
|
1969
|
+
const deterministicQuota429 = res.status === 429 && /"code"\s*:\s*"(?:luna_monthly_quota_exhausted|plan_allowance_exhausted)"/i.test(text);
|
|
1970
|
+
if (!retryable(res.status) || deterministicQuota429) throw lastError;
|
|
1874
1971
|
}
|
|
1875
1972
|
throw lastError ?? new ProviderError(t("Request failed"));
|
|
1876
1973
|
}
|
|
@@ -1929,6 +2026,43 @@ function createProvider(options) {
|
|
|
1929
2026
|
}
|
|
1930
2027
|
};
|
|
1931
2028
|
}
|
|
2029
|
+
function withFallbackProvider(primary, fallbacks) {
|
|
2030
|
+
return {
|
|
2031
|
+
async chat(req) {
|
|
2032
|
+
let emitted = false;
|
|
2033
|
+
const wrappedReq = {
|
|
2034
|
+
...req,
|
|
2035
|
+
onText: req.onText ? (delta) => {
|
|
2036
|
+
emitted = true;
|
|
2037
|
+
req.onText?.(delta);
|
|
2038
|
+
} : () => {
|
|
2039
|
+
emitted = true;
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
try {
|
|
2043
|
+
return await primary.chat(wrappedReq);
|
|
2044
|
+
} catch (primaryError) {
|
|
2045
|
+
if (emitted || req.signal?.aborted) throw primaryError;
|
|
2046
|
+
const status = primaryError instanceof ProviderError ? primaryError.status : void 0;
|
|
2047
|
+
const canFallback = status === void 0 || status === 429 || status === 408 || status >= 500;
|
|
2048
|
+
if (!canFallback) throw primaryError;
|
|
2049
|
+
const chain = fallbacks();
|
|
2050
|
+
if (!chain.length) throw primaryError;
|
|
2051
|
+
let lastError = primaryError;
|
|
2052
|
+
for (const fb of chain) {
|
|
2053
|
+
if (req.signal?.aborted) throw lastError;
|
|
2054
|
+
try {
|
|
2055
|
+
return await fb.chat(req);
|
|
2056
|
+
} catch (fbError) {
|
|
2057
|
+
lastError = fbError;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
throw lastError;
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
listModels: () => primary.listModels()
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
1932
2066
|
|
|
1933
2067
|
// src/textToolCalls.ts
|
|
1934
2068
|
var PAIR = /(?:<arg_key>\s*|`)?([A-Za-z_]\w*)(?:\s*<\/arg_key>)?`?\s*(?:=\s*`|<arg_value>)([\s\S]*?)<\/arg_value>/g;
|
|
@@ -2048,7 +2182,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2048
2182
|
...payload.input !== void 0 ? { BCCLI_HOOK_INPUT: JSON.stringify(payload.input) } : {},
|
|
2049
2183
|
...payload.output !== void 0 ? { BCCLI_HOOK_OUTPUT: payload.output } : {}
|
|
2050
2184
|
};
|
|
2051
|
-
return new Promise((
|
|
2185
|
+
return new Promise((resolve5) => {
|
|
2052
2186
|
let stdout = "";
|
|
2053
2187
|
let stderr = "";
|
|
2054
2188
|
let timedOut = false;
|
|
@@ -2057,7 +2191,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2057
2191
|
if (settled) return;
|
|
2058
2192
|
settled = true;
|
|
2059
2193
|
clearTimeout(timer);
|
|
2060
|
-
|
|
2194
|
+
resolve5(result);
|
|
2061
2195
|
};
|
|
2062
2196
|
let child;
|
|
2063
2197
|
const shell = defaultShell();
|
|
@@ -2073,7 +2207,7 @@ function runOne(command, event, payload, opts) {
|
|
|
2073
2207
|
child = spawn(command, { shell: true, env, cwd: opts.cwd ?? process.cwd(), stdio: ["ignore", "pipe", "pipe"] });
|
|
2074
2208
|
}
|
|
2075
2209
|
} catch (error) {
|
|
2076
|
-
|
|
2210
|
+
resolve5({ failure: error.message });
|
|
2077
2211
|
return;
|
|
2078
2212
|
}
|
|
2079
2213
|
const timer = setTimeout(() => {
|
|
@@ -2128,7 +2262,9 @@ import { z as z8 } from "zod";
|
|
|
2128
2262
|
|
|
2129
2263
|
// src/tools/bash.ts
|
|
2130
2264
|
import { spawn as spawn2 } from "child_process";
|
|
2131
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2265
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2266
|
+
import { tmpdir } from "os";
|
|
2267
|
+
import { join as join2 } from "path";
|
|
2132
2268
|
import { z } from "zod";
|
|
2133
2269
|
|
|
2134
2270
|
// src/tools/sandbox.ts
|
|
@@ -2317,7 +2453,7 @@ function offlineEnv(env = process.env) {
|
|
|
2317
2453
|
};
|
|
2318
2454
|
}
|
|
2319
2455
|
function runCommand(command, opts) {
|
|
2320
|
-
return new Promise((
|
|
2456
|
+
return new Promise((resolve5) => {
|
|
2321
2457
|
const child = spawnChild(command, opts.cwd, opts.env, opts.shell ?? defaultShell());
|
|
2322
2458
|
if (!exitHookInstalled) {
|
|
2323
2459
|
process.on("exit", killAllCommands);
|
|
@@ -2330,7 +2466,14 @@ function runCommand(command, opts) {
|
|
|
2330
2466
|
let dropped = 0;
|
|
2331
2467
|
let timedOut = false;
|
|
2332
2468
|
let aborted = false;
|
|
2469
|
+
const fullChunks = [];
|
|
2470
|
+
let fullBytes = 0;
|
|
2471
|
+
const MAX_SPILL_BYTES = 10 * 1024 * 1024;
|
|
2333
2472
|
const onData = (data) => {
|
|
2473
|
+
if (fullBytes < MAX_SPILL_BYTES) {
|
|
2474
|
+
fullChunks.push(data);
|
|
2475
|
+
fullBytes += data.length;
|
|
2476
|
+
}
|
|
2334
2477
|
let s = data;
|
|
2335
2478
|
if (head.length < HALF) {
|
|
2336
2479
|
const take = s.slice(0, HALF - head.length);
|
|
@@ -2369,10 +2512,19 @@ function runCommand(command, opts) {
|
|
|
2369
2512
|
active.delete(killAsAborted);
|
|
2370
2513
|
clearTimeout(timer);
|
|
2371
2514
|
opts.signal.removeEventListener("abort", onAbort);
|
|
2515
|
+
let spillNote = "";
|
|
2516
|
+
if (dropped && fullChunks.length) {
|
|
2517
|
+
try {
|
|
2518
|
+
const spillPath = join2(tmpdir(), `bccli-bash-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.log`);
|
|
2519
|
+
writeFileSync2(spillPath, fullChunks.join(""), "utf8");
|
|
2520
|
+
spillNote = `; full output saved to ${spillPath}`;
|
|
2521
|
+
} catch {
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2372
2524
|
const output = dropped ? `${head}
|
|
2373
|
-
\u2026 [${dropped} characters truncated] \u2026
|
|
2525
|
+
\u2026 [${dropped} characters truncated${spillNote}] \u2026
|
|
2374
2526
|
${tail}` : head + tail;
|
|
2375
|
-
|
|
2527
|
+
resolve5({ output: output + extra, exitCode, timedOut, aborted });
|
|
2376
2528
|
};
|
|
2377
2529
|
child.on("error", (error) => finish(null, error.message));
|
|
2378
2530
|
child.on("close", (code) => finish(code));
|
|
@@ -2494,46 +2646,139 @@ function formatDiff(lines) {
|
|
|
2494
2646
|
}
|
|
2495
2647
|
|
|
2496
2648
|
// src/tools/paths.ts
|
|
2649
|
+
import { statSync } from "fs";
|
|
2497
2650
|
import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
|
|
2651
|
+
var readMtimes = /* @__PURE__ */ new WeakMap();
|
|
2498
2652
|
function resolvePath(cwd, path) {
|
|
2499
2653
|
return isAbsolute2(path) ? path : resolve2(cwd, path);
|
|
2500
2654
|
}
|
|
2501
2655
|
function errorCode(error) {
|
|
2502
2656
|
return error.code ?? error.message;
|
|
2503
2657
|
}
|
|
2658
|
+
function recordFileMtime(readFiles, abs) {
|
|
2659
|
+
readFiles.add(abs);
|
|
2660
|
+
try {
|
|
2661
|
+
let map = readMtimes.get(readFiles);
|
|
2662
|
+
if (!map) {
|
|
2663
|
+
map = /* @__PURE__ */ new Map();
|
|
2664
|
+
readMtimes.set(readFiles, map);
|
|
2665
|
+
}
|
|
2666
|
+
map.set(abs, statSync(abs).mtimeMs);
|
|
2667
|
+
} catch {
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function isFileModifiedSinceRead(readFiles, abs) {
|
|
2671
|
+
const recorded = readMtimes.get(readFiles)?.get(abs);
|
|
2672
|
+
if (recorded === void 0) return false;
|
|
2673
|
+
try {
|
|
2674
|
+
return statSync(abs).mtimeMs > recorded + 50;
|
|
2675
|
+
} catch {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2504
2679
|
|
|
2505
2680
|
// src/tools/edit.ts
|
|
2506
|
-
var
|
|
2507
|
-
path: z2.string().describe("File path, relative to the project or absolute"),
|
|
2681
|
+
var editEntrySchema = z2.object({
|
|
2508
2682
|
old_string: z2.string().describe("Exact text to replace; must be unique unless replace_all"),
|
|
2509
2683
|
new_string: z2.string().describe("Replacement text"),
|
|
2510
2684
|
replace_all: z2.boolean().optional().describe("Replace every occurrence")
|
|
2511
2685
|
});
|
|
2686
|
+
var schema = z2.object({
|
|
2687
|
+
path: z2.string().describe("File path, relative to the project or absolute"),
|
|
2688
|
+
old_string: z2.string().optional().describe("Exact text to replace; must be unique unless replace_all"),
|
|
2689
|
+
new_string: z2.string().optional().describe("Replacement text"),
|
|
2690
|
+
replace_all: z2.boolean().optional().describe("Replace every occurrence"),
|
|
2691
|
+
edits: z2.array(editEntrySchema).optional().describe("Optional list of sequential edits to apply atomically to the same file")
|
|
2692
|
+
});
|
|
2512
2693
|
function countOccurrences(haystack, needle) {
|
|
2513
2694
|
let count = 0;
|
|
2514
2695
|
for (let i = haystack.indexOf(needle); i !== -1; i = haystack.indexOf(needle, i + needle.length)) count++;
|
|
2515
2696
|
return count;
|
|
2516
2697
|
}
|
|
2517
|
-
function
|
|
2698
|
+
function normalizeTypographic(s) {
|
|
2699
|
+
return s.replace(/[\u2018\u2019\u201A\u201B]/g, "'").replace(/[\u201C\u201D\u201E\u201F]/g, '"').replace(/[\u2013\u2014]/g, "-").replace(/\u00A0/g, " ");
|
|
2700
|
+
}
|
|
2701
|
+
function stripReadLinePrefixes(s) {
|
|
2702
|
+
const lines = s.split(/\r?\n/);
|
|
2703
|
+
if (lines.every((l) => !l.trim() || /^\s*\d+\t/.test(l))) {
|
|
2704
|
+
return lines.map((l) => l.replace(/^\s*\d+\t/, "")).join("\n");
|
|
2705
|
+
}
|
|
2706
|
+
return s;
|
|
2707
|
+
}
|
|
2708
|
+
function matchByNormalizedLines(content, oldText, newText, replaceAll) {
|
|
2709
|
+
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2710
|
+
const fileLines = content.split(/\r?\n/);
|
|
2711
|
+
const oldLines = stripReadLinePrefixes(oldText).split(/\r?\n/);
|
|
2712
|
+
if (!oldLines.length) return { count: 0 };
|
|
2713
|
+
const normLine = (l) => normalizeTypographic(l).trimEnd();
|
|
2714
|
+
const normOld = oldLines.map(normLine);
|
|
2715
|
+
const matches = [];
|
|
2716
|
+
for (let i = 0; i + oldLines.length <= fileLines.length; i++) {
|
|
2717
|
+
let ok = true;
|
|
2718
|
+
for (let j = 0; j < oldLines.length; j++) {
|
|
2719
|
+
if (normLine(fileLines[i + j]) !== normOld[j]) {
|
|
2720
|
+
ok = false;
|
|
2721
|
+
break;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
if (ok) {
|
|
2725
|
+
matches.push(i);
|
|
2726
|
+
if (replaceAll) i += oldLines.length - 1;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
if (matches.length === 0) return { count: 0 };
|
|
2730
|
+
if (matches.length > 1 && !replaceAll) return { count: matches.length };
|
|
2731
|
+
const replacementLines = stripReadLinePrefixes(newText).split(/\r?\n/);
|
|
2732
|
+
const outLines = [...fileLines];
|
|
2733
|
+
for (let m = matches.length - 1; m >= 0; m--) {
|
|
2734
|
+
outLines.splice(matches[m], oldLines.length, ...replacementLines);
|
|
2735
|
+
}
|
|
2736
|
+
return { count: matches.length, result: outLines.join(eol) };
|
|
2737
|
+
}
|
|
2738
|
+
function applySingleEdit(content, path, input) {
|
|
2518
2739
|
if (input.old_string === input.new_string) {
|
|
2519
2740
|
return { output: "old_string and new_string are identical; nothing to change.", isError: true };
|
|
2520
2741
|
}
|
|
2521
2742
|
const eol = content.includes("\r\n") ? "\r\n" : "\n";
|
|
2522
2743
|
const toEol = (s) => s.replace(/\r?\n/g, eol);
|
|
2744
|
+
const strippedOld = stripReadLinePrefixes(input.old_string);
|
|
2745
|
+
const strippedNew = stripReadLinePrefixes(input.new_string);
|
|
2523
2746
|
const attempts = [[input.old_string, input.new_string]];
|
|
2524
2747
|
if (toEol(input.old_string) !== input.old_string) attempts.push([toEol(input.old_string), toEol(input.new_string)]);
|
|
2748
|
+
if (strippedOld !== input.old_string) {
|
|
2749
|
+
attempts.push([strippedOld, strippedNew], [toEol(strippedOld), toEol(strippedNew)]);
|
|
2750
|
+
}
|
|
2525
2751
|
for (const [oldText, newText] of attempts) {
|
|
2526
2752
|
const count = oldText ? countOccurrences(content, oldText) : 0;
|
|
2527
2753
|
if (count === 0) continue;
|
|
2528
2754
|
if (count > 1 && !input.replace_all) {
|
|
2529
2755
|
return {
|
|
2530
|
-
output: `old_string appears ${count} times in ${
|
|
2756
|
+
output: `old_string appears ${count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
2531
2757
|
isError: true
|
|
2532
2758
|
};
|
|
2533
2759
|
}
|
|
2534
2760
|
return input.replace_all ? content.split(oldText).join(newText) : content.replace(oldText, () => newText);
|
|
2535
2761
|
}
|
|
2536
|
-
|
|
2762
|
+
const fuzzy = matchByNormalizedLines(content, input.old_string, input.new_string, input.replace_all);
|
|
2763
|
+
if (fuzzy.count > 1 && !input.replace_all) {
|
|
2764
|
+
return {
|
|
2765
|
+
output: `old_string appears ${fuzzy.count} times in ${path}. Add more context to make it unique, or use replace_all.`,
|
|
2766
|
+
isError: true
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
if (fuzzy.count > 0 && fuzzy.result !== void 0) return fuzzy.result;
|
|
2770
|
+
return { output: `old_string not found in ${path}. Re-read the file.`, isError: true };
|
|
2771
|
+
}
|
|
2772
|
+
function applyEdit(content, input) {
|
|
2773
|
+
const list2 = input.edits?.length ? input.edits : input.old_string !== void 0 && input.new_string !== void 0 ? [{ old_string: input.old_string, new_string: input.new_string, replace_all: input.replace_all }] : [];
|
|
2774
|
+
if (!list2.length) return { output: "Provide old_string and new_string (or edits array).", isError: true };
|
|
2775
|
+
let current2 = content;
|
|
2776
|
+
for (const item of list2) {
|
|
2777
|
+
const next = applySingleEdit(current2, input.path, item);
|
|
2778
|
+
if (typeof next !== "string") return next;
|
|
2779
|
+
current2 = next;
|
|
2780
|
+
}
|
|
2781
|
+
return current2;
|
|
2537
2782
|
}
|
|
2538
2783
|
var editTool = defineTool({
|
|
2539
2784
|
name: "edit",
|
|
@@ -2544,6 +2789,9 @@ var editTool = defineTool({
|
|
|
2544
2789
|
async validate(input, ctx) {
|
|
2545
2790
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
2546
2791
|
if (!ctx.readFiles.has(abs)) return `Read ${input.path} with read before editing it.`;
|
|
2792
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
2793
|
+
return `${input.path} has been modified since it was last read. Read it again before editing.`;
|
|
2794
|
+
}
|
|
2547
2795
|
let content;
|
|
2548
2796
|
try {
|
|
2549
2797
|
content = await readFile(abs, "utf8");
|
|
@@ -2564,6 +2812,9 @@ var editTool = defineTool({
|
|
|
2564
2812
|
if (!ctx.readFiles.has(abs)) {
|
|
2565
2813
|
return { output: `Read ${input.path} with read before editing it.`, isError: true };
|
|
2566
2814
|
}
|
|
2815
|
+
if (isFileModifiedSinceRead(ctx.readFiles, abs)) {
|
|
2816
|
+
return { output: `${input.path} has been modified since it was last read. Read it again before editing.`, isError: true };
|
|
2817
|
+
}
|
|
2567
2818
|
let content;
|
|
2568
2819
|
try {
|
|
2569
2820
|
content = await readFile(abs, "utf8");
|
|
@@ -2574,6 +2825,7 @@ var editTool = defineTool({
|
|
|
2574
2825
|
if (typeof next !== "string") return next;
|
|
2575
2826
|
await ctx.checkpoint?.(abs);
|
|
2576
2827
|
await writeFile(abs, next);
|
|
2828
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
2577
2829
|
return { output: `Edited ${input.path}.`, display: formatDiff(diffLines(content, next)) };
|
|
2578
2830
|
}
|
|
2579
2831
|
});
|
|
@@ -2845,20 +3097,22 @@ async function loadPage(inputUrl, signal, pinned, fetchPage) {
|
|
|
2845
3097
|
var fetchTool = createFetchTool();
|
|
2846
3098
|
|
|
2847
3099
|
// src/tools/glob.ts
|
|
3100
|
+
import { statSync as statSync2 } from "fs";
|
|
2848
3101
|
import { glob as nativeGlob } from "fs/promises";
|
|
3102
|
+
import { join as join4 } from "path";
|
|
2849
3103
|
import { z as z4 } from "zod";
|
|
2850
3104
|
|
|
2851
3105
|
// src/abort.ts
|
|
2852
3106
|
var ABORTED = /* @__PURE__ */ Symbol("aborted");
|
|
2853
3107
|
function raceAbort(promise, signal) {
|
|
2854
3108
|
if (signal.aborted) return Promise.resolve(ABORTED);
|
|
2855
|
-
return new Promise((
|
|
2856
|
-
const onAbort = () =>
|
|
3109
|
+
return new Promise((resolve5, reject) => {
|
|
3110
|
+
const onAbort = () => resolve5(ABORTED);
|
|
2857
3111
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2858
3112
|
promise.then(
|
|
2859
3113
|
(value) => {
|
|
2860
3114
|
signal.removeEventListener("abort", onAbort);
|
|
2861
|
-
|
|
3115
|
+
resolve5(value);
|
|
2862
3116
|
},
|
|
2863
3117
|
(error) => {
|
|
2864
3118
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -2884,22 +3138,38 @@ async function collectUntilAbort(iterable, signal, onItem) {
|
|
|
2884
3138
|
|
|
2885
3139
|
// src/tools/ignore.ts
|
|
2886
3140
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2887
|
-
import { join as
|
|
2888
|
-
function ignorePatterns(root) {
|
|
3141
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
3142
|
+
function ignorePatterns(root, projectRoot) {
|
|
2889
3143
|
const patterns = ["**/node_modules/**", "**/.git/**"];
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
3144
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3145
|
+
const dirs2 = [];
|
|
3146
|
+
let cur = resolve3(root);
|
|
3147
|
+
const stop = projectRoot ? resolve3(projectRoot) : cur;
|
|
3148
|
+
while (true) {
|
|
3149
|
+
if (!seen.has(cur)) {
|
|
3150
|
+
seen.add(cur);
|
|
3151
|
+
dirs2.push(cur);
|
|
3152
|
+
}
|
|
3153
|
+
if (cur === stop || !cur.startsWith(stop)) break;
|
|
3154
|
+
const parent = dirname2(cur);
|
|
3155
|
+
if (parent === cur) break;
|
|
3156
|
+
cur = parent;
|
|
3157
|
+
}
|
|
3158
|
+
for (const dir of dirs2) {
|
|
3159
|
+
let lines = [];
|
|
3160
|
+
try {
|
|
3161
|
+
lines = readFileSync2(join3(dir, ".gitignore"), "utf8").split(/\r?\n/);
|
|
3162
|
+
} catch {
|
|
3163
|
+
continue;
|
|
3164
|
+
}
|
|
3165
|
+
for (const raw of lines) {
|
|
3166
|
+
const line = raw.trim();
|
|
3167
|
+
if (!line || line.startsWith("#") || line.startsWith("!")) continue;
|
|
3168
|
+
const p = line.replace(/^\//, "").replace(/\/$/, "");
|
|
3169
|
+
patterns.push(`**/${p}`, `**/${p}/**`);
|
|
3170
|
+
}
|
|
2901
3171
|
}
|
|
2902
|
-
return patterns;
|
|
3172
|
+
return [...new Set(patterns)];
|
|
2903
3173
|
}
|
|
2904
3174
|
|
|
2905
3175
|
// src/tools/globSafety.ts
|
|
@@ -2941,19 +3211,30 @@ var globTool = defineTool({
|
|
|
2941
3211
|
path: z4.string().optional().describe("Base directory, default project root")
|
|
2942
3212
|
}),
|
|
2943
3213
|
kind: "read",
|
|
2944
|
-
target: (input) => input.pattern,
|
|
3214
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
2945
3215
|
async run(input, ctx) {
|
|
2946
3216
|
const invalid = validateGlobPattern(input.pattern);
|
|
2947
3217
|
if (invalid) return { output: invalid, isError: true };
|
|
2948
3218
|
const root = resolvePath(ctx.cwd, input.path ?? ".");
|
|
2949
3219
|
const files = [];
|
|
2950
3220
|
const finished = await collectUntilAbort(
|
|
2951
|
-
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root) }),
|
|
3221
|
+
nativeGlob(input.pattern, { cwd: root, exclude: ignorePatterns(root, ctx.cwd) }),
|
|
2952
3222
|
ctx.signal,
|
|
2953
3223
|
(file) => files.push(normalizeGlobPath(file))
|
|
2954
3224
|
);
|
|
2955
3225
|
if (!finished) return { output: "Cancelled by the user.", isError: true };
|
|
2956
|
-
|
|
3226
|
+
const mtime = (rel) => {
|
|
3227
|
+
try {
|
|
3228
|
+
return statSync2(join4(root, rel)).mtimeMs;
|
|
3229
|
+
} catch {
|
|
3230
|
+
return 0;
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
const mtimes = new Map(files.map((f) => [f, mtime(f)]));
|
|
3234
|
+
files.sort((a, b) => {
|
|
3235
|
+
const diff = (mtimes.get(b) ?? 0) - (mtimes.get(a) ?? 0);
|
|
3236
|
+
return Math.abs(diff) >= 50 ? diff : a.localeCompare(b);
|
|
3237
|
+
});
|
|
2957
3238
|
if (!files.length) return { output: "No matching files." };
|
|
2958
3239
|
const first = files.slice(0, 500);
|
|
2959
3240
|
const shown = [];
|
|
@@ -2974,75 +3255,141 @@ var globTool = defineTool({
|
|
|
2974
3255
|
|
|
2975
3256
|
// src/tools/grep.ts
|
|
2976
3257
|
import { execFile } from "child_process";
|
|
3258
|
+
import { statSync as statSync3 } from "fs";
|
|
2977
3259
|
import { readFile as readFile2 } from "fs/promises";
|
|
2978
3260
|
import { glob as nativeGlob2 } from "fs/promises";
|
|
3261
|
+
import { basename, dirname as dirname3 } from "path";
|
|
2979
3262
|
import { z as z5 } from "zod";
|
|
2980
3263
|
var MAX_MATCHES = 200;
|
|
2981
3264
|
var MAX_OUTPUT_CHARS2 = 24e3;
|
|
3265
|
+
function escapeRegExp(s) {
|
|
3266
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3267
|
+
}
|
|
3268
|
+
function isSingleFile(p) {
|
|
3269
|
+
try {
|
|
3270
|
+
return statSync3(p).isFile();
|
|
3271
|
+
} catch {
|
|
3272
|
+
return false;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
2982
3275
|
async function grepJs(pattern, opts) {
|
|
2983
|
-
const
|
|
3276
|
+
const source = opts.literal ? escapeRegExp(pattern) : pattern;
|
|
3277
|
+
const regex = new RegExp(source, opts.ignoreCase ? "i" : "");
|
|
3278
|
+
const single = isSingleFile(opts.root);
|
|
2984
3279
|
const files = [];
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3280
|
+
if (single) {
|
|
3281
|
+
files.push(basename(opts.root));
|
|
3282
|
+
} else {
|
|
3283
|
+
const finished = await collectUntilAbort(
|
|
3284
|
+
nativeGlob2(opts.glob ?? "**/*", { cwd: opts.root, exclude: ignorePatterns(opts.root, opts.projectRoot) }),
|
|
3285
|
+
opts.signal,
|
|
3286
|
+
(file) => files.push(normalizeGlobPath(file))
|
|
3287
|
+
);
|
|
3288
|
+
if (!finished) return [];
|
|
3289
|
+
}
|
|
3290
|
+
const baseDir = single ? dirname3(opts.root) : opts.root;
|
|
3291
|
+
const ctxLines = Math.max(0, Math.min(10, Math.floor(opts.context ?? 0)));
|
|
3292
|
+
const mode = opts.outputMode ?? "content";
|
|
2990
3293
|
const out = [];
|
|
2991
|
-
if (!finished) return out;
|
|
2992
3294
|
for (const file of files.sort()) {
|
|
2993
3295
|
if (opts.signal?.aborted) break;
|
|
2994
3296
|
let text;
|
|
2995
3297
|
try {
|
|
2996
|
-
const buffer = await readFile2(resolvePath(
|
|
3298
|
+
const buffer = await readFile2(resolvePath(baseDir, file));
|
|
2997
3299
|
if (buffer.subarray(0, 8e3).includes(0)) continue;
|
|
2998
3300
|
text = buffer.toString("utf8");
|
|
2999
3301
|
} catch {
|
|
3000
3302
|
continue;
|
|
3001
3303
|
}
|
|
3002
3304
|
const lines = text.split(/\r?\n/);
|
|
3305
|
+
let fileMatches = 0;
|
|
3003
3306
|
for (let i = 0; i < lines.length && out.length < MAX_MATCHES; i++) {
|
|
3004
|
-
if (regex.test(lines[i]))
|
|
3307
|
+
if (regex.test(lines[i])) {
|
|
3308
|
+
fileMatches++;
|
|
3309
|
+
if (mode === "files_with_matches") {
|
|
3310
|
+
out.push(file);
|
|
3311
|
+
break;
|
|
3312
|
+
}
|
|
3313
|
+
if (mode === "content") {
|
|
3314
|
+
const from = Math.max(0, i - ctxLines);
|
|
3315
|
+
const to = Math.min(lines.length - 1, i + ctxLines);
|
|
3316
|
+
for (let j = from; j <= to && out.length < MAX_MATCHES; j++) {
|
|
3317
|
+
const sep2 = j === i ? ":" : "-";
|
|
3318
|
+
out.push(`${file}${sep2}${j + 1}${sep2}${lines[j].slice(0, 300)}`);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3005
3322
|
}
|
|
3323
|
+
if (mode === "count" && fileMatches > 0) out.push(`${file}:${fileMatches}`);
|
|
3006
3324
|
if (out.length >= MAX_MATCHES) break;
|
|
3007
3325
|
}
|
|
3008
3326
|
return out;
|
|
3009
3327
|
}
|
|
3010
3328
|
function grepRg(pattern, opts) {
|
|
3011
|
-
const
|
|
3329
|
+
const single = isSingleFile(opts.root);
|
|
3330
|
+
const cwd = single ? dirname3(opts.root) : opts.root;
|
|
3331
|
+
const target = single ? basename(opts.root) : ".";
|
|
3332
|
+
const mode = opts.outputMode ?? "content";
|
|
3333
|
+
const args = ["--color", "never"];
|
|
3334
|
+
if (mode === "files_with_matches") {
|
|
3335
|
+
args.push("--files-with-matches");
|
|
3336
|
+
} else if (mode === "count") {
|
|
3337
|
+
args.push("--count");
|
|
3338
|
+
} else {
|
|
3339
|
+
args.push("--line-number", "--no-heading", "--max-columns", "300");
|
|
3340
|
+
if (opts.context && opts.context > 0) args.push("-C", String(Math.min(10, Math.floor(opts.context))));
|
|
3341
|
+
}
|
|
3342
|
+
if (opts.literal) args.push("-F");
|
|
3012
3343
|
if (opts.ignoreCase) args.push("-i");
|
|
3013
|
-
if (opts.glob) args.push("-g", opts.glob);
|
|
3014
|
-
args.push("
|
|
3015
|
-
return new Promise((
|
|
3016
|
-
execFile("rg", args, { cwd
|
|
3017
|
-
if (opts.signal?.aborted) return
|
|
3344
|
+
if (opts.glob && !single) args.push("-g", opts.glob);
|
|
3345
|
+
args.push("-e", pattern, target);
|
|
3346
|
+
return new Promise((resolve5) => {
|
|
3347
|
+
execFile("rg", args, { cwd, maxBuffer: 10 * 1024 * 1024, signal: opts.signal }, (error, stdout) => {
|
|
3348
|
+
if (opts.signal?.aborted) return resolve5([]);
|
|
3018
3349
|
const code = error?.code;
|
|
3019
|
-
if (code === "ENOENT") return
|
|
3020
|
-
if (error && error.code !== 1) return
|
|
3021
|
-
|
|
3350
|
+
if (code === "ENOENT") return resolve5(void 0);
|
|
3351
|
+
if (error && error.code !== 1) return resolve5(void 0);
|
|
3352
|
+
resolve5(
|
|
3353
|
+
stdout.split(/\r?\n/).filter(Boolean).map((l) => l.replace(/^\.[\\/]/, "").replace(/\\/g, "/")).slice(0, MAX_MATCHES)
|
|
3354
|
+
);
|
|
3022
3355
|
});
|
|
3023
3356
|
});
|
|
3024
3357
|
}
|
|
3025
3358
|
var grepTool = defineTool({
|
|
3026
3359
|
name: "grep",
|
|
3027
|
-
description: "Search file contents with a regular expression. Respects .gitignore. Returns path:line:text (max 200).",
|
|
3360
|
+
description: "Search file contents with a regular expression or literal text. Respects .gitignore. Returns path:line:text (max 200).",
|
|
3028
3361
|
schema: z5.object({
|
|
3029
|
-
pattern: z5.string().describe("Regular expression"),
|
|
3030
|
-
path: z5.string().optional().describe("
|
|
3362
|
+
pattern: z5.string().describe("Regular expression or literal string"),
|
|
3363
|
+
path: z5.string().optional().describe("File or directory to search, default project root"),
|
|
3031
3364
|
glob: z5.string().optional().describe('Only files matching this glob, e.g. "**/*.ts"'),
|
|
3032
|
-
ignore_case: z5.boolean().optional()
|
|
3365
|
+
ignore_case: z5.boolean().optional(),
|
|
3366
|
+
literal: z5.boolean().optional().describe("Treat pattern as literal text instead of regex"),
|
|
3367
|
+
context: z5.number().int().min(0).max(10).optional().describe("Number of context lines before and after each match"),
|
|
3368
|
+
output_mode: z5.enum(["content", "files_with_matches", "count"]).optional().describe("Output format, default content")
|
|
3033
3369
|
}),
|
|
3034
3370
|
kind: "read",
|
|
3035
|
-
target: (input) => input.pattern,
|
|
3371
|
+
target: (input) => input.path ? `${input.pattern} ${input.path}` : input.pattern,
|
|
3036
3372
|
async run(input, ctx) {
|
|
3037
3373
|
if (input.glob) {
|
|
3038
3374
|
const invalid = validateGlobPattern(input.glob);
|
|
3039
3375
|
if (invalid) return { output: invalid, isError: true };
|
|
3040
3376
|
}
|
|
3041
|
-
const opts = {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3377
|
+
const opts = {
|
|
3378
|
+
root: resolvePath(ctx.cwd, input.path ?? "."),
|
|
3379
|
+
projectRoot: ctx.cwd,
|
|
3380
|
+
glob: input.glob,
|
|
3381
|
+
ignoreCase: input.ignore_case,
|
|
3382
|
+
literal: input.literal,
|
|
3383
|
+
context: input.context,
|
|
3384
|
+
outputMode: input.output_mode,
|
|
3385
|
+
signal: ctx.signal
|
|
3386
|
+
};
|
|
3387
|
+
if (!input.literal) {
|
|
3388
|
+
try {
|
|
3389
|
+
new RegExp(input.pattern);
|
|
3390
|
+
} catch (error) {
|
|
3391
|
+
return { output: `Invalid regex: ${error.message}`, isError: true };
|
|
3392
|
+
}
|
|
3046
3393
|
}
|
|
3047
3394
|
const matches = await grepRg(input.pattern, opts) ?? await grepJs(input.pattern, opts);
|
|
3048
3395
|
if (ctx.signal.aborted) return { output: "Cancelled by the user.", isError: true };
|
|
@@ -3064,6 +3411,7 @@ var grepTool = defineTool({
|
|
|
3064
3411
|
});
|
|
3065
3412
|
|
|
3066
3413
|
// src/tools/read.ts
|
|
3414
|
+
import { readdirSync, statSync as statSync4 } from "fs";
|
|
3067
3415
|
import { readFile as readFile3 } from "fs/promises";
|
|
3068
3416
|
import { z as z6 } from "zod";
|
|
3069
3417
|
var MAX_LINE = 2e3;
|
|
@@ -3147,6 +3495,16 @@ function createReadTool(options = {}) {
|
|
|
3147
3495
|
target: (input) => input.path,
|
|
3148
3496
|
async run(input, ctx) {
|
|
3149
3497
|
const abs = resolvePath(ctx.cwd, input.path);
|
|
3498
|
+
try {
|
|
3499
|
+
if (statSync4(abs).isDirectory()) {
|
|
3500
|
+
const entries = readdirSync(abs, { withFileTypes: true }).map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort((a, b) => a.localeCompare(b));
|
|
3501
|
+
const start = (input.offset ?? 1) - 1;
|
|
3502
|
+
const limit = input.limit ?? 2e3;
|
|
3503
|
+
const slice = entries.slice(start, start + limit);
|
|
3504
|
+
return { output: slice.join("\n") || "(empty directory)", display: t("{n} lines", { n: slice.length }) };
|
|
3505
|
+
}
|
|
3506
|
+
} catch {
|
|
3507
|
+
}
|
|
3150
3508
|
let buffer;
|
|
3151
3509
|
try {
|
|
3152
3510
|
buffer = await readFile3(abs);
|
|
@@ -3155,13 +3513,13 @@ function createReadTool(options = {}) {
|
|
|
3155
3513
|
}
|
|
3156
3514
|
const mediaType = vision ? imageMediaType(buffer) : void 0;
|
|
3157
3515
|
if (mediaType) {
|
|
3158
|
-
ctx.readFiles
|
|
3516
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3159
3517
|
return readImageResult(input, buffer, mediaType);
|
|
3160
3518
|
}
|
|
3161
3519
|
if (buffer.subarray(0, 8e3).includes(0)) {
|
|
3162
3520
|
return { output: `${input.path} is a binary file; not read.`, isError: true };
|
|
3163
3521
|
}
|
|
3164
|
-
ctx.readFiles
|
|
3522
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3165
3523
|
return readTextResult(input, buffer);
|
|
3166
3524
|
}
|
|
3167
3525
|
});
|
|
@@ -3171,7 +3529,7 @@ var readTool = createReadTool({ vision: true });
|
|
|
3171
3529
|
// src/tools/write.ts
|
|
3172
3530
|
import { existsSync as existsSync4 } from "fs";
|
|
3173
3531
|
import { mkdir, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
3174
|
-
import { dirname as
|
|
3532
|
+
import { dirname as dirname4 } from "path";
|
|
3175
3533
|
import { z as z7 } from "zod";
|
|
3176
3534
|
var writeTool = defineTool({
|
|
3177
3535
|
name: "write",
|
|
@@ -3198,12 +3556,12 @@ var writeTool = defineTool({
|
|
|
3198
3556
|
}
|
|
3199
3557
|
try {
|
|
3200
3558
|
await ctx.checkpoint?.(abs);
|
|
3201
|
-
await mkdir(
|
|
3559
|
+
await mkdir(dirname4(abs), { recursive: true });
|
|
3202
3560
|
await writeFile2(abs, input.content);
|
|
3203
3561
|
} catch (error) {
|
|
3204
3562
|
return { output: `Failed to write ${input.path}: ${errorCode(error)}`, isError: true };
|
|
3205
3563
|
}
|
|
3206
|
-
ctx.readFiles
|
|
3564
|
+
recordFileMtime(ctx.readFiles, abs);
|
|
3207
3565
|
const lines = input.content.split("\n").length;
|
|
3208
3566
|
return { output: `Wrote ${input.path} (${lines} lines).`, display: t("{lines} lines written", { lines }) };
|
|
3209
3567
|
}
|
|
@@ -3313,12 +3671,36 @@ function budgetStatus(usage, cap, model) {
|
|
|
3313
3671
|
}
|
|
3314
3672
|
|
|
3315
3673
|
// src/tokenBudget.ts
|
|
3674
|
+
var IMAGE_TOKEN_COST = 1e3;
|
|
3316
3675
|
var estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
3317
|
-
|
|
3676
|
+
function isImageAttachmentMessage(m) {
|
|
3677
|
+
if (m.role !== "user" || !Array.isArray(m.content)) return false;
|
|
3678
|
+
const hasImage = m.content.some((p) => p.type === "image_url");
|
|
3679
|
+
const firstText = m.content.find((p) => p.type === "text");
|
|
3680
|
+
return hasImage && !!firstText && firstText.type === "text" && firstText.text.startsWith("Image read from ");
|
|
3681
|
+
}
|
|
3682
|
+
function isUserTurnBoundary(m) {
|
|
3683
|
+
return m.role === "user" && !isImageAttachmentMessage(m);
|
|
3684
|
+
}
|
|
3685
|
+
function estimateMessages(messages) {
|
|
3686
|
+
let total = 0;
|
|
3687
|
+
for (const m of messages) {
|
|
3688
|
+
if (m.role === "user" && Array.isArray(m.content)) {
|
|
3689
|
+
for (const part of m.content) {
|
|
3690
|
+
if (part.type === "image_url") total += IMAGE_TOKEN_COST;
|
|
3691
|
+
else total += estimateTokens(part.text);
|
|
3692
|
+
}
|
|
3693
|
+
total += 8;
|
|
3694
|
+
} else {
|
|
3695
|
+
total += estimateTokens(JSON.stringify(m));
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
return total;
|
|
3699
|
+
}
|
|
3318
3700
|
function trimToFit(messages, maxTokens) {
|
|
3319
3701
|
let out = messages;
|
|
3320
3702
|
while (estimateMessages(out) > maxTokens) {
|
|
3321
|
-
const boundary = out.findIndex((m, i) => i > 0 && m
|
|
3703
|
+
const boundary = out.findIndex((m, i) => i > 0 && isUserTurnBoundary(m));
|
|
3322
3704
|
if (boundary === -1) return out;
|
|
3323
3705
|
out = out.slice(boundary);
|
|
3324
3706
|
}
|
|
@@ -3328,13 +3710,13 @@ function splitKeepRecent(messages, keepTokens) {
|
|
|
3328
3710
|
if (messages.length === 0) return [[], []];
|
|
3329
3711
|
let start = messages.length;
|
|
3330
3712
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3331
|
-
if (i > 0 && messages[i]
|
|
3713
|
+
if (i > 0 && !isUserTurnBoundary(messages[i])) continue;
|
|
3332
3714
|
if (estimateMessages(messages.slice(i)) <= keepTokens) start = i;
|
|
3333
3715
|
else break;
|
|
3334
3716
|
}
|
|
3335
3717
|
if (start === messages.length) {
|
|
3336
3718
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3337
|
-
if (messages[i]
|
|
3719
|
+
if (isUserTurnBoundary(messages[i])) {
|
|
3338
3720
|
start = i;
|
|
3339
3721
|
break;
|
|
3340
3722
|
}
|
|
@@ -3382,6 +3764,7 @@ var Agent2 = class {
|
|
|
3382
3764
|
this.messages = [...opts.history ?? []];
|
|
3383
3765
|
this.maxSteps = typeof opts.maxSteps === "number" && Number.isFinite(opts.maxSteps) && opts.maxSteps > 0 ? Math.max(1, Math.floor(opts.maxSteps)) : void 0;
|
|
3384
3766
|
this.contextWindow = opts.contextWindow ?? 128e3;
|
|
3767
|
+
this.budgetModel = opts.budgetModel ?? "";
|
|
3385
3768
|
this.systemPrompt = opts.systemPrompt;
|
|
3386
3769
|
this.reasoning = opts.reasoning ?? "auto";
|
|
3387
3770
|
}
|
|
@@ -3405,6 +3788,7 @@ var Agent2 = class {
|
|
|
3405
3788
|
maxSteps;
|
|
3406
3789
|
// One window for every model; resolved from contextWindowFor() (catalog + env override).
|
|
3407
3790
|
contextWindow;
|
|
3791
|
+
budgetModel;
|
|
3408
3792
|
systemPrompt;
|
|
3409
3793
|
setSystemPrompt(text) {
|
|
3410
3794
|
this.systemPrompt = text;
|
|
@@ -3412,6 +3796,15 @@ var Agent2 = class {
|
|
|
3412
3796
|
setContextWindow(window) {
|
|
3413
3797
|
this.contextWindow = window;
|
|
3414
3798
|
}
|
|
3799
|
+
getContextWindow() {
|
|
3800
|
+
return this.contextWindow;
|
|
3801
|
+
}
|
|
3802
|
+
setBudgetModel(model) {
|
|
3803
|
+
this.budgetModel = model;
|
|
3804
|
+
}
|
|
3805
|
+
getBudgetModel() {
|
|
3806
|
+
return this.budgetModel;
|
|
3807
|
+
}
|
|
3415
3808
|
setTools(tools) {
|
|
3416
3809
|
this.tools = tools;
|
|
3417
3810
|
this.definitions = toolDefinitions(tools);
|
|
@@ -3612,7 +4005,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
3612
4005
|
}
|
|
3613
4006
|
/** Current cap status; undefined means the budget is not exhausted. */
|
|
3614
4007
|
overBudget() {
|
|
3615
|
-
return budgetStatus(this.totalUsage, this.opts.usageCap, this.
|
|
4008
|
+
return budgetStatus(this.totalUsage, this.opts.usageCap, this.budgetModel);
|
|
3616
4009
|
}
|
|
3617
4010
|
/** Every tool call needs an answer in the history, or the next request to the model is rejected. */
|
|
3618
4011
|
cancelCalls(calls) {
|
|
@@ -3732,7 +4125,7 @@ ${t("This model may not support tool calling. Try another model with /model.")}`
|
|
|
3732
4125
|
};
|
|
3733
4126
|
|
|
3734
4127
|
// src/checkpoints.ts
|
|
3735
|
-
import { existsSync as existsSync5, statSync } from "fs";
|
|
4128
|
+
import { existsSync as existsSync5, statSync as statSync5 } from "fs";
|
|
3736
4129
|
import { readFile as readFile5, rm, writeFile as writeFile3 } from "fs/promises";
|
|
3737
4130
|
var MAX_TURNS = 20;
|
|
3738
4131
|
var MAX_BYTES2 = 2 * 1024 * 1024;
|
|
@@ -3759,8 +4152,8 @@ var CheckpointStore = class {
|
|
|
3759
4152
|
history = [];
|
|
3760
4153
|
current;
|
|
3761
4154
|
pendingRedo;
|
|
4155
|
+
offset = 0;
|
|
3762
4156
|
beginTurn() {
|
|
3763
|
-
if (this.current?.size === 0) return;
|
|
3764
4157
|
this.current = /* @__PURE__ */ new Map();
|
|
3765
4158
|
this.history.push(this.current);
|
|
3766
4159
|
this.pendingRedo = void 0;
|
|
@@ -3771,16 +4164,19 @@ var CheckpointStore = class {
|
|
|
3771
4164
|
const turn = this.current;
|
|
3772
4165
|
if (turn.has(path)) return;
|
|
3773
4166
|
if (!existsSync5(path)) turn.set(path, null);
|
|
3774
|
-
else if (
|
|
4167
|
+
else if (statSync5(path).size > MAX_BYTES2) turn.set(path, "skipped");
|
|
3775
4168
|
else turn.set(path, await readFile5(path));
|
|
3776
|
-
while (this.turns() > MAX_TURNS)
|
|
4169
|
+
while (this.turns() > MAX_TURNS) {
|
|
4170
|
+
this.history.shift();
|
|
4171
|
+
this.offset++;
|
|
4172
|
+
}
|
|
3777
4173
|
}
|
|
3778
4174
|
turns() {
|
|
3779
4175
|
return this.history.filter((t2) => t2.size).length;
|
|
3780
4176
|
}
|
|
3781
4177
|
/** Total entries including empty ones; used to align file snapshots with conversation turns. */
|
|
3782
4178
|
entries() {
|
|
3783
|
-
return this.history.length;
|
|
4179
|
+
return this.offset + this.history.length;
|
|
3784
4180
|
}
|
|
3785
4181
|
async undo() {
|
|
3786
4182
|
while (this.history.length && !this.history.at(-1)?.size) this.history.pop();
|
|
@@ -3800,7 +4196,7 @@ var CheckpointStore = class {
|
|
|
3800
4196
|
* pairs conversation turns with the entry count recorded when each turn started.
|
|
3801
4197
|
*/
|
|
3802
4198
|
async undoTo(keep) {
|
|
3803
|
-
const target = Math.max(0, Math.min(keep, this.history.length));
|
|
4199
|
+
const target = Math.max(0, Math.min(keep - this.offset, this.history.length));
|
|
3804
4200
|
const result = emptyResult();
|
|
3805
4201
|
const redo = /* @__PURE__ */ new Map();
|
|
3806
4202
|
let applied = false;
|
|
@@ -3825,8 +4221,11 @@ var CheckpointStore = class {
|
|
|
3825
4221
|
const turn = this.pendingRedo;
|
|
3826
4222
|
if (!turn) return void 0;
|
|
3827
4223
|
this.pendingRedo = void 0;
|
|
4224
|
+
const undoEntry = /* @__PURE__ */ new Map();
|
|
4225
|
+
await this.captureFor(turn, undoEntry);
|
|
3828
4226
|
const result = emptyResult();
|
|
3829
4227
|
await restoreTurn(turn, result);
|
|
4228
|
+
if (undoEntry.size) this.history.push(undoEntry);
|
|
3830
4229
|
return result;
|
|
3831
4230
|
}
|
|
3832
4231
|
/** Records the on-disk state of a turn's paths so redo() can put it back. */
|
|
@@ -3839,7 +4238,7 @@ var CheckpointStore = class {
|
|
|
3839
4238
|
}
|
|
3840
4239
|
try {
|
|
3841
4240
|
if (!existsSync5(path)) into.set(path, null);
|
|
3842
|
-
else if (
|
|
4241
|
+
else if (statSync5(path).size > MAX_BYTES2) into.set(path, "skipped");
|
|
3843
4242
|
else into.set(path, await readFile5(path));
|
|
3844
4243
|
} catch {
|
|
3845
4244
|
into.set(path, "skipped");
|
|
@@ -3850,6 +4249,7 @@ var CheckpointStore = class {
|
|
|
3850
4249
|
this.history.length = 0;
|
|
3851
4250
|
this.current = void 0;
|
|
3852
4251
|
this.pendingRedo = void 0;
|
|
4252
|
+
this.offset = 0;
|
|
3853
4253
|
}
|
|
3854
4254
|
};
|
|
3855
4255
|
|
|
@@ -3897,8 +4297,8 @@ import { z as z9 } from "zod";
|
|
|
3897
4297
|
|
|
3898
4298
|
// src/mcp/config.ts
|
|
3899
4299
|
import { createHash } from "crypto";
|
|
3900
|
-
import {
|
|
3901
|
-
import { dirname as
|
|
4300
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
4301
|
+
import { dirname as dirname5, join as join5, resolve as resolve4 } from "path";
|
|
3902
4302
|
var ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
3903
4303
|
function expandEnvVars(config, env) {
|
|
3904
4304
|
const expand = (value) => value.replace(ENV_REF, (match, name, fallback) => {
|
|
@@ -3920,33 +4320,39 @@ function expandEnvVars(config, env) {
|
|
|
3920
4320
|
|
|
3921
4321
|
// src/mcp/oauth.ts
|
|
3922
4322
|
import { spawn as spawn3 } from "child_process";
|
|
3923
|
-
import {
|
|
4323
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
3924
4324
|
import { createServer } from "http";
|
|
3925
|
-
import { dirname as
|
|
4325
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
3926
4326
|
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
|
3927
|
-
var oauthStorePath = (home) =>
|
|
4327
|
+
var oauthStorePath = (home) => join6(home, "mcp-oauth.json");
|
|
3928
4328
|
function readStore(home) {
|
|
3929
4329
|
const path = oauthStorePath(home);
|
|
3930
4330
|
if (!existsSync7(path)) return {};
|
|
3931
4331
|
try {
|
|
3932
4332
|
return JSON.parse(readFileSync4(path, "utf8"));
|
|
3933
|
-
} catch {
|
|
4333
|
+
} catch (error) {
|
|
4334
|
+
quarantineCorruptFile(path, error);
|
|
3934
4335
|
return {};
|
|
3935
4336
|
}
|
|
3936
4337
|
}
|
|
3937
4338
|
function writeStore(home, store) {
|
|
3938
4339
|
const path = oauthStorePath(home);
|
|
3939
|
-
mkdirSync3(
|
|
3940
|
-
|
|
3941
|
-
`,
|
|
3942
|
-
chmodSync3(path, 384);
|
|
4340
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
4341
|
+
writeFileAtomic(path, `${JSON.stringify(store, null, 2)}
|
|
4342
|
+
`, 384);
|
|
3943
4343
|
}
|
|
3944
4344
|
function hasStoredTokens(home, serverUrl) {
|
|
3945
4345
|
return Boolean(readStore(home)[serverUrl]?.tokens);
|
|
3946
4346
|
}
|
|
3947
|
-
function
|
|
3948
|
-
|
|
3949
|
-
|
|
4347
|
+
function openCommand(url, platform) {
|
|
4348
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
4349
|
+
if (platform === "win32") return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url.href] };
|
|
4350
|
+
return { command: platform === "darwin" ? "open" : "xdg-open", args: [url.href] };
|
|
4351
|
+
}
|
|
4352
|
+
function defaultOpen(url, deps = {}) {
|
|
4353
|
+
const plan = openCommand(url, deps.platform ?? process.platform);
|
|
4354
|
+
if (!plan) return;
|
|
4355
|
+
const child = (deps.spawn ?? spawn3)(plan.command, plan.args, { detached: true, stdio: "ignore" });
|
|
3950
4356
|
child.unref();
|
|
3951
4357
|
child.on("error", () => {
|
|
3952
4358
|
});
|
|
@@ -4025,7 +4431,28 @@ var FileOAuthProvider = class {
|
|
|
4025
4431
|
};
|
|
4026
4432
|
|
|
4027
4433
|
// src/mcp/manager.ts
|
|
4028
|
-
|
|
4434
|
+
function formatMcpContent(content) {
|
|
4435
|
+
const parts = [];
|
|
4436
|
+
const images = [];
|
|
4437
|
+
for (const c of content ?? []) {
|
|
4438
|
+
if (c.type === "text") {
|
|
4439
|
+
parts.push(c.text ?? "");
|
|
4440
|
+
} else if (c.type === "image" && c.data) {
|
|
4441
|
+
const mediaType = c.mimeType || "image/png";
|
|
4442
|
+
images.push({ mediaType, data: c.data, path: "mcp:image" });
|
|
4443
|
+
parts.push(`[image attached: ${mediaType}]`);
|
|
4444
|
+
} else if (c.type === "resource" && c.resource?.text) {
|
|
4445
|
+
parts.push(c.resource.uri ? `[${c.resource.uri}]
|
|
4446
|
+
${c.resource.text}` : c.resource.text);
|
|
4447
|
+
} else {
|
|
4448
|
+
parts.push(`[${c.type}]`);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
return {
|
|
4452
|
+
output: parts.join("\n") || "(empty)",
|
|
4453
|
+
...images.length ? { images } : {}
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4029
4456
|
var CHILD_ENV_KEYS = [
|
|
4030
4457
|
// portable
|
|
4031
4458
|
"PATH",
|
|
@@ -4154,11 +4581,13 @@ var McpManager = class {
|
|
|
4154
4581
|
async run(input, ctx) {
|
|
4155
4582
|
try {
|
|
4156
4583
|
const result = await client.callTool({ name: tool.name, arguments: input }, void 0, { signal: ctx.signal });
|
|
4157
|
-
const
|
|
4584
|
+
const formatted = formatMcpContent(result.content);
|
|
4585
|
+
const output = formatted.output;
|
|
4158
4586
|
return {
|
|
4159
|
-
output: output.length >
|
|
4160
|
-
\u2026 [truncated at
|
|
4161
|
-
isError: !!result.isError
|
|
4587
|
+
output: output.length > 32e3 ? `${output.slice(0, 32e3)}
|
|
4588
|
+
\u2026 [truncated at 32000 characters; narrow the MCP request if possible]` : output,
|
|
4589
|
+
isError: !!result.isError,
|
|
4590
|
+
...formatted.images ? { images: formatted.images } : {}
|
|
4162
4591
|
};
|
|
4163
4592
|
} catch (error) {
|
|
4164
4593
|
return { output: `Server MCP ${server} error: ${error.message}`, isError: true };
|
|
@@ -4192,8 +4621,8 @@ import { homedir as homedir2 } from "os";
|
|
|
4192
4621
|
|
|
4193
4622
|
// src/context.ts
|
|
4194
4623
|
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
4195
|
-
import { dirname as
|
|
4196
|
-
var FILES = ["AGENTS.md", "BCCLI.md"];
|
|
4624
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
4625
|
+
var FILES = ["AGENTS.md", "CLAUDE.md", "BCCLI.md"];
|
|
4197
4626
|
function readIf(path) {
|
|
4198
4627
|
try {
|
|
4199
4628
|
return existsSync8(path) ? readFileSync5(path, "utf8").trim() : void 0;
|
|
@@ -4203,11 +4632,11 @@ function readIf(path) {
|
|
|
4203
4632
|
}
|
|
4204
4633
|
function instructionPaths(cwd, home) {
|
|
4205
4634
|
const dirs2 = [];
|
|
4206
|
-
for (let dir = cwd; ; dir =
|
|
4635
|
+
for (let dir = cwd; ; dir = dirname7(dir)) {
|
|
4207
4636
|
dirs2.unshift(dir);
|
|
4208
|
-
if (
|
|
4637
|
+
if (dirname7(dir) === dir) break;
|
|
4209
4638
|
}
|
|
4210
|
-
return [
|
|
4639
|
+
return [join7(home, "BCCLI.md"), ...dirs2.flatMap((dir) => FILES.map((name) => join7(dir, name)))];
|
|
4211
4640
|
}
|
|
4212
4641
|
function loadInstructions(cwd, home) {
|
|
4213
4642
|
const parts = [];
|
|
@@ -4219,9 +4648,9 @@ ${text}`);
|
|
|
4219
4648
|
return parts.join("\n\n");
|
|
4220
4649
|
}
|
|
4221
4650
|
function isGitRepo(cwd) {
|
|
4222
|
-
for (let dir = cwd; ; dir =
|
|
4223
|
-
if (existsSync8(
|
|
4224
|
-
if (
|
|
4651
|
+
for (let dir = cwd; ; dir = dirname7(dir)) {
|
|
4652
|
+
if (existsSync8(join7(dir, ".git"))) return true;
|
|
4653
|
+
if (dirname7(dir) === dir) return false;
|
|
4225
4654
|
}
|
|
4226
4655
|
}
|
|
4227
4656
|
function buildSystemPrompt(opts) {
|
|
@@ -4264,16 +4693,34 @@ ${instructions}` : ""}`;
|
|
|
4264
4693
|
}
|
|
4265
4694
|
|
|
4266
4695
|
// src/extensions.ts
|
|
4267
|
-
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync6, statSync as
|
|
4268
|
-
import { basename, join as
|
|
4696
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync6 } from "fs";
|
|
4697
|
+
import { basename as basename2, join as join8 } from "path";
|
|
4269
4698
|
function parseFrontmatter(text) {
|
|
4270
4699
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
4271
4700
|
if (!match) return { data: {}, body: text };
|
|
4272
4701
|
const data = {};
|
|
4702
|
+
let currentListKey;
|
|
4273
4703
|
for (const line of match[1].split(/\r?\n/)) {
|
|
4704
|
+
const listItem = /^\s*-\s+(.+)$/.exec(line);
|
|
4705
|
+
if (listItem && currentListKey) {
|
|
4706
|
+
const item = listItem[1].trim().replace(/^["']|["']$/g, "");
|
|
4707
|
+
const prev = data[currentListKey];
|
|
4708
|
+
if (Array.isArray(prev)) prev.push(item);
|
|
4709
|
+
else data[currentListKey] = [item];
|
|
4710
|
+
continue;
|
|
4711
|
+
}
|
|
4274
4712
|
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
|
|
4275
|
-
if (!kv)
|
|
4713
|
+
if (!kv) {
|
|
4714
|
+
if (line.trim()) currentListKey = void 0;
|
|
4715
|
+
continue;
|
|
4716
|
+
}
|
|
4276
4717
|
let value = kv[2].trim();
|
|
4718
|
+
if (!value) {
|
|
4719
|
+
currentListKey = kv[1];
|
|
4720
|
+
data[kv[1]] = "";
|
|
4721
|
+
continue;
|
|
4722
|
+
}
|
|
4723
|
+
currentListKey = void 0;
|
|
4277
4724
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
4278
4725
|
data[kv[1]] = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
4279
4726
|
continue;
|
|
@@ -4285,11 +4732,11 @@ function parseFrontmatter(text) {
|
|
|
4285
4732
|
}
|
|
4286
4733
|
var str = (v) => Array.isArray(v) ? v.join(", ") : v;
|
|
4287
4734
|
function dirs(r, kind) {
|
|
4288
|
-
return [
|
|
4735
|
+
return [join8(r.userHome, ".claude", kind), join8(r.home, kind), join8(r.cwd, ".claude", kind), join8(r.cwd, ".bccli", kind)];
|
|
4289
4736
|
}
|
|
4290
4737
|
function list(dir) {
|
|
4291
4738
|
try {
|
|
4292
|
-
return
|
|
4739
|
+
return readdirSync2(dir).sort();
|
|
4293
4740
|
} catch {
|
|
4294
4741
|
return [];
|
|
4295
4742
|
}
|
|
@@ -4298,11 +4745,11 @@ function loadSkills(r) {
|
|
|
4298
4745
|
const byName = /* @__PURE__ */ new Map();
|
|
4299
4746
|
for (const dir of dirs(r, "skills")) {
|
|
4300
4747
|
for (const entry of list(dir)) {
|
|
4301
|
-
const file =
|
|
4748
|
+
const file = join8(dir, entry, "SKILL.md");
|
|
4302
4749
|
if (!existsSync9(file)) continue;
|
|
4303
4750
|
const { data } = parseFrontmatter(readFileSync6(file, "utf8"));
|
|
4304
4751
|
const name = str(data.name) || entry;
|
|
4305
|
-
byName.set(name, { name, description: str(data.description) ?? "", dir:
|
|
4752
|
+
byName.set(name, { name, description: str(data.description) ?? "", dir: join8(dir, entry), file });
|
|
4306
4753
|
}
|
|
4307
4754
|
}
|
|
4308
4755
|
return [...byName.values()];
|
|
@@ -4311,10 +4758,10 @@ function loadCommands(r) {
|
|
|
4311
4758
|
const byName = /* @__PURE__ */ new Map();
|
|
4312
4759
|
for (const dir of dirs(r, "commands")) {
|
|
4313
4760
|
for (const entry of list(dir)) {
|
|
4314
|
-
const file =
|
|
4315
|
-
if (!entry.endsWith(".md") || !
|
|
4761
|
+
const file = join8(dir, entry);
|
|
4762
|
+
if (!entry.endsWith(".md") || !statSync6(file).isFile()) continue;
|
|
4316
4763
|
const { data, body } = parseFrontmatter(readFileSync6(file, "utf8"));
|
|
4317
|
-
const def = { name:
|
|
4764
|
+
const def = { name: basename2(entry, ".md"), body: body.trim() };
|
|
4318
4765
|
if (data.description) def.description = str(data.description);
|
|
4319
4766
|
if (data["argument-hint"]) def.argumentHint = str(data["argument-hint"]);
|
|
4320
4767
|
byName.set(def.name, def);
|
|
@@ -4327,8 +4774,8 @@ function loadAgentDefs(r) {
|
|
|
4327
4774
|
for (const dir of dirs(r, "agents")) {
|
|
4328
4775
|
for (const entry of list(dir)) {
|
|
4329
4776
|
if (!entry.endsWith(".md")) continue;
|
|
4330
|
-
const { data, body } = parseFrontmatter(readFileSync6(
|
|
4331
|
-
const name = str(data.name) ||
|
|
4777
|
+
const { data, body } = parseFrontmatter(readFileSync6(join8(dir, entry), "utf8"));
|
|
4778
|
+
const name = str(data.name) || basename2(entry, ".md");
|
|
4332
4779
|
const def = { name, description: str(data.description) ?? "", prompt: body.trim() };
|
|
4333
4780
|
if (data.tools)
|
|
4334
4781
|
def.tools = Array.isArray(data.tools) ? data.tools : String(data.tools).split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -4346,8 +4793,7 @@ function loadAgentDefs(r) {
|
|
|
4346
4793
|
}
|
|
4347
4794
|
|
|
4348
4795
|
// src/providers.ts
|
|
4349
|
-
import {
|
|
4350
|
-
import { join as join7 } from "path";
|
|
4796
|
+
import { join as join9 } from "path";
|
|
4351
4797
|
function providerName(config, id) {
|
|
4352
4798
|
return PRESETS.find((p) => p.id === id)?.name ?? config.providers[id]?.name ?? id;
|
|
4353
4799
|
}
|
|
@@ -4406,11 +4852,11 @@ async function addProviderKey(providerId, key, deps) {
|
|
|
4406
4852
|
|
|
4407
4853
|
// src/session.ts
|
|
4408
4854
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
4409
|
-
import { appendFileSync, existsSync as existsSync10, mkdirSync as
|
|
4410
|
-
import { join as
|
|
4855
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync as rmSync2, statSync as statSync7 } from "fs";
|
|
4856
|
+
import { basename as basename3, join as join10 } from "path";
|
|
4411
4857
|
function projectDir(home, cwd) {
|
|
4412
4858
|
const slug = cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-100) || "root";
|
|
4413
|
-
return
|
|
4859
|
+
return join10(home, "sessions", slug);
|
|
4414
4860
|
}
|
|
4415
4861
|
function repairTail(messages) {
|
|
4416
4862
|
const out = [...messages];
|
|
@@ -4428,19 +4874,22 @@ var Session = class _Session {
|
|
|
4428
4874
|
this.file = file;
|
|
4429
4875
|
}
|
|
4430
4876
|
file;
|
|
4877
|
+
get id() {
|
|
4878
|
+
return basename3(this.file, ".jsonl");
|
|
4879
|
+
}
|
|
4431
4880
|
static create(home, cwd, now = /* @__PURE__ */ new Date()) {
|
|
4432
4881
|
const dir = projectDir(home, cwd);
|
|
4433
|
-
|
|
4882
|
+
mkdirSync4(dir, { recursive: true });
|
|
4434
4883
|
const stamp = now.toISOString().replace(/[:.]/g, "-");
|
|
4435
|
-
return new _Session(
|
|
4884
|
+
return new _Session(join10(dir, `${stamp}-${randomBytes2(3).toString("hex")}.jsonl`));
|
|
4436
4885
|
}
|
|
4437
4886
|
static list(home, cwd) {
|
|
4438
4887
|
const dir = projectDir(home, cwd);
|
|
4439
4888
|
if (!existsSync10(dir)) return [];
|
|
4440
|
-
return
|
|
4441
|
-
const session = new _Session(
|
|
4442
|
-
const first = session.
|
|
4443
|
-
return { session, mtime:
|
|
4889
|
+
return readdirSync3(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
4890
|
+
const session = new _Session(join10(dir, f));
|
|
4891
|
+
const first = session.loadHistory().find((m) => m.role === "user");
|
|
4892
|
+
return { session, mtime: statSync7(session.file).mtime, preview: first ? contentText(first.content).slice(0, 80) : "" };
|
|
4444
4893
|
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime() || b.session.file.localeCompare(a.session.file));
|
|
4445
4894
|
}
|
|
4446
4895
|
static latest(home, cwd) {
|
|
@@ -4480,6 +4929,44 @@ var Session = class _Session {
|
|
|
4480
4929
|
}
|
|
4481
4930
|
return repairTail(messages);
|
|
4482
4931
|
}
|
|
4932
|
+
/** Model of the most recent turn that recorded usage — serve attach continues a session with its own model. */
|
|
4933
|
+
lastModel() {
|
|
4934
|
+
const records = this.records();
|
|
4935
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
4936
|
+
const record = records[i];
|
|
4937
|
+
if (record.t === "usage" && record.model) return record.model;
|
|
4938
|
+
}
|
|
4939
|
+
return void 0;
|
|
4940
|
+
}
|
|
4941
|
+
/** Accumulated token usage since the last non-compaction reset. */
|
|
4942
|
+
totalUsage() {
|
|
4943
|
+
const segments = [{ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } }];
|
|
4944
|
+
for (const record of this.records()) {
|
|
4945
|
+
const cur = segments[segments.length - 1];
|
|
4946
|
+
if (record.t === "reset") {
|
|
4947
|
+
segments.push({ msgs: [], usage: { inputTokens: 0, outputTokens: 0 } });
|
|
4948
|
+
} else if (record.t === "msg") {
|
|
4949
|
+
cur.msgs.push(record.m);
|
|
4950
|
+
} else if (record.t === "usage") {
|
|
4951
|
+
cur.usage.inputTokens += record.inputTokens;
|
|
4952
|
+
cur.usage.outputTokens += record.outputTokens;
|
|
4953
|
+
if (record.cachedInputTokens) cur.usage.cachedInputTokens = (cur.usage.cachedInputTokens ?? 0) + record.cachedInputTokens;
|
|
4954
|
+
if (record.cacheWriteTokens) cur.usage.cacheWriteTokens = (cur.usage.cacheWriteTokens ?? 0) + record.cacheWriteTokens;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
let total = { ...segments[0].usage };
|
|
4958
|
+
for (const seg of segments.slice(1)) {
|
|
4959
|
+
if (!isCompactionSummary(seg.msgs[0])) {
|
|
4960
|
+
total = { ...seg.usage };
|
|
4961
|
+
} else {
|
|
4962
|
+
total.inputTokens += seg.usage.inputTokens;
|
|
4963
|
+
total.outputTokens += seg.usage.outputTokens;
|
|
4964
|
+
if (seg.usage.cachedInputTokens) total.cachedInputTokens = (total.cachedInputTokens ?? 0) + seg.usage.cachedInputTokens;
|
|
4965
|
+
if (seg.usage.cacheWriteTokens) total.cacheWriteTokens = (total.cacheWriteTokens ?? 0) + seg.usage.cacheWriteTokens;
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
return total;
|
|
4969
|
+
}
|
|
4483
4970
|
/**
|
|
4484
4971
|
* What the user sees on resume: the whole conversation. Compaction only shrinks what the model is sent, so
|
|
4485
4972
|
* its reset does not hide the older turns here. A real reset (/clear, rewind) still starts over, the
|
|
@@ -4492,12 +4979,22 @@ var Session = class _Session {
|
|
|
4492
4979
|
else if (record.t === "msg") segments[segments.length - 1].push(record.m);
|
|
4493
4980
|
}
|
|
4494
4981
|
let shown = segments[0];
|
|
4982
|
+
let lastSummary;
|
|
4495
4983
|
for (const segment of segments.slice(1)) {
|
|
4496
4984
|
if (!isCompactionSummary(segment[0])) {
|
|
4497
4985
|
shown = segment;
|
|
4986
|
+
lastSummary = void 0;
|
|
4498
4987
|
continue;
|
|
4499
4988
|
}
|
|
4500
4989
|
const kept = segment.slice(2);
|
|
4990
|
+
if (lastSummary && sameMessage2(segment[0], lastSummary)) {
|
|
4991
|
+
const subIdx = findContiguousSlice(shown, kept);
|
|
4992
|
+
if (subIdx !== -1) {
|
|
4993
|
+
shown = shown.slice(0, subIdx + kept.length);
|
|
4994
|
+
continue;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
lastSummary = segment[0];
|
|
4501
4998
|
let at = 0;
|
|
4502
4999
|
while (at <= shown.length && !endsWithPrefixOf(shown, at, kept)) at++;
|
|
4503
5000
|
shown = [...shown, ...kept.slice(shown.length - at)];
|
|
@@ -4524,6 +5021,20 @@ function endsWithPrefixOf(shown, at, kept) {
|
|
|
4524
5021
|
for (let j = 0; j < n; j++) if (!sameMessage2(shown[at + j], kept[j])) return false;
|
|
4525
5022
|
return true;
|
|
4526
5023
|
}
|
|
5024
|
+
function findContiguousSlice(shown, slice) {
|
|
5025
|
+
if (slice.length === 0) return shown.length;
|
|
5026
|
+
for (let i = 0; i + slice.length <= shown.length; i++) {
|
|
5027
|
+
let ok = true;
|
|
5028
|
+
for (let j = 0; j < slice.length; j++) {
|
|
5029
|
+
if (!sameMessage2(shown[i + j], slice[j])) {
|
|
5030
|
+
ok = false;
|
|
5031
|
+
break;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
if (ok) return i;
|
|
5035
|
+
}
|
|
5036
|
+
return -1;
|
|
5037
|
+
}
|
|
4527
5038
|
|
|
4528
5039
|
// src/tools/plan.ts
|
|
4529
5040
|
import { z as z10 } from "zod";
|
|
@@ -4633,6 +5144,7 @@ import { z as z13 } from "zod";
|
|
|
4633
5144
|
function createTaskTool(opts) {
|
|
4634
5145
|
const byName = new Map(opts.agents.map((a) => [a.name, a]));
|
|
4635
5146
|
const list2 = opts.agents.map((a) => `- ${a.name}: ${a.description}`).join("\n");
|
|
5147
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4636
5148
|
let askChain = Promise.resolve();
|
|
4637
5149
|
const enqueueAsk = (fn) => {
|
|
4638
5150
|
const next = askChain.then(
|
|
@@ -4652,7 +5164,8 @@ ${list2}`,
|
|
|
4652
5164
|
schema: z13.object({
|
|
4653
5165
|
agent: z13.string().describe("Agent name"),
|
|
4654
5166
|
description: z13.string().describe("3-6 word summary shown to the user"),
|
|
4655
|
-
prompt: z13.string().describe("Full instructions; the subagent sees nothing else")
|
|
5167
|
+
prompt: z13.string().describe("Full instructions; the subagent sees nothing else"),
|
|
5168
|
+
session_id: z13.string().optional().describe("Optional session id to continue a previous subagent conversation")
|
|
4656
5169
|
}),
|
|
4657
5170
|
kind: "read",
|
|
4658
5171
|
target: (input) => `[${input.agent}] ${input.description}`,
|
|
@@ -4679,6 +5192,7 @@ ${list2}`,
|
|
|
4679
5192
|
} catch (error) {
|
|
4680
5193
|
return { output: `Agent "${def.name}": ${error.message}`, isError: true };
|
|
4681
5194
|
}
|
|
5195
|
+
const priorHistory = input.session_id ? sessions.get(input.session_id) : void 0;
|
|
4682
5196
|
const child = new Agent2({
|
|
4683
5197
|
provider,
|
|
4684
5198
|
tools,
|
|
@@ -4688,6 +5202,7 @@ ${list2}`,
|
|
|
4688
5202
|
# Your role
|
|
4689
5203
|
${def.prompt}`,
|
|
4690
5204
|
cwd: opts.cwd,
|
|
5205
|
+
...priorHistory ? { history: priorHistory } : {},
|
|
4691
5206
|
// Every subagent runs with a step cap so a looping child cannot burn tokens forever.
|
|
4692
5207
|
maxSteps: def.maxSteps ?? 50,
|
|
4693
5208
|
label: def.name,
|
|
@@ -4708,12 +5223,29 @@ ${def.prompt}`,
|
|
|
4708
5223
|
const ask = ctx.ask;
|
|
4709
5224
|
if (ask) child.askPermission = (req) => enqueueAsk(() => ask(req));
|
|
4710
5225
|
await child.run(input.prompt, ctx.signal);
|
|
5226
|
+
if (input.session_id) sessions.set(input.session_id, [...child.messages]);
|
|
4711
5227
|
ctx.addUsage?.(child.totalUsage);
|
|
4712
5228
|
const tokens2 = child.totalUsage.inputTokens + child.totalUsage.outputTokens;
|
|
4713
5229
|
const display = t("{steps} steps \xB7 {tokens} tokens", { steps, tokens: tokens2 >= 1e3 ? `${Math.round(tokens2 / 1e3)}k` : tokens2 });
|
|
4714
5230
|
if (outcome?.type === "error") return { output: `Subagent ${def.name} failed: ${outcome.message}`, isError: true, display };
|
|
4715
5231
|
if (outcome?.type === "aborted") return { output: `Subagent ${def.name} was cancelled.`, isError: true, display };
|
|
4716
|
-
|
|
5232
|
+
let last = [...child.messages].reverse().find((m) => m.role === "assistant" && m.content);
|
|
5233
|
+
if (!last && outcome?.type === "stepLimit" && !ctx.signal.aborted) {
|
|
5234
|
+
try {
|
|
5235
|
+
const synthesis = await provider.chat({
|
|
5236
|
+
messages: [
|
|
5237
|
+
...child.messages,
|
|
5238
|
+
{ role: "user", content: "Step limit reached. Summarize all findings gathered so far concisely." }
|
|
5239
|
+
],
|
|
5240
|
+
signal: ctx.signal,
|
|
5241
|
+
reasoning: opts.reasoning?.() ?? "auto"
|
|
5242
|
+
});
|
|
5243
|
+
if (synthesis.text.trim()) {
|
|
5244
|
+
last = { role: "assistant", content: synthesis.text.trim() };
|
|
5245
|
+
}
|
|
5246
|
+
} catch {
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
4717
5249
|
const report = last && typeof last.content === "string" ? last.content : "(no report)";
|
|
4718
5250
|
const suffix = outcome?.type === "stepLimit" ? `
|
|
4719
5251
|
|
|
@@ -4759,7 +5291,7 @@ function createTodoTool(store) {
|
|
|
4759
5291
|
|
|
4760
5292
|
// src/tools/memory.ts
|
|
4761
5293
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
4762
|
-
import { join as
|
|
5294
|
+
import { join as join11 } from "path";
|
|
4763
5295
|
import { z as z15 } from "zod";
|
|
4764
5296
|
|
|
4765
5297
|
// src/slash/memory.ts
|
|
@@ -4782,11 +5314,11 @@ function createSaveMemoryTool(opts) {
|
|
|
4782
5314
|
kind: "edit",
|
|
4783
5315
|
// The target is what the prompt shows and what the permission model judges: the
|
|
4784
5316
|
// global store really lives outside the project, so it must not look like one.
|
|
4785
|
-
target: (input) => input.scope === "global" ?
|
|
5317
|
+
target: (input) => input.scope === "global" ? join11(opts.home, "BCCLI.md") : "AGENTS.md",
|
|
4786
5318
|
async run(input, ctx) {
|
|
4787
5319
|
const content = input.content.trim();
|
|
4788
5320
|
if (!content) return { output: "Memory content is empty.", isError: true };
|
|
4789
|
-
const file = input.scope === "global" ?
|
|
5321
|
+
const file = input.scope === "global" ? join11(opts.home, "BCCLI.md") : join11(ctx.cwd, "AGENTS.md");
|
|
4790
5322
|
if (existsSync12(file)) {
|
|
4791
5323
|
const lines = readFileSync10(file, "utf8").split("\n");
|
|
4792
5324
|
if (lines.some((line) => line.trim() === `- ${content}`)) {
|
|
@@ -4804,18 +5336,21 @@ function createSaveMemoryTool(opts) {
|
|
|
4804
5336
|
import { z as z16 } from "zod";
|
|
4805
5337
|
|
|
4806
5338
|
// src/repomap.ts
|
|
4807
|
-
import { readdirSync as
|
|
4808
|
-
import { extname, join as
|
|
5339
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
5340
|
+
import { extname, join as join12, relative as relative2, sep } from "path";
|
|
4809
5341
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
4810
5342
|
"node_modules",
|
|
4811
5343
|
".git",
|
|
4812
5344
|
"dist",
|
|
4813
5345
|
"build",
|
|
5346
|
+
"out",
|
|
4814
5347
|
"coverage",
|
|
4815
5348
|
".next",
|
|
5349
|
+
".turbo",
|
|
4816
5350
|
"target",
|
|
4817
5351
|
"vendor",
|
|
4818
5352
|
"__pycache__",
|
|
5353
|
+
"venv",
|
|
4819
5354
|
".venv",
|
|
4820
5355
|
".cache"
|
|
4821
5356
|
]);
|
|
@@ -4841,21 +5376,22 @@ var CODE_EXTS = /* @__PURE__ */ new Set([
|
|
|
4841
5376
|
".ps1"
|
|
4842
5377
|
]);
|
|
4843
5378
|
var MAX_FILES = 400;
|
|
5379
|
+
var MAX_SCAN_FILES = 5e3;
|
|
4844
5380
|
var MAX_FILE_BYTES = 2e5;
|
|
4845
5381
|
var MAX_SYMBOLS_PER_FILE = 12;
|
|
4846
5382
|
var FAMILY = {
|
|
4847
5383
|
ts: [
|
|
4848
|
-
/^\s*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+([A-Za-z0-9_$]+)/,
|
|
5384
|
+
/^\s*(?:export\s+)?(?:default\s+)?(?:declare\s+)?(?:public\s+|private\s+|protected\s+|internal\s+)?(?:static\s+)?(?:abstract\s+|final\s+|sealed\s+)?(?:async\s+)?(?:function|class|interface|type|enum)\s+([A-Za-z0-9_$]+)/,
|
|
4849
5385
|
/^\s*export\s+(?:const|let)\s+([A-Za-z0-9_$]+)/
|
|
4850
5386
|
],
|
|
4851
|
-
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*class\s+(\w+)/],
|
|
5387
|
+
py: [/^\s*(?:async\s+)?def\s+(\w+)/, /^\s*(?:module|class)\s+(\w+)/],
|
|
4852
5388
|
go: [/^func\s+(?:\([^)]*\)\s*)?(\w+)/, /^type\s+(\w+)/],
|
|
4853
5389
|
rs: [/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?(?:async\s+)?(?:fn|struct|enum|trait|type)\s+(\w+)/],
|
|
4854
5390
|
c: [/^\s*(?:static\s+|inline\s+|extern\s+)*[A-Za-z_][A-Za-z0-9_\s*]*\b(\w+)\s*\([^;]*\)\s*\{?\s*$/]
|
|
4855
5391
|
};
|
|
4856
5392
|
function familyFor(ext) {
|
|
4857
|
-
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"
|
|
4858
|
-
if (ext === ".py" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
5393
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".java", ".cs", ".php"].includes(ext)) return FAMILY.ts;
|
|
5394
|
+
if (ext === ".py" || ext === ".rb" || ext === ".sh" || ext === ".ps1") return FAMILY.py;
|
|
4859
5395
|
if (ext === ".go") return FAMILY.go;
|
|
4860
5396
|
if (ext === ".rs") return FAMILY.rs;
|
|
4861
5397
|
return FAMILY.c;
|
|
@@ -4875,16 +5411,16 @@ function extractSymbols(text, ext) {
|
|
|
4875
5411
|
return out;
|
|
4876
5412
|
}
|
|
4877
5413
|
function walk(dir, root, files) {
|
|
4878
|
-
if (files.length >=
|
|
5414
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
4879
5415
|
let entries;
|
|
4880
5416
|
try {
|
|
4881
|
-
entries =
|
|
5417
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
4882
5418
|
} catch {
|
|
4883
5419
|
return;
|
|
4884
5420
|
}
|
|
4885
5421
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4886
|
-
if (files.length >=
|
|
4887
|
-
const full =
|
|
5422
|
+
if (files.length >= MAX_SCAN_FILES) return;
|
|
5423
|
+
const full = join12(dir, e.name);
|
|
4888
5424
|
if (e.isDirectory()) {
|
|
4889
5425
|
if (SKIP_DIRS.has(e.name) || e.name.startsWith(".") && full !== root) continue;
|
|
4890
5426
|
walk(full, root, files);
|
|
@@ -4895,16 +5431,17 @@ function walk(dir, root, files) {
|
|
|
4895
5431
|
}
|
|
4896
5432
|
function buildRepoMapSync(cwd, opts = {}) {
|
|
4897
5433
|
const maxTokens = opts.maxTokens ?? 2048;
|
|
4898
|
-
const
|
|
4899
|
-
walk(cwd, cwd,
|
|
4900
|
-
|
|
4901
|
-
(a, b) => relative2(cwd, a).split(
|
|
5434
|
+
const scanned = [];
|
|
5435
|
+
walk(cwd, cwd, scanned);
|
|
5436
|
+
scanned.sort(
|
|
5437
|
+
(a, b) => relative2(cwd, a).split(sep).length - relative2(cwd, b).split(sep).length || a.localeCompare(b)
|
|
4902
5438
|
);
|
|
5439
|
+
const files = scanned.slice(0, MAX_FILES);
|
|
4903
5440
|
const lines = [];
|
|
4904
5441
|
let used = 0;
|
|
4905
5442
|
let truncated = false;
|
|
4906
5443
|
for (const file of files) {
|
|
4907
|
-
const rel = relative2(cwd, file).split(
|
|
5444
|
+
const rel = relative2(cwd, file).split(sep).join("/");
|
|
4908
5445
|
let symbols = [];
|
|
4909
5446
|
try {
|
|
4910
5447
|
const text = readFileSync11(file, "utf8");
|
|
@@ -4939,13 +5476,50 @@ function createRepoMapTool() {
|
|
|
4939
5476
|
}
|
|
4940
5477
|
|
|
4941
5478
|
// src/tools/diagnostics.ts
|
|
4942
|
-
import { extname as
|
|
5479
|
+
import { extname as extname3 } from "path";
|
|
4943
5480
|
import { z as z17 } from "zod";
|
|
4944
5481
|
|
|
4945
5482
|
// src/lsp.ts
|
|
4946
5483
|
import { spawn as spawn4 } from "child_process";
|
|
4947
5484
|
import { readFileSync as readFileSync12 } from "fs";
|
|
5485
|
+
import { extname as extname2 } from "path";
|
|
4948
5486
|
import { pathToFileURL } from "url";
|
|
5487
|
+
var LANGUAGE_IDS = {
|
|
5488
|
+
".ts": "typescript",
|
|
5489
|
+
".tsx": "typescriptreact",
|
|
5490
|
+
".mts": "typescript",
|
|
5491
|
+
".cts": "typescript",
|
|
5492
|
+
".js": "javascript",
|
|
5493
|
+
".jsx": "javascriptreact",
|
|
5494
|
+
".mjs": "javascript",
|
|
5495
|
+
".cjs": "javascript",
|
|
5496
|
+
".py": "python",
|
|
5497
|
+
".rs": "rust",
|
|
5498
|
+
".go": "go",
|
|
5499
|
+
".java": "java",
|
|
5500
|
+
".c": "c",
|
|
5501
|
+
".h": "c",
|
|
5502
|
+
".cpp": "cpp",
|
|
5503
|
+
".hpp": "cpp",
|
|
5504
|
+
".cs": "csharp",
|
|
5505
|
+
".rb": "ruby",
|
|
5506
|
+
".php": "php",
|
|
5507
|
+
".json": "json",
|
|
5508
|
+
".md": "markdown",
|
|
5509
|
+
".sh": "shellscript",
|
|
5510
|
+
".ps1": "powershell",
|
|
5511
|
+
".yaml": "yaml",
|
|
5512
|
+
".yml": "yaml",
|
|
5513
|
+
".toml": "toml",
|
|
5514
|
+
".html": "html",
|
|
5515
|
+
".css": "css"
|
|
5516
|
+
};
|
|
5517
|
+
function languageIdFor(file) {
|
|
5518
|
+
return LANGUAGE_IDS[extname2(file).toLowerCase()] ?? "plaintext";
|
|
5519
|
+
}
|
|
5520
|
+
function useShellForLsp(command, platform = process.platform) {
|
|
5521
|
+
return platform === "win32" && !/\.(exe|mjs|cjs|js)$/i.test(command.trim());
|
|
5522
|
+
}
|
|
4949
5523
|
function encodeLsp(msg) {
|
|
4950
5524
|
const body = Buffer.from(JSON.stringify(msg), "utf8");
|
|
4951
5525
|
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r
|
|
@@ -4978,8 +5552,12 @@ function createLspParser(onMessage) {
|
|
|
4978
5552
|
};
|
|
4979
5553
|
}
|
|
4980
5554
|
function getDiagnostics(opts) {
|
|
4981
|
-
return new Promise((
|
|
4982
|
-
const child = spawn4(opts.server.command, opts.server.args ?? [], {
|
|
5555
|
+
return new Promise((resolve5) => {
|
|
5556
|
+
const child = spawn4(opts.server.command, opts.server.args ?? [], {
|
|
5557
|
+
cwd: opts.cwd,
|
|
5558
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
5559
|
+
shell: useShellForLsp(opts.server.command)
|
|
5560
|
+
});
|
|
4983
5561
|
const uri = pathToFileURL(opts.file).href;
|
|
4984
5562
|
let settled = false;
|
|
4985
5563
|
let nextId = 2;
|
|
@@ -4993,6 +5571,7 @@ function getDiagnostics(opts) {
|
|
|
4993
5571
|
if (settled) return;
|
|
4994
5572
|
settled = true;
|
|
4995
5573
|
clearTimeout(timer);
|
|
5574
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
4996
5575
|
send({ jsonrpc: "2.0", id: nextId++, method: "shutdown" });
|
|
4997
5576
|
send({ jsonrpc: "2.0", method: "exit" });
|
|
4998
5577
|
const killer = setTimeout(() => {
|
|
@@ -5002,8 +5581,11 @@ function getDiagnostics(opts) {
|
|
|
5002
5581
|
}
|
|
5003
5582
|
}, 500);
|
|
5004
5583
|
killer.unref?.();
|
|
5005
|
-
|
|
5584
|
+
resolve5(diags);
|
|
5006
5585
|
};
|
|
5586
|
+
const onAbort = () => finish([]);
|
|
5587
|
+
if (opts.signal?.aborted) return finish([]);
|
|
5588
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5007
5589
|
const timer = setTimeout(() => finish([]), opts.timeoutMs ?? 8e3);
|
|
5008
5590
|
const parser = createLspParser((msg) => {
|
|
5009
5591
|
if (msg.id === 1 && msg.result) {
|
|
@@ -5016,7 +5598,7 @@ function getDiagnostics(opts) {
|
|
|
5016
5598
|
send({
|
|
5017
5599
|
jsonrpc: "2.0",
|
|
5018
5600
|
method: "textDocument/didOpen",
|
|
5019
|
-
params: { textDocument: { uri, languageId:
|
|
5601
|
+
params: { textDocument: { uri, languageId: languageIdFor(opts.file), version: 1, text } }
|
|
5020
5602
|
});
|
|
5021
5603
|
}
|
|
5022
5604
|
if (msg.method === "textDocument/publishDiagnostics" && msg.params?.uri === uri) {
|
|
@@ -5054,7 +5636,7 @@ function createDiagnosticsTool(servers) {
|
|
|
5054
5636
|
target: (input) => input.path,
|
|
5055
5637
|
async run(input, ctx) {
|
|
5056
5638
|
const file = resolvePath(ctx.cwd, input.path);
|
|
5057
|
-
const ext =
|
|
5639
|
+
const ext = extname3(file).toLowerCase();
|
|
5058
5640
|
const server = servers.find((s) => s.extensions.includes(ext));
|
|
5059
5641
|
if (!server) {
|
|
5060
5642
|
return { output: t("No LSP server is configured for {ext} files.", { ext: ext || "(none)" }), isError: true };
|
|
@@ -5070,7 +5652,7 @@ function createDiagnosticsTool(servers) {
|
|
|
5070
5652
|
// src/plugins.ts
|
|
5071
5653
|
import { existsSync as existsSync13 } from "fs";
|
|
5072
5654
|
import { createRequire } from "module";
|
|
5073
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5655
|
+
import { isAbsolute as isAbsolute3, join as join13 } from "path";
|
|
5074
5656
|
import { z as z18 } from "zod";
|
|
5075
5657
|
var require_ = createRequire(import.meta.url);
|
|
5076
5658
|
function pluginLog(message) {
|
|
@@ -5095,7 +5677,7 @@ function loadPlugins(specs, opts) {
|
|
|
5095
5677
|
for (const spec of specs) {
|
|
5096
5678
|
let file;
|
|
5097
5679
|
try {
|
|
5098
|
-
file = isAbsolute3(spec) ? spec : spec.startsWith(".") ?
|
|
5680
|
+
file = isAbsolute3(spec) ? spec : spec.startsWith(".") ? join13(opts.home, spec) : require_.resolve(spec);
|
|
5099
5681
|
} catch {
|
|
5100
5682
|
throw new ConfigError(t("Plugin not found: {spec}", { spec }));
|
|
5101
5683
|
}
|
|
@@ -5172,7 +5754,7 @@ function createRuntime(opts) {
|
|
|
5172
5754
|
let config = loadConfig(opts.cwd, env);
|
|
5173
5755
|
let modelRef = opts.args.model ?? config.model;
|
|
5174
5756
|
let reasoning = opts.args.reasoning ?? config.reasoning;
|
|
5175
|
-
const
|
|
5757
|
+
const makeSingleProvider = (ref) => {
|
|
5176
5758
|
const resolved = resolveModel(config, ref, env);
|
|
5177
5759
|
return opts.provider ?? createProvider({
|
|
5178
5760
|
baseURL: resolved.baseURL,
|
|
@@ -5184,6 +5766,25 @@ function createRuntime(opts) {
|
|
|
5184
5766
|
enableProgrammaticToolCalling: lunaPtcAutoEnabled(env)
|
|
5185
5767
|
});
|
|
5186
5768
|
};
|
|
5769
|
+
const makeProvider = (ref) => {
|
|
5770
|
+
const primary = makeSingleProvider(ref);
|
|
5771
|
+
if (opts.provider) return primary;
|
|
5772
|
+
const fallbackRefs = [
|
|
5773
|
+
...opts.args.fallbackModel ? [opts.args.fallbackModel] : [],
|
|
5774
|
+
...config.fallbackModels ?? []
|
|
5775
|
+
].filter((r) => r && r !== ref);
|
|
5776
|
+
if (!fallbackRefs.length) return primary;
|
|
5777
|
+
return withFallbackProvider(
|
|
5778
|
+
primary,
|
|
5779
|
+
() => fallbackRefs.flatMap((fbRef) => {
|
|
5780
|
+
try {
|
|
5781
|
+
return [makeSingleProvider(fbRef)];
|
|
5782
|
+
} catch {
|
|
5783
|
+
return [];
|
|
5784
|
+
}
|
|
5785
|
+
})
|
|
5786
|
+
);
|
|
5787
|
+
};
|
|
5187
5788
|
const provider = makeProvider(modelRef);
|
|
5188
5789
|
const mode = opts.args.allowAll ? "allowAll" : opts.args.permissionMode ?? config.permissionMode;
|
|
5189
5790
|
const rules = [...config.allow, ...opts.args.allowedTools.map((t2) => TOOL_RULES[t2] ?? t2)];
|
|
@@ -5205,6 +5806,10 @@ function createRuntime(opts) {
|
|
|
5205
5806
|
if (latest) {
|
|
5206
5807
|
session = latest;
|
|
5207
5808
|
history = latest.load();
|
|
5809
|
+
if (!opts.args.model) {
|
|
5810
|
+
const prevModel = latest.lastModel();
|
|
5811
|
+
if (prevModel) modelRef = prevModel;
|
|
5812
|
+
}
|
|
5208
5813
|
}
|
|
5209
5814
|
}
|
|
5210
5815
|
const repoMap = config.repoMap ? buildRepoMapSync(opts.cwd) : void 0;
|
|
@@ -5248,7 +5853,7 @@ function createRuntime(opts) {
|
|
|
5248
5853
|
// Subagents get whatever the main agent has right now (incl. MCP tools), minus task itself.
|
|
5249
5854
|
baseTools: () => agent.tools.filter((t2) => t2.name !== "task"),
|
|
5250
5855
|
permissions,
|
|
5251
|
-
provider: () => agent.provider,
|
|
5856
|
+
provider: () => opts.provider ? agent.provider : makeProvider(modelRef),
|
|
5252
5857
|
providerFor: makeProvider,
|
|
5253
5858
|
systemPrompt: (childModelRef) => systemFor(childModelRef ?? modelRef),
|
|
5254
5859
|
cwd: opts.cwd,
|
|
@@ -5334,6 +5939,20 @@ function createRuntime(opts) {
|
|
|
5334
5939
|
session = previous;
|
|
5335
5940
|
agent.load(previous.load());
|
|
5336
5941
|
agent.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
5942
|
+
if (!opts.args.model) {
|
|
5943
|
+
const prevModel = previous.lastModel();
|
|
5944
|
+
if (prevModel) {
|
|
5945
|
+
try {
|
|
5946
|
+
const nextProvider = makeProvider(prevModel);
|
|
5947
|
+
modelRef = prevModel;
|
|
5948
|
+
agent.provider = nextProvider;
|
|
5949
|
+
agent.setBudgetModel(prevModel);
|
|
5950
|
+
agent.setContextWindow(contextWindowFor(prevModel, env));
|
|
5951
|
+
agent.setSystemPrompt(systemFor(modelRef));
|
|
5952
|
+
} catch {
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5337
5956
|
todos.set([]);
|
|
5338
5957
|
checkpoints.clear();
|
|
5339
5958
|
turnMarks.length = 0;
|
|
@@ -5382,6 +6001,7 @@ function createRuntime(opts) {
|
|
|
5382
6001
|
const nextProvider = makeProvider(ref);
|
|
5383
6002
|
modelRef = ref;
|
|
5384
6003
|
agent.provider = nextProvider;
|
|
6004
|
+
agent.setBudgetModel(ref);
|
|
5385
6005
|
agent.setContextWindow(contextWindowFor(ref, env));
|
|
5386
6006
|
agent.setSystemPrompt(systemFor(modelRef));
|
|
5387
6007
|
},
|
|
@@ -5407,6 +6027,7 @@ async function* streamTask(opts) {
|
|
|
5407
6027
|
let finished = false;
|
|
5408
6028
|
let text = "";
|
|
5409
6029
|
let stopReason = "done";
|
|
6030
|
+
let errorMessage2;
|
|
5410
6031
|
const usage = { inputTokens: 0, outputTokens: 0 };
|
|
5411
6032
|
const toolCalls = [];
|
|
5412
6033
|
const byId = /* @__PURE__ */ new Map();
|
|
@@ -5456,28 +6077,45 @@ async function* streamTask(opts) {
|
|
|
5456
6077
|
case "aborted":
|
|
5457
6078
|
stopReason = "aborted";
|
|
5458
6079
|
break;
|
|
6080
|
+
case "error":
|
|
6081
|
+
stopReason = "error";
|
|
6082
|
+
errorMessage2 = event.message;
|
|
6083
|
+
push({ type: "error", message: event.message });
|
|
6084
|
+
break;
|
|
5459
6085
|
default:
|
|
5460
6086
|
break;
|
|
5461
6087
|
}
|
|
5462
6088
|
};
|
|
5463
|
-
const
|
|
5464
|
-
|
|
6089
|
+
const controller = new AbortController();
|
|
6090
|
+
const onParentAbort = () => controller.abort();
|
|
6091
|
+
if (opts.signal) {
|
|
6092
|
+
if (opts.signal.aborted) controller.abort();
|
|
6093
|
+
else opts.signal.addEventListener("abort", onParentAbort, { once: true });
|
|
6094
|
+
}
|
|
6095
|
+
const running = rt.agent.run(opts.prompt, controller.signal).catch((err) => {
|
|
6096
|
+
stopReason = "error";
|
|
6097
|
+
errorMessage2 = err instanceof Error ? err.message : String(err);
|
|
5465
6098
|
}).finally(() => {
|
|
5466
6099
|
finished = true;
|
|
5467
6100
|
wake?.();
|
|
5468
6101
|
});
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
6102
|
+
try {
|
|
6103
|
+
while (!finished || queue.length) {
|
|
6104
|
+
if (queue.length) {
|
|
6105
|
+
yield queue.shift();
|
|
6106
|
+
continue;
|
|
6107
|
+
}
|
|
6108
|
+
await new Promise((resolve5) => {
|
|
6109
|
+
wake = resolve5;
|
|
6110
|
+
});
|
|
6111
|
+
wake = null;
|
|
5473
6112
|
}
|
|
5474
|
-
await
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
6113
|
+
await running;
|
|
6114
|
+
yield { type: "result", result: { text, toolCalls, usage, stopReason, ...errorMessage2 ? { error: errorMessage2 } : {} } };
|
|
6115
|
+
} finally {
|
|
6116
|
+
opts.signal?.removeEventListener("abort", onParentAbort);
|
|
6117
|
+
if (!finished) controller.abort();
|
|
5478
6118
|
}
|
|
5479
|
-
await running;
|
|
5480
|
-
yield { type: "result", result: { text, toolCalls, usage, stopReason } };
|
|
5481
6119
|
}
|
|
5482
6120
|
async function runTask(opts) {
|
|
5483
6121
|
let out = { text: "", toolCalls: [], usage: { inputTokens: 0, outputTokens: 0 }, stopReason: "done" };
|