magpie-agent-console 1.2.0 → 1.3.1
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.md +28 -8
- package/app/app.css +1 -1
- package/app/app.js +89 -67
- package/daemon/dist/main.js +937 -152
- package/daemon/dist/main.js.map +1 -1
- package/lib/gate.mjs +25 -5
- package/lib/wrapper.mjs +184 -0
- package/package.json +1 -1
- package/page/boot.js +4 -0
package/daemon/dist/main.js
CHANGED
|
@@ -146,6 +146,9 @@ var init_i18n = __esm({
|
|
|
146
146
|
"notice.daemonRestart": "\u540E\u53F0\u5DF2\u91CD\u542F\uFF0C\u672C\u8F6E\u5DF2\u4E2D\u65AD",
|
|
147
147
|
"notice.sessionStartFailed": "\u4F1A\u8BDD\u542F\u52A8\u5931\u8D25\uFF1A{detail}",
|
|
148
148
|
"notice.sendFailed": "\u53D1\u9001\u5931\u8D25\uFF1A{detail}",
|
|
149
|
+
"notice.bashMissing": "\u6CA1\u6709\u627E\u5230 Git Bash\u3002\u8FD9\u4E2A Agent \u5728 Windows \u4E0A\u8981\u7528 Git Bash \u8FD0\u884C\u547D\u4EE4\uFF08C:\\Windows\\System32\\bash.exe \u662F WSL \u542F\u52A8\u5668\uFF0C\u4E0D\u80FD\u7528\uFF09\u3002\u8BF7\u5B89\u88C5 Git for Windows\uFF1Ahttps://git-scm.com/download/win \uFF0C\u6216\u5728\u300C\u8BBE\u7F6E \u2192 Bash \u8DEF\u5F84\u300D\u91CC\u624B\u52A8\u6307\u5B9A bash.exe\u3002",
|
|
150
|
+
"notice.autoApproveUpgrade": "1.3 \u65B0\u589E\u300C\u5168\u90E8\u81EA\u52A8\u540C\u610F\u300D\uFF1A\u73B0\u5728\u5BF9 Cursor\u3001OpenCode\u3001dsh \u4E5F\u4F1A\u771F\u6B63\u81EA\u52A8\u540C\u610F\uFF081.2 \u7684\u300C\u5168\u90E8\u653E\u884C\u300D\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\uFF0C\u4ECD\u4F1A\u8BE2\u95EE\uFF09\u3002\u4F60\u4E4B\u524D\u628A\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F\u8BBE\u6210\u4E86\u5B83\uFF0C\u4E3A\u5B89\u5168\u8D77\u89C1\u8FD9\u4E2A\u5BF9\u8BDD\u4ECD\u4ECE\u300C\u6BCF\u6B21\u8BE2\u95EE\u300D\u5F00\u59CB\uFF1B\u9700\u8981\u65F6\u8BF7\u5728\u4E0B\u65B9\u6743\u9650\u83DC\u5355\u91CC\u624B\u52A8\u5F00\u542F\uFF0C\u6216\u5728\u8BBE\u7F6E\u91CC\u91CD\u65B0\u9009\u62E9\u9ED8\u8BA4\u6743\u9650\u6A21\u5F0F\u3002",
|
|
151
|
+
"notice.autoApproveMigrated": "\u8FD9\u4E2A\u5BF9\u8BDD\u5728 1.2 \u91CC\u8BBE\u6210\u4E86\u300C\u5168\u90E8\u653E\u884C\u300D\uFF0C\u5F53\u65F6\u8FD9\u4E2A Agent \u5B9E\u9645\u4ECD\u4F1A\u8BE2\u95EE\u30021.3 \u8D77\u300C\u5168\u90E8\u81EA\u52A8\u540C\u610F\u300D\u4F1A\u771F\u6B63\u81EA\u52A8\u540C\u610F\uFF0C\u6240\u4EE5\u5DF2\u6539\u56DE\u300C\u6BCF\u6B21\u8BE2\u95EE\u300D\uFF1B\u9700\u8981\u65F6\u8BF7\u5728\u4E0B\u65B9\u6743\u9650\u83DC\u5355\u91CC\u624B\u52A8\u5F00\u542F\u3002",
|
|
149
152
|
"notice.effortNextTurn": "\u601D\u8003\u5F3A\u5EA6\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548",
|
|
150
153
|
"notice.effortResetOnModel": "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u601D\u8003\u5F3A\u5EA6\uFF0C\u5DF2\u6062\u590D\u4E3A Default",
|
|
151
154
|
"turn.agentExit": "Agent \u8FDB\u7A0B\u610F\u5916\u9000\u51FA",
|
|
@@ -256,6 +259,9 @@ var init_i18n = __esm({
|
|
|
256
259
|
"notice.daemonRestart": "Daemon restarted \u2014 this turn was interrupted",
|
|
257
260
|
"notice.sessionStartFailed": "Session start failed: {detail}",
|
|
258
261
|
"notice.sendFailed": "Send failed: {detail}",
|
|
262
|
+
"notice.bashMissing": "Git Bash was not found. On Windows this agent runs its commands with Git Bash (C:\\Windows\\System32\\bash.exe is the WSL launcher and won't work). Install Git for Windows: https://git-scm.com/download/win , or set bash.exe under Settings \u2192 Bash path.",
|
|
263
|
+
"notice.autoApproveUpgrade": "New in 1.3: Auto-approve all now really auto-approves for Cursor, OpenCode and dsh too (1.2's bypass mode did nothing for them; they still asked). Your default permission mode was set to it, so to be safe this conversation starts in Always Ask; switch it on in the permission menu below if you want it, or pick the default permission mode again in Settings.",
|
|
264
|
+
"notice.autoApproveMigrated": "This conversation was set to bypass permissions in 1.2, where this agent still asked anyway. From 1.3 Auto-approve all really auto-approves, so it was put back to Always Ask; switch it on in the permission menu below if you want it.",
|
|
259
265
|
"notice.effortNextTurn": "Reasoning effort saved \u2014 takes effect on the next turn",
|
|
260
266
|
"notice.effortResetOnModel": "This model does not support that effort level; reset to Default",
|
|
261
267
|
"turn.agentExit": "Agent process exited unexpectedly",
|
|
@@ -711,12 +717,7 @@ async function scriptPlan(ctx) {
|
|
|
711
717
|
emit({
|
|
712
718
|
type: "plan",
|
|
713
719
|
itemId: "plan1",
|
|
714
|
-
markdown: "## \u5B9E\u65BD\u8BA1\u5212\n\n1. \u6539 `src/index.ts` \u5165\u53E3\n2. \u66F4\u65B0\u6D4B\u8BD5\n3. \u8DD1\u4E00\u904D e2e"
|
|
715
|
-
entries: [
|
|
716
|
-
{ step: "\u6539 src/index.ts \u5165\u53E3", status: "pending" },
|
|
717
|
-
{ step: "\u66F4\u65B0\u6D4B\u8BD5", status: "pending" },
|
|
718
|
-
{ step: "\u8DD1\u4E00\u904D e2e", status: "pending" }
|
|
719
|
-
]
|
|
720
|
+
markdown: "## \u5B9E\u65BD\u8BA1\u5212\n\n1. \u6539 `src/index.ts` \u5165\u53E3\n2. \u66F4\u65B0\u6D4B\u8BD5\n3. \u8DD1\u4E00\u904D e2e"
|
|
720
721
|
});
|
|
721
722
|
const requestId = "perm-" + rid();
|
|
722
723
|
emit({
|
|
@@ -808,6 +809,10 @@ function pickScript(text) {
|
|
|
808
809
|
if (t.startsWith("/long")) return scriptLong;
|
|
809
810
|
if (t.startsWith("/exit")) return scriptExit;
|
|
810
811
|
if (t.startsWith("/serve")) return scriptServe;
|
|
812
|
+
if (t.startsWith("/tools")) return scriptTools;
|
|
813
|
+
if (t.startsWith("/bashhint")) return scriptBashHint;
|
|
814
|
+
if (t.startsWith("/winrun")) return scriptWinRun;
|
|
815
|
+
if (t.startsWith("/tasks")) return scriptTasks;
|
|
811
816
|
return scriptDefault;
|
|
812
817
|
}
|
|
813
818
|
async function sleepAbortable(ms, ctx) {
|
|
@@ -823,6 +828,166 @@ function chunkText(s, n) {
|
|
|
823
828
|
for (let i = 0; i < s.length; i += n) out.push(s.slice(i, i + n));
|
|
824
829
|
return out;
|
|
825
830
|
}
|
|
831
|
+
async function scriptTools(ctx) {
|
|
832
|
+
const { emit } = ctx;
|
|
833
|
+
const sleepA = (ms) => sleepAbortable(ms, ctx);
|
|
834
|
+
const tool = async (id2, kind, title, input, end, ms = 150) => {
|
|
835
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "running", input });
|
|
836
|
+
if (await sleepA(ms)) return true;
|
|
837
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "ok", durationMs: ms, ...end });
|
|
838
|
+
return false;
|
|
839
|
+
};
|
|
840
|
+
emit({ type: "reasoning", itemId: "tr1", text: "\u5148\u770B\u4E00\u4E0B\u6D4B\u8BD5\u548C\u914D\u7F6E\u3002" });
|
|
841
|
+
if (await tool("x1", "read", "\u8BFB\u53D6 package.json", { file_path: "package.json" }, { output: "{\u2026}" })) return;
|
|
842
|
+
if (await tool("x2", "search", '\u641C\u7D22 "describe("', { pattern: "describe(" }, { output: "12 \u5904\u5339\u914D" })) return;
|
|
843
|
+
emit({ type: "reasoning", itemId: "tr2", text: "" });
|
|
844
|
+
if (await tool("x3", "read", "\u8BFB\u53D6 test/app.test.ts", { file_path: "test/app.test.ts" }, { output: "\u2026" })) return;
|
|
845
|
+
if (await tool("x3b", "read", "\u8BFB\u53D6 test/server.test.ts", { file_path: "test/server.test.ts" }, { output: "\u2026" })) return;
|
|
846
|
+
if (await tool("x3c", "search", '\u641C\u7D22 "createServer"', { pattern: "createServer" }, { output: "3 \u5904\u5339\u914D" })) return;
|
|
847
|
+
emit({ type: "tool_call", itemId: "x4", kind: "bash", title: "\u8FD0\u884C npm run lint", status: "running", input: { command: "npm run lint" } });
|
|
848
|
+
if (await sleepA(200)) return;
|
|
849
|
+
emit({
|
|
850
|
+
type: "tool_call",
|
|
851
|
+
itemId: "x4",
|
|
852
|
+
kind: "bash",
|
|
853
|
+
title: "\u8FD0\u884C npm run lint",
|
|
854
|
+
status: "failed",
|
|
855
|
+
exitCode: 1,
|
|
856
|
+
durationMs: 200,
|
|
857
|
+
output: "src/app.ts:12:7 error 'unused' is assigned a value but never used no-unused-vars\n\n\u2716 1 problem (1 error, 0 warnings)"
|
|
858
|
+
});
|
|
859
|
+
const oldStr = "const unused = 1;\nexport const app = 2;";
|
|
860
|
+
const newStr = "export const app = 2;";
|
|
861
|
+
if (await tool(
|
|
862
|
+
"x5",
|
|
863
|
+
"edit",
|
|
864
|
+
"\u7F16\u8F91 src/app.ts",
|
|
865
|
+
{ file_path: "src/app.ts", old_string: oldStr, new_string: newStr },
|
|
866
|
+
{ detail: editDetail({ path: "src/app.ts", old: oldStr, new: newStr }) }
|
|
867
|
+
)) return;
|
|
868
|
+
emit({ type: "reasoning", itemId: "tr3", text: "lint \u4FEE\u597D\u4E86\uFF0C\u518D\u8DD1\u4E00\u904D\u5B8C\u6574\u6D4B\u8BD5\u3002" });
|
|
869
|
+
const cmd = "npm test -- --reporter=verbose --coverage --run test/app.test.ts test/server.test.ts test/client.test.ts";
|
|
870
|
+
emit({ type: "tool_call", itemId: "x6", kind: "bash", title: `\u8FD0\u884C ${cmd}`, status: "running", input: { command: cmd } });
|
|
871
|
+
const slow = Number(process.env.AGENT_CONSOLE_MOCK_TOOLS_MS) || 2500;
|
|
872
|
+
if (await sleepA(slow)) return;
|
|
873
|
+
emit({ type: "tool_call", itemId: "x6", kind: "bash", title: `\u8FD0\u884C ${cmd}`, status: "ok", output: "\u2713 3 \u4E2A\u6D4B\u8BD5\u6587\u4EF6\u901A\u8FC7", exitCode: 0, durationMs: slow });
|
|
874
|
+
const reply = ctx.replyPrefix() + "lint \u62A5\u9519\u5DF2\u4FEE\u590D\uFF0C\u6D4B\u8BD5\u5168\u90E8\u901A\u8FC7\u3002";
|
|
875
|
+
emit({ type: "assistant_message", itemId: "tm1", text: reply });
|
|
876
|
+
}
|
|
877
|
+
async function scriptWinRun(ctx) {
|
|
878
|
+
const { emit } = ctx;
|
|
879
|
+
const sleepA = (ms) => sleepAbortable(ms, ctx);
|
|
880
|
+
const step = Number(process.env.AGENT_CONSOLE_MOCK_STEP_MS) || 250;
|
|
881
|
+
const think = async (id2, text) => {
|
|
882
|
+
for (const ch of chunkText(text, 6)) {
|
|
883
|
+
emit({ type: "reasoning_delta", itemId: id2, text: ch });
|
|
884
|
+
if (await sleepA(40)) return true;
|
|
885
|
+
}
|
|
886
|
+
emit({ type: "reasoning", itemId: id2, text });
|
|
887
|
+
return false;
|
|
888
|
+
};
|
|
889
|
+
const tool = async (id2, kind, title, input, ms = step) => {
|
|
890
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "running", input });
|
|
891
|
+
if (await sleepA(ms)) return true;
|
|
892
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "ok", durationMs: ms, output: "ok" });
|
|
893
|
+
return false;
|
|
894
|
+
};
|
|
895
|
+
const dir = "D:\\workwork\\\u6296\u97F3\u5FEB\u624B\u81EA\u52A8\u4E0A\u4F20";
|
|
896
|
+
const start = `Set-Location "${dir}"; if (Test-Path ".venv\\Scripts\\pythonw.exe") { Start-Process -FilePath ".venv\\Scripts\\pythonw.exe" -ArgumentList "-m","app.main" -WorkingDirectory "${dir}"; Write-Host "\u5DF2\u542F\u52A8\uFF08\u6E90\u7801\u6A21\u5F0F\uFF09" } else { Write-Host "venv \u4E0D\u5B58\u5728\uFF0C\u5C1D\u8BD5 exe"; Start-Process -FilePath "\u98CE\u666F\u89C6\u9891\u5DE5\u5177.exe" -WorkingDirectory "${dir}"; Write-Host "\u5DF2\u542F\u52A8\uFF08exe\uFF09" }`;
|
|
897
|
+
const ps = `Get-Process pythonw -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "*\u6296\u97F3\u5FEB\u624B\u81EA\u52A8\u4E0A\u4F20*" } | Select-Object Id, Path, StartTime | Format-Table -AutoSize`;
|
|
898
|
+
if (await think("w1", "\u5148\u68C0\u67E5\u662F\u5426\u5DF2\u6709\u5B9E\u4F8B\u5728\u8DD1\u3002")) return;
|
|
899
|
+
emit({ type: "assistant_message", itemId: "wm1", text: "\u5148\u68C0\u67E5\u662F\u5426\u5DF2\u6709\u5B9E\u4F8B\u5728\u8DD1\uFF0C\u518D\u542F\u52A8\u5E94\u7528\u3002" });
|
|
900
|
+
if (await tool("s1", "search", "\u641C\u7D22 pythonw", { pattern: "pythonw" })) return;
|
|
901
|
+
if (await tool("s2", "search", "\u641C\u7D22 app.main", { pattern: "app.main" })) return;
|
|
902
|
+
if (await think("w2", "\u770B\u4E0B\u542F\u52A8\u811A\u672C\u3002")) return;
|
|
903
|
+
if (await tool("s3", "search", "\u641C\u7D22 \u542F\u52A8.bat", { pattern: "\u542F\u52A8.bat" })) return;
|
|
904
|
+
if (await tool("r1", "read", "\u8BFB\u53D6 \u542F\u52A8.bat", { file_path: `${dir}\\\u542F\u52A8.bat` })) return;
|
|
905
|
+
if (await think("w3", "")) return;
|
|
906
|
+
if (await tool("b0", "bash", `\u8FD0\u884C ${ps}`, { command: ps }, step * 2)) return;
|
|
907
|
+
if (await think("w4", "\u6CA1\u6709\u5B9E\u4F8B\u5728\u8DD1\uFF0C\u76F4\u63A5\u542F\u52A8\u3002")) return;
|
|
908
|
+
emit({ type: "tool_call", itemId: "b1", kind: "bash", title: `\u8FD0\u884C ${start}`, status: "running", input: { command: start } });
|
|
909
|
+
if (await sleepA(step)) return;
|
|
910
|
+
const requestId = "perm-" + rid();
|
|
911
|
+
emit({
|
|
912
|
+
type: "permission_request",
|
|
913
|
+
requestId,
|
|
914
|
+
title: start,
|
|
915
|
+
kind: "tool",
|
|
916
|
+
detail: { command: start, cwd: dir },
|
|
917
|
+
options: [
|
|
918
|
+
{ id: "allow-once", label: "Allow once", kind: "allow_once" },
|
|
919
|
+
{ id: "allow-always", label: "Allow always", kind: "allow_always" },
|
|
920
|
+
{ id: "reject-once", label: "Reject", kind: "reject_once" }
|
|
921
|
+
]
|
|
922
|
+
});
|
|
923
|
+
const ans = await ctx.waitFor(requestId);
|
|
924
|
+
if (ans.cancelled || ans.optionId === "reject-once") {
|
|
925
|
+
emit({ type: "tool_call", itemId: "b1", kind: "bash", title: `\u8FD0\u884C ${start}`, status: "failed", output: "Rejected", exitCode: 1 });
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
emit({ type: "tool_call", itemId: "b1", kind: "bash", title: `\u8FD0\u884C ${start}`, status: "ok", output: "\u5DF2\u542F\u52A8\uFF08\u6E90\u7801\u6A21\u5F0F\uFF09", exitCode: 0, durationMs: step });
|
|
929
|
+
if (await think("w5", "\u6539\u4E09\u4E2A\u914D\u7F6E\u3002")) return;
|
|
930
|
+
for (const n of [1, 2, 3]) if (await tool(`e${n}`, "edit", `\u7F16\u8F91 config/${n}.json`, { file_path: `${dir}\\config\\${n}.json` })) return;
|
|
931
|
+
if (await think("w6", "\u786E\u8BA4\u8FDB\u7A0B\u8D77\u6765\u4E86\u3002")) return;
|
|
932
|
+
if (await tool("r2", "read", "\u8BFB\u53D6 logs/selftest.txt", { file_path: `${dir}\\logs\\selftest.txt` })) return;
|
|
933
|
+
if (await tool("b2", "bash", `\u8FD0\u884C ${ps}`, { command: ps }, Number(process.env.AGENT_CONSOLE_MOCK_TOOLS_MS) || step * 6)) return;
|
|
934
|
+
emit({ type: "assistant_message", itemId: "wm2", text: ctx.replyPrefix() + "\u5DF2\u542F\u52A8\uFF0C\u8FDB\u7A0B\u5728\u8DD1\u3002" });
|
|
935
|
+
}
|
|
936
|
+
async function scriptTasks(ctx) {
|
|
937
|
+
const { emit } = ctx;
|
|
938
|
+
const sleepA = (ms) => sleepAbortable(ms, ctx);
|
|
939
|
+
const steps = ["\u590D\u73B0\u5076\u53D1\u5931\u8D25", "\u627E\u5230\u7ADE\u6001", "\u4FEE\u590D\u5E76\u52A0\u56FA\u6D4B\u8BD5"];
|
|
940
|
+
const plan = (done, running) => emit({
|
|
941
|
+
type: "plan",
|
|
942
|
+
itemId: "tk-plan",
|
|
943
|
+
markdown: steps.map((s) => `- ${s}`).join("\n"),
|
|
944
|
+
entries: steps.map((step, i) => ({ step, status: i < done ? "completed" : i === done && running ? "in_progress" : "pending" }))
|
|
945
|
+
});
|
|
946
|
+
const tool = async (id2, kind, title, input, end = {}, ms = 120) => {
|
|
947
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "running", input });
|
|
948
|
+
if (await sleepA(ms)) return true;
|
|
949
|
+
emit({ type: "tool_call", itemId: id2, kind, title, status: "ok", durationMs: ms, output: "ok", ...end });
|
|
950
|
+
return false;
|
|
951
|
+
};
|
|
952
|
+
plan(0, true);
|
|
953
|
+
if (await tool("k1", "bash", "\u8FD0\u884C npx vitest run src/checkout --repeat 50", { command: "npx vitest run src/checkout --repeat 50" })) return;
|
|
954
|
+
if (await tool("k2", "read", "\u8BFB\u53D6 src/checkout/useCheckout.ts", { file_path: "src/checkout/useCheckout.ts" })) return;
|
|
955
|
+
plan(1, true);
|
|
956
|
+
emit({ type: "reasoning", itemId: "k-th", text: "submit() \u6CA1\u6709\u7B49 confirmOrder\u3002" });
|
|
957
|
+
const oldStr = ' setStatus("pending");\n void confirmOrder(order.id).then(setStatus);';
|
|
958
|
+
const newStr = ' setStatus("pending");\n try {\n setStatus(await confirmOrder(order.id));\n } catch (error) {\n setStatus("failed");\n throw error;\n }';
|
|
959
|
+
if (await tool(
|
|
960
|
+
"k3",
|
|
961
|
+
"edit",
|
|
962
|
+
"\u7F16\u8F91 src/checkout/useCheckout.ts",
|
|
963
|
+
{ file_path: "src/checkout/useCheckout.ts", old_string: oldStr, new_string: newStr },
|
|
964
|
+
{ detail: editDetail({ path: "src/checkout/useCheckout.ts", old: oldStr, new: newStr }) }
|
|
965
|
+
)) return;
|
|
966
|
+
plan(2, true);
|
|
967
|
+
if (await tool("k4", "search", '\u641C\u7D22 "confirmOrder"', { pattern: "confirmOrder" })) return;
|
|
968
|
+
const hold = Number(process.env.AGENT_CONSOLE_MOCK_TASKS_HOLD_MS) || 0;
|
|
969
|
+
if (await tool("k5", "bash", "\u8FD0\u884C npx tsc --noEmit", { command: "npx tsc --noEmit" }, {}, Math.max(120, hold))) return;
|
|
970
|
+
plan(3, false);
|
|
971
|
+
const reply = ctx.replyPrefix() + [
|
|
972
|
+
"**\u6839\u56E0\uFF1A** `submit()` \u4E22\u6389\u4E86\u786E\u8BA4\u8BF7\u6C42\u7684 Promise\uFF0C\u8BA2\u5355\u786E\u8BA4\u524D\u5C31\u8FD4\u56DE\u4E86\u3002",
|
|
973
|
+
"",
|
|
974
|
+
"```ts",
|
|
975
|
+
"async function submit() {",
|
|
976
|
+
' setStatus("pending");',
|
|
977
|
+
" const order = await createOrder(cart);",
|
|
978
|
+
" setStatus(await confirmOrder(order.id));",
|
|
979
|
+
"}",
|
|
980
|
+
"```",
|
|
981
|
+
"",
|
|
982
|
+
"\u91CD\u590D\u8DD1 50 \u6B21\u90FD\u901A\u8FC7\u4E86\u3002"
|
|
983
|
+
].join("\n");
|
|
984
|
+
emit({ type: "assistant_message", itemId: "k-msg", text: reply });
|
|
985
|
+
}
|
|
986
|
+
async function scriptBashHint(ctx) {
|
|
987
|
+
ctx.emit({ type: "notice", level: "warn", code: "bash_missing", message: tr("notice.bashMissing") });
|
|
988
|
+
await sleepAbortable(100, ctx);
|
|
989
|
+
ctx.emit({ type: "assistant_message", itemId: "bh1", text: ctx.replyPrefix() + "\uFF08\u6A21\u62DF\uFF09" });
|
|
990
|
+
}
|
|
826
991
|
async function scriptServe(ctx) {
|
|
827
992
|
const emit = (e) => ctx.emit(e);
|
|
828
993
|
const reply = [
|
|
@@ -871,6 +1036,8 @@ function createMockProvider() {
|
|
|
871
1036
|
name: "Mock\uFF08\u6F14\u793A\uFF09",
|
|
872
1037
|
capabilities: mockCapabilities,
|
|
873
1038
|
async detect() {
|
|
1039
|
+
const d = Number(process.env.AGENT_CONSOLE_MOCK_DETECT_MS) || 0;
|
|
1040
|
+
if (d > 0) await sleep(d);
|
|
874
1041
|
return { available: true, bin: "mock", version: "demo" };
|
|
875
1042
|
},
|
|
876
1043
|
async listModels() {
|
|
@@ -881,6 +1048,8 @@ function createMockProvider() {
|
|
|
881
1048
|
},
|
|
882
1049
|
async createSession(o) {
|
|
883
1050
|
mockProviderMetrics.createSessionCalls++;
|
|
1051
|
+
const d = Number(process.env.AGENT_CONSOLE_MOCK_START_MS) || 0;
|
|
1052
|
+
if (d > 0) await sleep(d);
|
|
884
1053
|
return new MockSession(o);
|
|
885
1054
|
}
|
|
886
1055
|
};
|
|
@@ -1141,11 +1310,251 @@ var init_pathscan = __esm({
|
|
|
1141
1310
|
}
|
|
1142
1311
|
});
|
|
1143
1312
|
|
|
1313
|
+
// daemon/src/util/gitBash.ts
|
|
1314
|
+
import path2 from "node:path";
|
|
1315
|
+
import { statSync as statSync3 } from "node:fs";
|
|
1316
|
+
import { spawn } from "node:child_process";
|
|
1317
|
+
function envGet2(env, name) {
|
|
1318
|
+
if (env[name] !== void 0) return env[name];
|
|
1319
|
+
const lower = name.toLowerCase();
|
|
1320
|
+
for (const k of Object.keys(env)) if (k.toLowerCase() === lower) return env[k];
|
|
1321
|
+
return void 0;
|
|
1322
|
+
}
|
|
1323
|
+
function syncIsFile(p) {
|
|
1324
|
+
try {
|
|
1325
|
+
return statSync3(p).isFile();
|
|
1326
|
+
} catch {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function withTimeout(p, ms) {
|
|
1331
|
+
return new Promise((resolve4) => {
|
|
1332
|
+
const t = setTimeout(() => resolve4(false), ms);
|
|
1333
|
+
t.unref?.();
|
|
1334
|
+
p.then((v) => {
|
|
1335
|
+
clearTimeout(t);
|
|
1336
|
+
resolve4(v);
|
|
1337
|
+
}, () => {
|
|
1338
|
+
clearTimeout(t);
|
|
1339
|
+
resolve4(false);
|
|
1340
|
+
});
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
function isWslBash(p, env = {}) {
|
|
1344
|
+
const n = norm(p);
|
|
1345
|
+
if (/\\windowsapps\\/.test(n + "\\")) return true;
|
|
1346
|
+
if (/^[a-z]:\\windows\\(system32|syswow64|sysnative)\\/.test(n)) return true;
|
|
1347
|
+
const root = envGet2(env, "SystemRoot") || envGet2(env, "windir");
|
|
1348
|
+
if (root) {
|
|
1349
|
+
const r = norm(root);
|
|
1350
|
+
for (const sub of ["system32", "syswow64", "sysnative"]) if (n.startsWith(`${r}\\${sub}\\`)) return true;
|
|
1351
|
+
}
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
function cleanPath(raw) {
|
|
1355
|
+
return raw.trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
|
|
1356
|
+
}
|
|
1357
|
+
function checkBashPath(raw, deps = {}) {
|
|
1358
|
+
const p = cleanPath(String(raw ?? ""));
|
|
1359
|
+
if (!p) return { ok: false, reason: "empty" };
|
|
1360
|
+
if (!/^[A-Za-z]:[\\/]/.test(p)) return { ok: false, reason: "relative" };
|
|
1361
|
+
const base = W.basename(p).toLowerCase();
|
|
1362
|
+
if (base !== "bash.exe" && base !== "sh.exe") return { ok: false, reason: "notBash" };
|
|
1363
|
+
const env = deps.env ?? process.env;
|
|
1364
|
+
if (isWslBash(p, env)) return { ok: false, reason: "wsl" };
|
|
1365
|
+
const full = W.normalize(p);
|
|
1366
|
+
if (!(deps.isFile ?? syncIsFile)(full)) return { ok: false, reason: "missing" };
|
|
1367
|
+
return { ok: true, path: full };
|
|
1368
|
+
}
|
|
1369
|
+
function bashForGitDir(dir) {
|
|
1370
|
+
const d = W.normalize(dir).replace(/[\\/]+$/, "");
|
|
1371
|
+
const base = W.basename(d).toLowerCase();
|
|
1372
|
+
const parent = W.dirname(d);
|
|
1373
|
+
const out = [];
|
|
1374
|
+
if (base === "bin" || base === "cmd") {
|
|
1375
|
+
const pb = W.basename(parent).toLowerCase();
|
|
1376
|
+
if (["mingw64", "mingw32", "clangarm64", "ucrt64", "usr"].includes(pb)) out.push(W.join(W.dirname(parent), "bin", "bash.exe"));
|
|
1377
|
+
out.push(W.join(parent, "bin", "bash.exe"));
|
|
1378
|
+
}
|
|
1379
|
+
out.push(W.join(d, "bash.exe"));
|
|
1380
|
+
return [...new Set(out)];
|
|
1381
|
+
}
|
|
1382
|
+
function knownBashCandidates(env, home) {
|
|
1383
|
+
const out = [];
|
|
1384
|
+
for (const v of ["ProgramFiles", "ProgramW6432", "ProgramFiles(x86)"]) {
|
|
1385
|
+
const d = envGet2(env, v);
|
|
1386
|
+
if (d) out.push(W.join(d, "Git", "bin", "bash.exe"));
|
|
1387
|
+
}
|
|
1388
|
+
out.push("C:\\Program Files\\Git\\bin\\bash.exe", "C:\\Program Files (x86)\\Git\\bin\\bash.exe");
|
|
1389
|
+
const local = envGet2(env, "LOCALAPPDATA");
|
|
1390
|
+
if (local) out.push(W.join(local, "Programs", "Git", "bin", "bash.exe"));
|
|
1391
|
+
const scoop = envGet2(env, "SCOOP") || (home ? W.join(home, "scoop") : "");
|
|
1392
|
+
if (scoop) out.push(W.join(scoop, "apps", "git", "current", "bin", "bash.exe"));
|
|
1393
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1394
|
+
return out.filter((p) => seen.has(norm(p)) ? false : (seen.add(norm(p)), true));
|
|
1395
|
+
}
|
|
1396
|
+
function driveBashCandidates(drives) {
|
|
1397
|
+
const out = [];
|
|
1398
|
+
for (const raw of drives) {
|
|
1399
|
+
const L = raw.replace(/[:\\/]/g, "").toUpperCase();
|
|
1400
|
+
if (!/^[A-Z]$/.test(L)) continue;
|
|
1401
|
+
out.push(`${L}:\\Git\\bin\\bash.exe`, `${L}:\\Program Files\\Git\\bin\\bash.exe`);
|
|
1402
|
+
}
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
async function findGitBash(manual, deps = {}) {
|
|
1406
|
+
const env = deps.env ?? process.env;
|
|
1407
|
+
const isFile = deps.isFile ?? syncIsFile;
|
|
1408
|
+
const ok = (p) => !isWslBash(p, env) && isFile(p);
|
|
1409
|
+
let manualError;
|
|
1410
|
+
if (manual && cleanPath(manual)) {
|
|
1411
|
+
const c = checkBashPath(manual, { ...deps, env });
|
|
1412
|
+
if (c.ok) return { found: { path: c.path, source: "manual" } };
|
|
1413
|
+
manualError = c;
|
|
1414
|
+
}
|
|
1415
|
+
for (const v of ["CLAUDE_CODE_GIT_BASH_PATH", "OPENCODE_GIT_BASH_PATH"]) {
|
|
1416
|
+
const c = checkBashPath(envGet2(env, v), { ...deps, env });
|
|
1417
|
+
if (c.ok) return { found: { path: c.path, source: "env" }, manualError };
|
|
1418
|
+
}
|
|
1419
|
+
for (const git of findOnPath("git.exe", { platform: "win32", env, extra: false, exists: isFile })) {
|
|
1420
|
+
for (const b of bashForGitDir(W.dirname(git))) if (ok(b)) return { found: { path: b, source: "git" }, manualError };
|
|
1421
|
+
}
|
|
1422
|
+
for (const b of knownBashCandidates(env, deps.home)) if (ok(b)) return { found: { path: b, source: "known" }, manualError };
|
|
1423
|
+
const drives = driveBashCandidates(deps.drives ?? ALL_DRIVES);
|
|
1424
|
+
const ms = deps.probeTimeoutMs ?? 1500;
|
|
1425
|
+
const list = drives.filter((p) => !isWslBash(p, env));
|
|
1426
|
+
const hit = deps.isFileAsync ? await Promise.all(list.map((p) => withTimeout(deps.isFileAsync(p), ms))).then((h) => list[h.indexOf(true)] ?? null) : await probeInChild(list, ms);
|
|
1427
|
+
if (hit) return { found: { path: hit, source: "drive" }, manualError };
|
|
1428
|
+
for (const b of findOnPath("bash.exe", { platform: "win32", env, extra: false, exists: isFile })) {
|
|
1429
|
+
if (!isWslBash(b, env)) return { found: { path: b, source: "path" }, manualError };
|
|
1430
|
+
}
|
|
1431
|
+
return { found: null, manualError };
|
|
1432
|
+
}
|
|
1433
|
+
function probeInChild(paths, ms) {
|
|
1434
|
+
if (!paths.length) return Promise.resolve(null);
|
|
1435
|
+
const script = "const fs=require('fs');const p=JSON.parse(process.argv[1]);p.forEach((x,i)=>fs.stat(x,(e,s)=>{if(!e&&s.isFile())process.stdout.write(i+'\\n')}));";
|
|
1436
|
+
return new Promise((resolve4) => {
|
|
1437
|
+
const hits = /* @__PURE__ */ new Set();
|
|
1438
|
+
let out = "";
|
|
1439
|
+
let done = false;
|
|
1440
|
+
const finish = () => {
|
|
1441
|
+
if (done) return;
|
|
1442
|
+
done = true;
|
|
1443
|
+
clearTimeout(timer);
|
|
1444
|
+
try {
|
|
1445
|
+
child.kill();
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
const first = [...hits].sort((a, b) => a - b)[0];
|
|
1449
|
+
resolve4(first === void 0 ? null : paths[first] ?? null);
|
|
1450
|
+
};
|
|
1451
|
+
const child = spawn(process.execPath, ["-e", script, JSON.stringify(paths)], { stdio: ["ignore", "pipe", "ignore"], windowsHide: true });
|
|
1452
|
+
const timer = setTimeout(finish, ms);
|
|
1453
|
+
child.stdout?.setEncoding("utf8");
|
|
1454
|
+
child.stdout?.on("data", (d) => {
|
|
1455
|
+
out += d;
|
|
1456
|
+
for (const line of out.split("\n").slice(0, -1)) if (/^\d+$/.test(line)) hits.add(Number(line));
|
|
1457
|
+
out = out.slice(out.lastIndexOf("\n") + 1);
|
|
1458
|
+
if (hits.has(0)) finish();
|
|
1459
|
+
});
|
|
1460
|
+
child.on("error", finish);
|
|
1461
|
+
child.on("close", finish);
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function bashPathDir(bash, isFile = syncIsFile) {
|
|
1465
|
+
const dir = W.dirname(W.normalize(bash));
|
|
1466
|
+
if (/\\usr\\bin$/i.test(dir)) {
|
|
1467
|
+
const top = W.join(W.dirname(W.dirname(dir)), "bin");
|
|
1468
|
+
if (isFile(W.join(top, "bash.exe"))) return top;
|
|
1469
|
+
}
|
|
1470
|
+
return dir;
|
|
1471
|
+
}
|
|
1472
|
+
function bashEnv(found, env, isFile = syncIsFile) {
|
|
1473
|
+
if (!found) return {};
|
|
1474
|
+
const out = {
|
|
1475
|
+
CLAUDE_CODE_GIT_BASH_PATH: found.path,
|
|
1476
|
+
OPENCODE_GIT_BASH_PATH: found.path
|
|
1477
|
+
};
|
|
1478
|
+
const dir = bashPathDir(found.path, isFile);
|
|
1479
|
+
const first = findOnPath("bash.exe", { platform: "win32", env, extra: false, exists: isFile })[0];
|
|
1480
|
+
if (!first || norm(first) !== norm(W.join(dir, "bash.exe"))) {
|
|
1481
|
+
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
1482
|
+
const rest = String(env[key] ?? "").split(";").filter((d) => d && norm(d.replace(/^"(.*)"$/, "$1")) !== norm(dir));
|
|
1483
|
+
out[key] = [dir, ...rest].join(";");
|
|
1484
|
+
}
|
|
1485
|
+
return out;
|
|
1486
|
+
}
|
|
1487
|
+
var W, GIT_FOR_WINDOWS_URL, norm, ALL_DRIVES;
|
|
1488
|
+
var init_gitBash = __esm({
|
|
1489
|
+
"daemon/src/util/gitBash.ts"() {
|
|
1490
|
+
init_pathscan();
|
|
1491
|
+
W = path2.win32;
|
|
1492
|
+
GIT_FOR_WINDOWS_URL = "https://git-scm.com/download/win";
|
|
1493
|
+
norm = (p) => W.normalize(p).replace(/[\\/]+$/, "").toLowerCase();
|
|
1494
|
+
ALL_DRIVES = "CDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
// daemon/src/util/agentShell.ts
|
|
1499
|
+
import { homedir as homedir3 } from "node:os";
|
|
1500
|
+
function refreshAgentShell(manual, o = {}) {
|
|
1501
|
+
const platform = o.platform ?? process.platform;
|
|
1502
|
+
const my = ++generation;
|
|
1503
|
+
const m = typeof manual === "string" ? manual : "";
|
|
1504
|
+
if (platform !== "win32") {
|
|
1505
|
+
status = { needed: false, found: null, manual: m };
|
|
1506
|
+
ready = Promise.resolve();
|
|
1507
|
+
return ready;
|
|
1508
|
+
}
|
|
1509
|
+
const env = o.deps?.env ?? process.env;
|
|
1510
|
+
ready = findGitBash(m, { home: homedir3(), ...o.deps, env }).then(({ found, manualError }) => {
|
|
1511
|
+
if (my !== generation) return;
|
|
1512
|
+
status = { needed: true, found, manual: m, ...manualError && !manualError.ok ? { manualError: manualError.reason } : {} };
|
|
1513
|
+
isFileDep = o.deps?.isFile;
|
|
1514
|
+
if (found) log("info", `Git Bash: ${found.path} (${found.source})`);
|
|
1515
|
+
else log("warn", "Git Bash not found; agents that need bash (Claude Code, Pi, dsh) will show a hint");
|
|
1516
|
+
}).catch((e) => {
|
|
1517
|
+
log("warn", "Git Bash detection failed", { err: String(e) });
|
|
1518
|
+
});
|
|
1519
|
+
return ready;
|
|
1520
|
+
}
|
|
1521
|
+
function agentShellReady(ms = 3e3) {
|
|
1522
|
+
return Promise.race([ready, new Promise((r) => setTimeout(r, ms).unref?.())]);
|
|
1523
|
+
}
|
|
1524
|
+
function agentShellStatus() {
|
|
1525
|
+
return status;
|
|
1526
|
+
}
|
|
1527
|
+
function bashMissingFor(providerId) {
|
|
1528
|
+
return status.needed && !status.found && BASH_AGENTS.has(providerId);
|
|
1529
|
+
}
|
|
1530
|
+
function withAgentShell(env) {
|
|
1531
|
+
if (!status.needed || !status.found) return env;
|
|
1532
|
+
const overrides = bashEnv(status.found, env, isFileDep);
|
|
1533
|
+
const out = { ...env };
|
|
1534
|
+
for (const [k, v] of Object.entries(overrides)) {
|
|
1535
|
+
const same = Object.keys(out).find((x) => x.toUpperCase() === k.toUpperCase() && x !== k);
|
|
1536
|
+
if (same && k.toUpperCase() === "PATH") delete out[same];
|
|
1537
|
+
out[k] = v;
|
|
1538
|
+
}
|
|
1539
|
+
return out;
|
|
1540
|
+
}
|
|
1541
|
+
var status, isFileDep, ready, generation, BASH_AGENTS;
|
|
1542
|
+
var init_agentShell = __esm({
|
|
1543
|
+
"daemon/src/util/agentShell.ts"() {
|
|
1544
|
+
init_gitBash();
|
|
1545
|
+
init_log();
|
|
1546
|
+
status = { needed: process.platform === "win32", found: null, manual: "" };
|
|
1547
|
+
ready = Promise.resolve();
|
|
1548
|
+
generation = 0;
|
|
1549
|
+
BASH_AGENTS = /* @__PURE__ */ new Set(["claude", "pi", "dsh"]);
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1144
1553
|
// daemon/src/util/bin.ts
|
|
1145
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
1554
|
+
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
1146
1555
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
1147
1556
|
import { existsSync as existsSync4 } from "node:fs";
|
|
1148
|
-
import
|
|
1557
|
+
import path3 from "node:path";
|
|
1149
1558
|
function which(name) {
|
|
1150
1559
|
return findOnPath(name);
|
|
1151
1560
|
}
|
|
@@ -1191,9 +1600,11 @@ function trackTree(child) {
|
|
|
1191
1600
|
function spawnBin(r, args, opts = {}) {
|
|
1192
1601
|
const a = argvFor(r, args);
|
|
1193
1602
|
const { group, ...rest } = opts;
|
|
1194
|
-
const child =
|
|
1603
|
+
const child = spawn2(a.cmd, a.args, {
|
|
1195
1604
|
windowsHide: true,
|
|
1196
1605
|
...rest,
|
|
1606
|
+
// Windows: Git Bash for the agent (CLAUDE_CODE_GIT_BASH_PATH, OPENCODE_GIT_BASH_PATH, PATH); unchanged elsewhere
|
|
1607
|
+
env: withAgentShell(rest.env ?? process.env),
|
|
1197
1608
|
...a.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
|
|
1198
1609
|
...group && !IS_WIN ? { detached: true } : {}
|
|
1199
1610
|
});
|
|
@@ -1202,7 +1613,7 @@ function spawnBin(r, args, opts = {}) {
|
|
|
1202
1613
|
}
|
|
1203
1614
|
function shimTarget(cmdText, cmdDir, deps = {}) {
|
|
1204
1615
|
const exists = deps.exists ?? existsSync4;
|
|
1205
|
-
const P = deps.win ?? IS_WIN ?
|
|
1616
|
+
const P = deps.win ?? IS_WIN ? path3.win32 : path3;
|
|
1206
1617
|
const cands = [];
|
|
1207
1618
|
const re = /"([^"]+?\.(?:js|mjs|cjs|exe|cmd|bat))"/gi;
|
|
1208
1619
|
let m;
|
|
@@ -1253,7 +1664,7 @@ function resolveBinUncached(name) {
|
|
|
1253
1664
|
async function resolveBinWith(name, deps = {}) {
|
|
1254
1665
|
const platform = deps.platform ?? process.platform;
|
|
1255
1666
|
const win = platform === "win32";
|
|
1256
|
-
const P = win ?
|
|
1667
|
+
const P = win ? path3.win32 : path3.posix;
|
|
1257
1668
|
const exists = deps.exists ?? existsSync4;
|
|
1258
1669
|
const hits = (deps.find ?? which)(name);
|
|
1259
1670
|
if (!hits.length) return { ok: false, reason: tr("bin.notOnPath", { name }) };
|
|
@@ -1294,7 +1705,7 @@ function killTree(child, { graceMs = 3e3 } = {}) {
|
|
|
1294
1705
|
const pid = child.pid;
|
|
1295
1706
|
if (IS_WIN) {
|
|
1296
1707
|
return new Promise((resolvePromise) => {
|
|
1297
|
-
const tk =
|
|
1708
|
+
const tk = spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
1298
1709
|
tk.on("error", () => {
|
|
1299
1710
|
try {
|
|
1300
1711
|
child.kill();
|
|
@@ -1349,6 +1760,7 @@ var init_bin = __esm({
|
|
|
1349
1760
|
"daemon/src/util/bin.ts"() {
|
|
1350
1761
|
init_pathscan();
|
|
1351
1762
|
init_i18n();
|
|
1763
|
+
init_agentShell();
|
|
1352
1764
|
IS_WIN = process.platform === "win32";
|
|
1353
1765
|
cache = /* @__PURE__ */ new Map();
|
|
1354
1766
|
CACHE_MS = 60 * 1e3;
|
|
@@ -1365,9 +1777,9 @@ function relPath(p, cwd) {
|
|
|
1365
1777
|
if (!p) return "";
|
|
1366
1778
|
const s = String(p);
|
|
1367
1779
|
if (!cwd) return s;
|
|
1368
|
-
const
|
|
1369
|
-
const a =
|
|
1370
|
-
const b =
|
|
1780
|
+
const norm2 = (x) => x.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1781
|
+
const a = norm2(s);
|
|
1782
|
+
const b = norm2(cwd);
|
|
1371
1783
|
if (a.toLowerCase().startsWith(b.toLowerCase() + "/")) return a.slice(b.length + 1);
|
|
1372
1784
|
if (a.toLowerCase() === b.toLowerCase()) return ".";
|
|
1373
1785
|
return s;
|
|
@@ -1780,6 +2192,162 @@ var init_launch = __esm({
|
|
|
1780
2192
|
}
|
|
1781
2193
|
});
|
|
1782
2194
|
|
|
2195
|
+
// daemon/src/util/decode.ts
|
|
2196
|
+
import { spawnSync } from "node:child_process";
|
|
2197
|
+
function codePageLabel(cp3) {
|
|
2198
|
+
const label = CP[cp3];
|
|
2199
|
+
if (!label) return null;
|
|
2200
|
+
try {
|
|
2201
|
+
new TextDecoder(label);
|
|
2202
|
+
return label;
|
|
2203
|
+
} catch {
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function labelForLocale(locale2) {
|
|
2208
|
+
const l = locale2.toLowerCase();
|
|
2209
|
+
if (/^zh[-_](tw|hk|mo|hant)/.test(l)) return codePageLabel(950);
|
|
2210
|
+
if (l.startsWith("zh")) return codePageLabel(936);
|
|
2211
|
+
if (l.startsWith("ja")) return codePageLabel(932);
|
|
2212
|
+
if (l.startsWith("ko")) return codePageLabel(949);
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2215
|
+
function parseCodePage(out) {
|
|
2216
|
+
const m = /ACP\s+REG_SZ\s+(\d{3,5})/i.exec(out) ?? /:\s*(\d{3,5})\s*$/m.exec(out.trim());
|
|
2217
|
+
return m ? Number(m[1]) : null;
|
|
2218
|
+
}
|
|
2219
|
+
function systemLegacyEncoding(platform = process.platform) {
|
|
2220
|
+
if (systemLabel !== void 0) return systemLabel;
|
|
2221
|
+
if (platform !== "win32") return null;
|
|
2222
|
+
let cp3 = null;
|
|
2223
|
+
try {
|
|
2224
|
+
const r = spawnSync("reg", ["query", "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "/v", "ACP"], { encoding: "latin1", timeout: 2e3, windowsHide: true });
|
|
2225
|
+
cp3 = parseCodePage(String(r.stdout ?? ""));
|
|
2226
|
+
} catch {
|
|
2227
|
+
}
|
|
2228
|
+
if (cp3 === 65001) return systemLabel = null;
|
|
2229
|
+
systemLabel = cp3 && codePageLabel(cp3) || labelForLocale(Intl.DateTimeFormat().resolvedOptions().locale);
|
|
2230
|
+
return systemLabel;
|
|
2231
|
+
}
|
|
2232
|
+
function utf8Tail(b) {
|
|
2233
|
+
for (let k = 1; k <= Math.min(3, b.length); k++) {
|
|
2234
|
+
const c = b[b.length - k];
|
|
2235
|
+
if ((c & 192) === 128) continue;
|
|
2236
|
+
const need = c >= 240 ? 4 : c >= 224 ? 3 : c >= 192 ? 2 : 1;
|
|
2237
|
+
return need > k ? k : 0;
|
|
2238
|
+
}
|
|
2239
|
+
return 0;
|
|
2240
|
+
}
|
|
2241
|
+
function legacyTail(b, label) {
|
|
2242
|
+
const dbcs = label === "gbk" || label === "gb18030" || label === "big5" || label === "euc-kr" || label === "shift_jis";
|
|
2243
|
+
if (!dbcs) return 0;
|
|
2244
|
+
const gb = label === "gbk" || label === "gb18030";
|
|
2245
|
+
let i = 0;
|
|
2246
|
+
while (i < b.length) {
|
|
2247
|
+
const c = b[i];
|
|
2248
|
+
let n = 1;
|
|
2249
|
+
if (label === "shift_jis") n = c >= 129 && c <= 159 || c >= 224 && c <= 252 ? 2 : 1;
|
|
2250
|
+
else if (c >= 129 && c <= 254) n = gb && i + 1 < b.length && b[i + 1] >= 48 && b[i + 1] <= 57 ? 4 : 2;
|
|
2251
|
+
if (i + n > b.length) return b.length - i;
|
|
2252
|
+
i += n;
|
|
2253
|
+
}
|
|
2254
|
+
return 0;
|
|
2255
|
+
}
|
|
2256
|
+
function hasLongUtf8(b) {
|
|
2257
|
+
for (const c of b) if (c >= 224) return true;
|
|
2258
|
+
return false;
|
|
2259
|
+
}
|
|
2260
|
+
function isAscii(b) {
|
|
2261
|
+
for (const c of b) if (c >= 128) return false;
|
|
2262
|
+
return true;
|
|
2263
|
+
}
|
|
2264
|
+
function decodeOutput(buf, legacy) {
|
|
2265
|
+
const d = new OutputDecoder(legacy === void 0 ? systemLegacyEncoding() : legacy);
|
|
2266
|
+
return d.write(buf) + d.end();
|
|
2267
|
+
}
|
|
2268
|
+
var CP, systemLabel, OutputDecoder;
|
|
2269
|
+
var init_decode = __esm({
|
|
2270
|
+
"daemon/src/util/decode.ts"() {
|
|
2271
|
+
CP = {
|
|
2272
|
+
936: "gbk",
|
|
2273
|
+
54936: "gb18030",
|
|
2274
|
+
950: "big5",
|
|
2275
|
+
932: "shift_jis",
|
|
2276
|
+
949: "euc-kr",
|
|
2277
|
+
866: "ibm866",
|
|
2278
|
+
874: "windows-874",
|
|
2279
|
+
1250: "windows-1250",
|
|
2280
|
+
1251: "windows-1251",
|
|
2281
|
+
1252: "windows-1252",
|
|
2282
|
+
1253: "windows-1253",
|
|
2283
|
+
1254: "windows-1254",
|
|
2284
|
+
1255: "windows-1255",
|
|
2285
|
+
1256: "windows-1256",
|
|
2286
|
+
1257: "windows-1257",
|
|
2287
|
+
1258: "windows-1258"
|
|
2288
|
+
// 437/850 (OEM, Western) have no WHATWG decoder; output there is almost always ASCII or already UTF-8
|
|
2289
|
+
};
|
|
2290
|
+
OutputDecoder = class {
|
|
2291
|
+
pending = new Uint8Array(0);
|
|
2292
|
+
legacyLast = false;
|
|
2293
|
+
utf8 = new TextDecoder("utf-8", { fatal: true });
|
|
2294
|
+
lenient = new TextDecoder("utf-8");
|
|
2295
|
+
legacy;
|
|
2296
|
+
/** legacy: encoding label for non-UTF-8 chunks; default = the system code page on Windows, none elsewhere */
|
|
2297
|
+
constructor(legacy = systemLegacyEncoding()) {
|
|
2298
|
+
let d = null;
|
|
2299
|
+
if (legacy) try {
|
|
2300
|
+
d = new TextDecoder(legacy);
|
|
2301
|
+
} catch {
|
|
2302
|
+
}
|
|
2303
|
+
this.legacy = d;
|
|
2304
|
+
}
|
|
2305
|
+
write(chunk) {
|
|
2306
|
+
if (typeof chunk === "string") return chunk;
|
|
2307
|
+
let b = chunk;
|
|
2308
|
+
if (this.pending.length) {
|
|
2309
|
+
const m = new Uint8Array(this.pending.length + chunk.length);
|
|
2310
|
+
m.set(this.pending);
|
|
2311
|
+
m.set(chunk, this.pending.length);
|
|
2312
|
+
b = m;
|
|
2313
|
+
this.pending = new Uint8Array(0);
|
|
2314
|
+
}
|
|
2315
|
+
if (!b.length) return "";
|
|
2316
|
+
const ut = utf8Tail(b);
|
|
2317
|
+
const head = b.subarray(0, b.length - ut);
|
|
2318
|
+
let text = null;
|
|
2319
|
+
try {
|
|
2320
|
+
text = this.utf8.decode(head);
|
|
2321
|
+
} catch {
|
|
2322
|
+
}
|
|
2323
|
+
if (text !== null) {
|
|
2324
|
+
const ascii = isAscii(head);
|
|
2325
|
+
if (!(this.legacy && this.legacyLast && !ascii && !hasLongUtf8(head))) {
|
|
2326
|
+
if (!ascii) this.legacyLast = false;
|
|
2327
|
+
this.pending = b.slice(b.length - ut);
|
|
2328
|
+
return text;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (!this.legacy) {
|
|
2332
|
+
this.pending = b.slice(b.length - ut);
|
|
2333
|
+
return this.lenient.decode(head);
|
|
2334
|
+
}
|
|
2335
|
+
this.legacyLast = true;
|
|
2336
|
+
const lt = legacyTail(b, this.legacy.encoding);
|
|
2337
|
+
this.pending = b.slice(b.length - lt);
|
|
2338
|
+
return this.legacy.decode(b.subarray(0, b.length - lt));
|
|
2339
|
+
}
|
|
2340
|
+
/** Flush whatever is held back (a truncated character at the very end becomes U+FFFD) */
|
|
2341
|
+
end() {
|
|
2342
|
+
const b = this.pending;
|
|
2343
|
+
this.pending = new Uint8Array(0);
|
|
2344
|
+
if (!b.length) return "";
|
|
2345
|
+
return (this.legacyLast && this.legacy ? this.legacy : this.lenient).decode(b);
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
|
|
1783
2351
|
// daemon/src/providers/claude/cli.ts
|
|
1784
2352
|
function claudeArgs(o) {
|
|
1785
2353
|
const args = [
|
|
@@ -1813,6 +2381,7 @@ var init_cli = __esm({
|
|
|
1813
2381
|
init_launch();
|
|
1814
2382
|
init_bin();
|
|
1815
2383
|
init_queue();
|
|
2384
|
+
init_decode();
|
|
1816
2385
|
SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._:\[\]\/@+-]{0,199}$/;
|
|
1817
2386
|
ClaudeCli = class {
|
|
1818
2387
|
constructor(o, canUseTool) {
|
|
@@ -1827,8 +2396,9 @@ var init_cli = __esm({
|
|
|
1827
2396
|
});
|
|
1828
2397
|
this.child.stdout?.setEncoding("utf8");
|
|
1829
2398
|
this.child.stdout?.on("data", (d) => this.onData(d));
|
|
2399
|
+
const errDec = new OutputDecoder();
|
|
1830
2400
|
this.child.stderr?.on("data", (d) => {
|
|
1831
|
-
this.stderrTail = (this.stderrTail +
|
|
2401
|
+
this.stderrTail = (this.stderrTail + errDec.write(d)).slice(-4096);
|
|
1832
2402
|
});
|
|
1833
2403
|
this.child.stdin?.on("error", () => {
|
|
1834
2404
|
});
|
|
@@ -2331,7 +2901,7 @@ var init_models = __esm({
|
|
|
2331
2901
|
import { readdir, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
2332
2902
|
import { existsSync as existsSync7 } from "node:fs";
|
|
2333
2903
|
import { join as join6 } from "node:path";
|
|
2334
|
-
import { homedir as
|
|
2904
|
+
import { homedir as homedir4 } from "node:os";
|
|
2335
2905
|
function parseLine(line) {
|
|
2336
2906
|
let r;
|
|
2337
2907
|
try {
|
|
@@ -2351,8 +2921,8 @@ function parseLine(line) {
|
|
|
2351
2921
|
}
|
|
2352
2922
|
return { cwd, userText };
|
|
2353
2923
|
}
|
|
2354
|
-
async function* linesOf(
|
|
2355
|
-
const text = await readFile5(
|
|
2924
|
+
async function* linesOf(path4) {
|
|
2925
|
+
const text = await readFile5(path4, "utf8").catch(() => "");
|
|
2356
2926
|
for (const line of text.split("\n")) {
|
|
2357
2927
|
if (line.trim()) yield line;
|
|
2358
2928
|
}
|
|
@@ -2364,11 +2934,11 @@ async function listClaudeHistory(cwd) {
|
|
|
2364
2934
|
const dirPath = join6(CLAUDE_ROOT, d);
|
|
2365
2935
|
const files = (await readdir(dirPath).catch(() => [])).filter((f) => f.endsWith(".jsonl"));
|
|
2366
2936
|
for (const f of files) {
|
|
2367
|
-
const
|
|
2937
|
+
const path4 = join6(dirPath, f);
|
|
2368
2938
|
let match = false;
|
|
2369
2939
|
let title = "";
|
|
2370
2940
|
let count = 0;
|
|
2371
|
-
for await (const line of linesOf(
|
|
2941
|
+
for await (const line of linesOf(path4)) {
|
|
2372
2942
|
const p = parseLine(line);
|
|
2373
2943
|
if (!p) continue;
|
|
2374
2944
|
if (!match) {
|
|
@@ -2380,7 +2950,7 @@ async function listClaudeHistory(cwd) {
|
|
|
2380
2950
|
if (p.userText) count++;
|
|
2381
2951
|
}
|
|
2382
2952
|
if (!match) continue;
|
|
2383
|
-
const s = await stat4(
|
|
2953
|
+
const s = await stat4(path4).catch(() => null);
|
|
2384
2954
|
out.push({
|
|
2385
2955
|
sessionId: f.replace(/\.jsonl$/, ""),
|
|
2386
2956
|
title: title || tr("history.untitled"),
|
|
@@ -2395,11 +2965,11 @@ async function listClaudeHistory(cwd) {
|
|
|
2395
2965
|
async function readClaudeHistory(sessionId, cwd) {
|
|
2396
2966
|
const dirs = await readdir(CLAUDE_ROOT).catch(() => []);
|
|
2397
2967
|
for (const d of dirs) {
|
|
2398
|
-
const
|
|
2399
|
-
if (!existsSync7(
|
|
2968
|
+
const path4 = join6(CLAUDE_ROOT, d, `${sessionId}.jsonl`);
|
|
2969
|
+
if (!existsSync7(path4)) continue;
|
|
2400
2970
|
const msgs = [];
|
|
2401
2971
|
let matched = false;
|
|
2402
|
-
for await (const line of linesOf(
|
|
2972
|
+
for await (const line of linesOf(path4)) {
|
|
2403
2973
|
const p = parseLine(line);
|
|
2404
2974
|
if (!matched) {
|
|
2405
2975
|
if (p?.cwd && samePath(p.cwd, cwd)) matched = true;
|
|
@@ -2422,7 +2992,7 @@ var init_import = __esm({
|
|
|
2422
2992
|
"daemon/src/history/import.ts"() {
|
|
2423
2993
|
init_paths();
|
|
2424
2994
|
init_i18n();
|
|
2425
|
-
CLAUDE_ROOT = join6(
|
|
2995
|
+
CLAUDE_ROOT = join6(homedir4(), ".claude", "projects");
|
|
2426
2996
|
rec2 = (v) => v && typeof v === "object" ? v : {};
|
|
2427
2997
|
str2 = (v) => typeof v === "string" ? v : void 0;
|
|
2428
2998
|
}
|
|
@@ -2440,8 +3010,9 @@ function runVersion(r, args, timeoutMs = 5e3) {
|
|
|
2440
3010
|
}
|
|
2441
3011
|
reject(new Error("timeout"));
|
|
2442
3012
|
}, timeoutMs);
|
|
2443
|
-
|
|
2444
|
-
p.
|
|
3013
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
3014
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
3015
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
2445
3016
|
p.on("error", reject);
|
|
2446
3017
|
p.on("close", (code) => {
|
|
2447
3018
|
clearTimeout(t);
|
|
@@ -2519,6 +3090,7 @@ var init_provider = __esm({
|
|
|
2519
3090
|
init_map();
|
|
2520
3091
|
init_i18n();
|
|
2521
3092
|
init_launch();
|
|
3093
|
+
init_decode();
|
|
2522
3094
|
INSTALL_HINT = "npm i -g @anthropic-ai/claude-code";
|
|
2523
3095
|
claudeCapabilities = {
|
|
2524
3096
|
resume: true,
|
|
@@ -2538,7 +3110,7 @@ var init_provider = __esm({
|
|
|
2538
3110
|
// daemon/src/history/codex.ts
|
|
2539
3111
|
import { readdir as readdir2, readFile as readFile6, stat as stat5 } from "node:fs/promises";
|
|
2540
3112
|
import { join as join7 } from "node:path";
|
|
2541
|
-
import { homedir as
|
|
3113
|
+
import { homedir as homedir5 } from "node:os";
|
|
2542
3114
|
function textOf(content, kind) {
|
|
2543
3115
|
if (!Array.isArray(content)) return "";
|
|
2544
3116
|
return content.filter((p) => p.type === kind || p.type === "input_text" || p.type === "output_text").map((p) => str3(p.text) ?? "").join("\n").trim();
|
|
@@ -2555,11 +3127,11 @@ async function* walk(dir, depth = 0) {
|
|
|
2555
3127
|
}
|
|
2556
3128
|
async function listCodexHistory(cwd) {
|
|
2557
3129
|
const out = [];
|
|
2558
|
-
for await (const
|
|
2559
|
-
const m = /rollout-.*-([0-9a-f-]{36})\.jsonl$/i.exec(
|
|
3130
|
+
for await (const path4 of walk(CODEX_ROOT)) {
|
|
3131
|
+
const m = /rollout-.*-([0-9a-f-]{36})\.jsonl$/i.exec(path4);
|
|
2560
3132
|
const sessionId = m?.[1];
|
|
2561
3133
|
if (!sessionId) continue;
|
|
2562
|
-
const text = await readFile6(
|
|
3134
|
+
const text = await readFile6(path4, "utf8").catch(() => "");
|
|
2563
3135
|
let match = false;
|
|
2564
3136
|
let title = "";
|
|
2565
3137
|
let count = 0;
|
|
@@ -2590,7 +3162,7 @@ async function listCodexHistory(cwd) {
|
|
|
2590
3162
|
}
|
|
2591
3163
|
}
|
|
2592
3164
|
if (!match) continue;
|
|
2593
|
-
const s = await stat5(
|
|
3165
|
+
const s = await stat5(path4).catch(() => null);
|
|
2594
3166
|
out.push({ sessionId, title: title || tr("history.untitled"), updatedAt: s ? Math.round(s.mtimeMs) : 0, messageCount: count });
|
|
2595
3167
|
}
|
|
2596
3168
|
out.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
@@ -2679,7 +3251,7 @@ var init_codex = __esm({
|
|
|
2679
3251
|
"daemon/src/history/codex.ts"() {
|
|
2680
3252
|
init_paths();
|
|
2681
3253
|
init_i18n();
|
|
2682
|
-
CODEX_ROOT = join7(
|
|
3254
|
+
CODEX_ROOT = join7(homedir5(), ".codex", "sessions");
|
|
2683
3255
|
rec3 = (v) => v && typeof v === "object" ? v : {};
|
|
2684
3256
|
str3 = (v) => typeof v === "string" ? v : void 0;
|
|
2685
3257
|
WRAPPERS = /^\s*<(environment_context|user_instructions|skills_instructions|permissions instructions)>/i;
|
|
@@ -2687,12 +3259,14 @@ var init_codex = __esm({
|
|
|
2687
3259
|
});
|
|
2688
3260
|
|
|
2689
3261
|
// daemon/src/providers/codex/rpc.ts
|
|
2690
|
-
import { spawn as
|
|
3262
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2691
3263
|
var CodexRpcError, CodexProcessExitedError, CodexRpc;
|
|
2692
3264
|
var init_rpc = __esm({
|
|
2693
3265
|
"daemon/src/providers/codex/rpc.ts"() {
|
|
2694
3266
|
init_log();
|
|
2695
3267
|
init_bin();
|
|
3268
|
+
init_decode();
|
|
3269
|
+
init_agentShell();
|
|
2696
3270
|
CodexRpcError = class extends Error {
|
|
2697
3271
|
code;
|
|
2698
3272
|
constructor(message, code) {
|
|
@@ -2715,9 +3289,9 @@ var init_rpc = __esm({
|
|
|
2715
3289
|
onNotification;
|
|
2716
3290
|
onExit;
|
|
2717
3291
|
constructor(argv, opts = {}) {
|
|
2718
|
-
this.proc =
|
|
3292
|
+
this.proc = spawn3(argv.cmd, argv.args, {
|
|
2719
3293
|
cwd: opts.cwd,
|
|
2720
|
-
env: opts.env ?? process.env,
|
|
3294
|
+
env: withAgentShell(opts.env ?? process.env),
|
|
2721
3295
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2722
3296
|
windowsHide: true,
|
|
2723
3297
|
...argv.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
|
|
@@ -2727,9 +3301,9 @@ var init_rpc = __esm({
|
|
|
2727
3301
|
trackTree(this.proc);
|
|
2728
3302
|
this.proc.stdout.setEncoding("utf8");
|
|
2729
3303
|
this.proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
2730
|
-
|
|
3304
|
+
const errDec = new OutputDecoder();
|
|
2731
3305
|
this.proc.stderr.on("data", (chunk) => {
|
|
2732
|
-
this.stderrTail = (this.stderrTail + chunk).slice(-4096);
|
|
3306
|
+
this.stderrTail = (this.stderrTail + errDec.write(chunk)).slice(-4096);
|
|
2733
3307
|
});
|
|
2734
3308
|
this.proc.on("error", () => this.failAll());
|
|
2735
3309
|
this.proc.on("close", (code) => {
|
|
@@ -3051,10 +3625,10 @@ function mapNotification(method, params, st) {
|
|
|
3051
3625
|
const steps = Array.isArray(params.plan) ? params.plan : [];
|
|
3052
3626
|
const items = steps.map((s) => {
|
|
3053
3627
|
const step = rec4(s);
|
|
3054
|
-
const
|
|
3628
|
+
const status2 = str4(step.status) ?? "pending";
|
|
3055
3629
|
return {
|
|
3056
3630
|
text: str4(step.step) ?? "",
|
|
3057
|
-
status:
|
|
3631
|
+
status: status2 === "completed" ? "done" : status2 === "inProgress" || status2 === "in_progress" ? "in_progress" : "pending"
|
|
3058
3632
|
};
|
|
3059
3633
|
});
|
|
3060
3634
|
if (items.length) out.push({ type: "todo", items });
|
|
@@ -3082,12 +3656,12 @@ function mapNotification(method, params, st) {
|
|
|
3082
3656
|
}
|
|
3083
3657
|
case "turn/completed": {
|
|
3084
3658
|
const turn = rec4(params.turn);
|
|
3085
|
-
const
|
|
3659
|
+
const status2 = str4(turn.status) ?? "completed";
|
|
3086
3660
|
const error = rec4(turn.error);
|
|
3087
3661
|
out.push({
|
|
3088
3662
|
type: "turn_ended",
|
|
3089
3663
|
turnId: str4(turn.id) ?? turnId ?? st.currentTurnId ?? "",
|
|
3090
|
-
status:
|
|
3664
|
+
status: status2 === "completed" ? "completed" : status2 === "interrupted" ? "interrupted" : "failed",
|
|
3091
3665
|
durationMs: typeof turn.durationMs === "number" ? turn.durationMs : void 0,
|
|
3092
3666
|
error: str4(error.message),
|
|
3093
3667
|
filesChanged: st.filesChanged || void 0
|
|
@@ -3117,7 +3691,7 @@ var init_map2 = __esm({
|
|
|
3117
3691
|
|
|
3118
3692
|
// daemon/src/providers/codex/commands.ts
|
|
3119
3693
|
import { readdir as readdir3, readFile as readFile7, stat as stat6 } from "node:fs/promises";
|
|
3120
|
-
import { homedir as
|
|
3694
|
+
import { homedir as homedir6 } from "node:os";
|
|
3121
3695
|
import { basename as basename3, join as join8 } from "node:path";
|
|
3122
3696
|
function parseFrontMatter(raw) {
|
|
3123
3697
|
if (!raw.startsWith("---\n")) return { meta: {}, body: raw };
|
|
@@ -3148,11 +3722,11 @@ async function readPromptFiles(dir) {
|
|
|
3148
3722
|
}
|
|
3149
3723
|
for (const f of names) {
|
|
3150
3724
|
if (!f.endsWith(".md")) continue;
|
|
3151
|
-
const
|
|
3725
|
+
const path4 = join8(dir, f);
|
|
3152
3726
|
try {
|
|
3153
|
-
const st = await stat6(
|
|
3727
|
+
const st = await stat6(path4);
|
|
3154
3728
|
if (!st.isFile()) continue;
|
|
3155
|
-
const raw = await readFile7(
|
|
3729
|
+
const raw = await readFile7(path4, "utf8");
|
|
3156
3730
|
const { meta, body } = parseFrontMatter(raw);
|
|
3157
3731
|
const name = basename3(f, ".md");
|
|
3158
3732
|
out.push({
|
|
@@ -3196,7 +3770,7 @@ async function readSkillDirs(root) {
|
|
|
3196
3770
|
return out;
|
|
3197
3771
|
}
|
|
3198
3772
|
async function loadCodexSlashEntries(cwd, opts) {
|
|
3199
|
-
const home =
|
|
3773
|
+
const home = homedir6();
|
|
3200
3774
|
const parts = await Promise.all([
|
|
3201
3775
|
opts?.skipHome ? Promise.resolve([]) : readPromptFiles(join8(home, ".codex", "prompts")),
|
|
3202
3776
|
readPromptFiles(join8(cwd, ".codex", "prompts")),
|
|
@@ -3536,7 +4110,7 @@ var init_types = __esm({
|
|
|
3536
4110
|
});
|
|
3537
4111
|
|
|
3538
4112
|
// daemon/src/providers/codex/provider.ts
|
|
3539
|
-
import { homedir as
|
|
4113
|
+
import { homedir as homedir7 } from "node:os";
|
|
3540
4114
|
import { join as join9 } from "node:path";
|
|
3541
4115
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
3542
4116
|
function runVersion2(r, args, timeoutMs = 5e3) {
|
|
@@ -3550,8 +4124,9 @@ function runVersion2(r, args, timeoutMs = 5e3) {
|
|
|
3550
4124
|
}
|
|
3551
4125
|
reject(new Error("timeout"));
|
|
3552
4126
|
}, timeoutMs);
|
|
3553
|
-
|
|
3554
|
-
p.
|
|
4127
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
4128
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
4129
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
3555
4130
|
p.on("error", reject);
|
|
3556
4131
|
p.on("close", (code) => {
|
|
3557
4132
|
clearTimeout(t);
|
|
@@ -3637,7 +4212,7 @@ function parseCodexConfig(text) {
|
|
|
3637
4212
|
return { modelProvider: provider, model: typeof model === "string" ? model : null, providerModels: [...models2] };
|
|
3638
4213
|
}
|
|
3639
4214
|
async function readCodexConfig() {
|
|
3640
|
-
const home = process.env.CODEX_HOME?.trim() || join9(
|
|
4215
|
+
const home = process.env.CODEX_HOME?.trim() || join9(homedir7(), ".codex");
|
|
3641
4216
|
const file = home.toLowerCase().endsWith(".toml") ? home : join9(home, "config.toml");
|
|
3642
4217
|
try {
|
|
3643
4218
|
return parseCodexConfig(await readFile8(file, "utf8"));
|
|
@@ -3722,6 +4297,7 @@ var init_provider2 = __esm({
|
|
|
3722
4297
|
init_i18n();
|
|
3723
4298
|
init_launch();
|
|
3724
4299
|
init_types();
|
|
4300
|
+
init_decode();
|
|
3725
4301
|
INSTALL_HINT2 = "npm i -g @openai/codex";
|
|
3726
4302
|
codexCapabilities = {
|
|
3727
4303
|
resume: true,
|
|
@@ -3747,8 +4323,8 @@ var init_provider2 = __esm({
|
|
|
3747
4323
|
function catalogById(id2) {
|
|
3748
4324
|
return PROVIDER_CATALOG.find((p) => p.id === id2);
|
|
3749
4325
|
}
|
|
3750
|
-
function providerCreatable(
|
|
3751
|
-
return
|
|
4326
|
+
function providerCreatable(status2) {
|
|
4327
|
+
return status2 !== "planned";
|
|
3752
4328
|
}
|
|
3753
4329
|
var PROVIDER_CATALOG;
|
|
3754
4330
|
var init_catalog = __esm({
|
|
@@ -4104,6 +4680,7 @@ var init_session3 = __esm({
|
|
|
4104
4680
|
init_prompt();
|
|
4105
4681
|
init_i18n();
|
|
4106
4682
|
init_log();
|
|
4683
|
+
init_decode();
|
|
4107
4684
|
AcpSession = class {
|
|
4108
4685
|
constructor(o) {
|
|
4109
4686
|
this.o = o;
|
|
@@ -4149,8 +4726,9 @@ var init_session3 = __esm({
|
|
|
4149
4726
|
if (!bin.ok) throw new Error(bin.reason);
|
|
4150
4727
|
try {
|
|
4151
4728
|
this.child = spawnResolved(bin, launchArgv(this.o.acpArgs, this.o.launch), { cwd: this.o.cwd });
|
|
4729
|
+
const errDec = new OutputDecoder();
|
|
4152
4730
|
this.child.stderr?.on("data", (d) => {
|
|
4153
|
-
const t =
|
|
4731
|
+
const t = errDec.write(d).trim();
|
|
4154
4732
|
if (t) log("info", `[${this.o.binName} acp] ${t.slice(0, 500)}`);
|
|
4155
4733
|
});
|
|
4156
4734
|
this.child.on("error", (e) => this.failInit(e));
|
|
@@ -4297,7 +4875,7 @@ var init_session3 = __esm({
|
|
|
4297
4875
|
return hit?.id ?? want;
|
|
4298
4876
|
}
|
|
4299
4877
|
if (this.o.flavor === "cursor") {
|
|
4300
|
-
const map = { default: "agent", plan: "plan", ask: "ask" };
|
|
4878
|
+
const map = { default: "agent", bypassPermissions: "agent", plan: "plan", ask: "ask" };
|
|
4301
4879
|
const want = map[mode2] ?? "agent";
|
|
4302
4880
|
const avail = this.active?.modes?.availableModes ?? [];
|
|
4303
4881
|
const hit = avail.find((m) => m.id === want || m.name.toLowerCase() === want);
|
|
@@ -4547,8 +5125,9 @@ function runVersion3(r, args, timeoutMs = 8e3) {
|
|
|
4547
5125
|
}
|
|
4548
5126
|
reject(new Error("timeout"));
|
|
4549
5127
|
}, timeoutMs);
|
|
4550
|
-
|
|
4551
|
-
p.
|
|
5128
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
5129
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
5130
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
4552
5131
|
p.on("error", reject);
|
|
4553
5132
|
p.on("close", (code) => {
|
|
4554
5133
|
clearTimeout(t);
|
|
@@ -4563,7 +5142,7 @@ async function resolveProviderBin(cfg2) {
|
|
|
4563
5142
|
}
|
|
4564
5143
|
function createAcpProvider(cfg2) {
|
|
4565
5144
|
const base = cfg2.capabilities ?? acpCapabilities;
|
|
4566
|
-
const ORDER = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
5145
|
+
const ORDER = ["plan", "default", "acceptEdits", "bypassPermissions", "ask"];
|
|
4567
5146
|
const caps = cfg2.modeMap ? { ...base, modes: ORDER.filter((m) => cfg2.modeMap[m] !== void 0), modeLabels: cfg2.modeLabels } : base;
|
|
4568
5147
|
return {
|
|
4569
5148
|
id: cfg2.id,
|
|
@@ -4628,6 +5207,7 @@ var init_provider3 = __esm({
|
|
|
4628
5207
|
init_types();
|
|
4629
5208
|
init_session3();
|
|
4630
5209
|
init_promptCaps();
|
|
5210
|
+
init_decode();
|
|
4631
5211
|
acpCapabilities = {
|
|
4632
5212
|
resume: true,
|
|
4633
5213
|
interrupt: true,
|
|
@@ -4643,7 +5223,9 @@ var init_provider3 = __esm({
|
|
|
4643
5223
|
cursorAcpCapabilities = {
|
|
4644
5224
|
...acpCapabilities,
|
|
4645
5225
|
questions: true,
|
|
4646
|
-
|
|
5226
|
+
// 1.3: bypassPermissions = 全部自动同意: Cursor's agent mode, every approval answered "allow once" by the daemon
|
|
5227
|
+
// (Cursor's own --force only exists at spawn, and Auto-review is not applied over ACP)
|
|
5228
|
+
modes: ["default", "bypassPermissions", "plan", "ask"]
|
|
4647
5229
|
};
|
|
4648
5230
|
}
|
|
4649
5231
|
});
|
|
@@ -4752,8 +5334,9 @@ function probeAcpRaw(r, acpArgs, timeoutMs = 3e4, extMethod) {
|
|
|
4752
5334
|
child.on("close", () => finish(new Error("ACP \u8FDB\u7A0B\u63D0\u524D\u9000\u51FA")));
|
|
4753
5335
|
child.stderr?.on("data", () => {
|
|
4754
5336
|
});
|
|
5337
|
+
child.stdout?.setEncoding("utf8");
|
|
4755
5338
|
child.stdout?.on("data", (d) => {
|
|
4756
|
-
buf += d
|
|
5339
|
+
buf += d;
|
|
4757
5340
|
let n;
|
|
4758
5341
|
while ((n = buf.indexOf("\n")) >= 0) {
|
|
4759
5342
|
const line = buf.slice(0, n).trim();
|
|
@@ -4910,8 +5493,9 @@ function acpConfigForEntry(entry, appVersion) {
|
|
|
4910
5493
|
...base,
|
|
4911
5494
|
acpArgs: ["acp"],
|
|
4912
5495
|
fetchModels: acpProbeModels(["acp"], entry.id),
|
|
4913
|
-
|
|
4914
|
-
|
|
5496
|
+
// 1.3: 全部自动同意 = build + the daemon answers each approval "allow once" (OPENCODE_PERMISSION only applies at spawn)
|
|
5497
|
+
modeMap: { plan: "plan", default: "build", bypassPermissions: "build" },
|
|
5498
|
+
modeLabels: { plan: "Plan", default: "Build", bypassPermissions: "Build" }
|
|
4915
5499
|
};
|
|
4916
5500
|
case "copilot":
|
|
4917
5501
|
return {
|
|
@@ -5033,13 +5617,13 @@ var init_pickFolderWin = __esm({
|
|
|
5033
5617
|
|
|
5034
5618
|
// daemon/src/util/pickFolder.ts
|
|
5035
5619
|
import { execFile as execFile2 } from "node:child_process";
|
|
5036
|
-
import { existsSync as existsSync8, statSync as
|
|
5620
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
5037
5621
|
function appleScriptString(s) {
|
|
5038
5622
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
5039
5623
|
}
|
|
5040
5624
|
function isDir(p) {
|
|
5041
5625
|
try {
|
|
5042
|
-
return !!p && existsSync8(p) &&
|
|
5626
|
+
return !!p && existsSync8(p) && statSync4(p).isDirectory();
|
|
5043
5627
|
} catch {
|
|
5044
5628
|
return false;
|
|
5045
5629
|
}
|
|
@@ -5088,20 +5672,20 @@ var init_pickFolder = __esm({
|
|
|
5088
5672
|
|
|
5089
5673
|
// daemon/src/providers/magpieGateway.ts
|
|
5090
5674
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
5091
|
-
import { homedir as
|
|
5675
|
+
import { homedir as homedir8 } from "node:os";
|
|
5092
5676
|
import { join as join11 } from "node:path";
|
|
5093
|
-
async function readJson(
|
|
5677
|
+
async function readJson(path4) {
|
|
5094
5678
|
try {
|
|
5095
|
-
return JSON.parse(await readFile11(
|
|
5679
|
+
return JSON.parse(await readFile11(path4, "utf8"));
|
|
5096
5680
|
} catch {
|
|
5097
5681
|
return null;
|
|
5098
5682
|
}
|
|
5099
5683
|
}
|
|
5100
5684
|
async function readApplied() {
|
|
5101
|
-
return await readJson(join11(
|
|
5685
|
+
return await readJson(join11(homedir8(), ".config", "magpie", "applied.json")) ?? {};
|
|
5102
5686
|
}
|
|
5103
5687
|
async function readClaudeEnv() {
|
|
5104
|
-
const s = await readJson(join11(
|
|
5688
|
+
const s = await readJson(join11(homedir8(), ".claude", "settings.json"));
|
|
5105
5689
|
return s?.env ?? {};
|
|
5106
5690
|
}
|
|
5107
5691
|
async function gatewayBase() {
|
|
@@ -5202,7 +5786,7 @@ var init_magpieGateway = __esm({
|
|
|
5202
5786
|
});
|
|
5203
5787
|
|
|
5204
5788
|
// daemon/src/providers/infoCache.ts
|
|
5205
|
-
function
|
|
5789
|
+
function withTimeout2(p, ms, fb) {
|
|
5206
5790
|
return Promise.race([p, new Promise((r) => setTimeout(() => r(fb), ms))]);
|
|
5207
5791
|
}
|
|
5208
5792
|
function fresh(e, ttl) {
|
|
@@ -5218,7 +5802,7 @@ async function cachedDetect(p, launch) {
|
|
|
5218
5802
|
let job = inflightDetect.get(key);
|
|
5219
5803
|
if (!job) {
|
|
5220
5804
|
const timedOut = { available: false, reason: tr("provider.timeout") };
|
|
5221
|
-
job =
|
|
5805
|
+
job = withTimeout2(
|
|
5222
5806
|
p.detect(launch).catch((e) => ({ available: false, reason: String(e?.message ?? e) })),
|
|
5223
5807
|
STEP_TIMEOUT_MS,
|
|
5224
5808
|
timedOut
|
|
@@ -5236,7 +5820,7 @@ async function cachedModels(p, launch) {
|
|
|
5236
5820
|
if (fresh(hit, MODELS_MS)) return hit.value;
|
|
5237
5821
|
let job = inflightModels.get(key);
|
|
5238
5822
|
if (!job) {
|
|
5239
|
-
job =
|
|
5823
|
+
job = withTimeout2(p.listModels(launch).catch(() => []), MODELS_TIMEOUT_MS, null).then(async (v) => {
|
|
5240
5824
|
if (v === null) return models.get(key)?.value ?? [];
|
|
5241
5825
|
const list = await withMagpie(p.id, dropPlaceholder(v)).catch(() => dropPlaceholder(v));
|
|
5242
5826
|
models.set(key, { at: Date.now(), value: list });
|
|
@@ -5289,13 +5873,113 @@ var init_modes = __esm({
|
|
|
5289
5873
|
}
|
|
5290
5874
|
});
|
|
5291
5875
|
|
|
5876
|
+
// daemon/src/providers/autoApprove.ts
|
|
5877
|
+
function pickAllowOnce(options) {
|
|
5878
|
+
const once = options.find((o) => o.kind === "allow_once");
|
|
5879
|
+
if (once) return once;
|
|
5880
|
+
const plain = options.find((o) => {
|
|
5881
|
+
const s = `${o.id} ${o.label ?? ""}`;
|
|
5882
|
+
return !o.kind?.startsWith("reject") && !REJECT.test(s) && ALLOW.test(s) && !/always|session|总是|始终/i.test(s);
|
|
5883
|
+
});
|
|
5884
|
+
return plain ?? null;
|
|
5885
|
+
}
|
|
5886
|
+
function autoApproveOption(mode2, providerId, req) {
|
|
5887
|
+
if (mode2 !== FULL_AUTO) return null;
|
|
5888
|
+
if (FULL_AUTO_NATIVE[providerId]?.daemonAnswers === false) return null;
|
|
5889
|
+
if (req.kind === "plan" || req.kind === "switch_mode") return null;
|
|
5890
|
+
return pickAllowOnce(req.options);
|
|
5891
|
+
}
|
|
5892
|
+
var FULL_AUTO, FULL_AUTO_NATIVE, REJECT, ALLOW;
|
|
5893
|
+
var init_autoApprove = __esm({
|
|
5894
|
+
"daemon/src/providers/autoApprove.ts"() {
|
|
5895
|
+
FULL_AUTO = "bypassPermissions";
|
|
5896
|
+
FULL_AUTO_NATIVE = {
|
|
5897
|
+
claude: { native: "--permission-mode bypassPermissions (+ --allow-dangerously-skip-permissions)", live: "nextTurn", daemonAnswers: true },
|
|
5898
|
+
codex: { native: "approvalPolicy never + sandbox danger-full-access (turn/start)", live: "nextTurn", daemonAnswers: true },
|
|
5899
|
+
cursor: { native: "none over ACP (--force is spawn-only); ACP mode agent", live: "live", daemonAnswers: true },
|
|
5900
|
+
copilot: { native: "ACP config allow_all = on (agent mode)", live: "live", daemonAnswers: true },
|
|
5901
|
+
opencode: { native: "none over ACP (OPENCODE_PERMISSION is spawn-only); ACP mode build", live: "live", daemonAnswers: true },
|
|
5902
|
+
devin: { native: "ACP mode bypass", live: "live", daemonAnswers: true },
|
|
5903
|
+
dsh: { native: "none known; ACP default mode", live: "live", daemonAnswers: true },
|
|
5904
|
+
// pi never asks before its own tools; requestPermission only carries Pi extensions' own dialogs, which stay with the user
|
|
5905
|
+
pi: { native: "always (Pi has no tool approvals)", live: "live", daemonAnswers: false },
|
|
5906
|
+
mock: { native: "mock", live: "live", daemonAnswers: true }
|
|
5907
|
+
};
|
|
5908
|
+
REJECT = /reject|deny|decline|cancel|never|拒绝/i;
|
|
5909
|
+
ALLOW = /allow|accept|approve|yes|允许|同意/i;
|
|
5910
|
+
}
|
|
5911
|
+
});
|
|
5912
|
+
|
|
5913
|
+
// daemon/src/migrate/autoApprove13.ts
|
|
5914
|
+
var autoApprove13_exports = {};
|
|
5915
|
+
__export(autoApprove13_exports, {
|
|
5916
|
+
LEGACY_NO_AUTO: () => LEGACY_NO_AUTO,
|
|
5917
|
+
MIGRATED_NOTICE_CODE: () => MIGRATED_NOTICE_CODE,
|
|
5918
|
+
UPGRADE_NOTICE_CODE: () => UPGRADE_NOTICE_CODE,
|
|
5919
|
+
endLegacyDefault: () => endLegacyDefault,
|
|
5920
|
+
guardLegacyMode: () => guardLegacyMode,
|
|
5921
|
+
migrateAutoApprove13: () => migrateAutoApprove13,
|
|
5922
|
+
upgradeNotice: () => upgradeNotice
|
|
5923
|
+
});
|
|
5924
|
+
function state(s) {
|
|
5925
|
+
const v = s[KEY];
|
|
5926
|
+
return v && typeof v === "object" && v.done === true ? { done: true, legacyDefault: v.legacyDefault === true, noticed: v.noticed === true } : null;
|
|
5927
|
+
}
|
|
5928
|
+
async function migrateAutoApprove13(stores) {
|
|
5929
|
+
const s = await stores.settings.get();
|
|
5930
|
+
if (state(s)) return { threads: [], legacyDefault: false, skipped: true };
|
|
5931
|
+
const moved = [];
|
|
5932
|
+
for (const t of await stores.threads.list()) {
|
|
5933
|
+
if (t.mode !== FULL_AUTO || !LEGACY_NO_AUTO.has(t.provider)) continue;
|
|
5934
|
+
try {
|
|
5935
|
+
await stores.threads.patch(t.id, { mode: "default" });
|
|
5936
|
+
await stores.threads.appendEvent(t.id, { type: "notice", level: "warn", code: MIGRATED_NOTICE_CODE, message: tr("notice.autoApproveMigrated") });
|
|
5937
|
+
moved.push(t.id);
|
|
5938
|
+
} catch (e) {
|
|
5939
|
+
log("warn", `\u5168\u90E8\u81EA\u52A8\u540C\u610F\u8FC1\u79FB\uFF1A\u5BF9\u8BDD ${t.id} \u5931\u8D25\uFF1A${String(e)}`);
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
const legacyDefault = s.defaultMode === FULL_AUTO;
|
|
5943
|
+
await stores.settings.set({ [KEY]: { done: true, legacyDefault, noticed: false } });
|
|
5944
|
+
if (moved.length || legacyDefault) log("info", `\u5168\u90E8\u81EA\u52A8\u540C\u610F\u8FC1\u79FB\uFF1A${moved.length} \u4E2A\u5BF9\u8BDD\u6539\u56DE\u6BCF\u6B21\u8BE2\u95EE\uFF1B\u65E7\u7684\u9ED8\u8BA4\u5168\u90E8\u653E\u884C=${legacyDefault}`);
|
|
5945
|
+
return { threads: moved, legacyDefault, skipped: false };
|
|
5946
|
+
}
|
|
5947
|
+
async function guardLegacyMode(settings, providerId, mode2) {
|
|
5948
|
+
if (mode2 !== FULL_AUTO || !LEGACY_NO_AUTO.has(providerId)) return { mode: mode2, notice: false };
|
|
5949
|
+
const s = await settings.get();
|
|
5950
|
+
const st = state(s);
|
|
5951
|
+
if (!st?.legacyDefault) return { mode: mode2, notice: false };
|
|
5952
|
+
if (st.noticed) return { mode: "default", notice: false };
|
|
5953
|
+
await settings.set({ [KEY]: { ...st, noticed: true } });
|
|
5954
|
+
return { mode: "default", notice: true };
|
|
5955
|
+
}
|
|
5956
|
+
async function endLegacyDefault(settings) {
|
|
5957
|
+
const st = state(await settings.get());
|
|
5958
|
+
if (st?.legacyDefault) await settings.set({ [KEY]: { ...st, legacyDefault: false } });
|
|
5959
|
+
}
|
|
5960
|
+
function upgradeNotice() {
|
|
5961
|
+
return { type: "notice", level: "info", code: UPGRADE_NOTICE_CODE, message: tr("notice.autoApproveUpgrade") };
|
|
5962
|
+
}
|
|
5963
|
+
var LEGACY_NO_AUTO, UPGRADE_NOTICE_CODE, MIGRATED_NOTICE_CODE, KEY;
|
|
5964
|
+
var init_autoApprove13 = __esm({
|
|
5965
|
+
"daemon/src/migrate/autoApprove13.ts"() {
|
|
5966
|
+
init_autoApprove();
|
|
5967
|
+
init_i18n();
|
|
5968
|
+
init_log();
|
|
5969
|
+
LEGACY_NO_AUTO = /* @__PURE__ */ new Set(["cursor", "opencode", "dsh"]);
|
|
5970
|
+
UPGRADE_NOTICE_CODE = "auto_approve_upgrade";
|
|
5971
|
+
MIGRATED_NOTICE_CODE = "auto_approve_migrated";
|
|
5972
|
+
KEY = "autoApproveUpgrade";
|
|
5973
|
+
}
|
|
5974
|
+
});
|
|
5975
|
+
|
|
5292
5976
|
// daemon/src/util/mentions.ts
|
|
5293
5977
|
import { realpath, stat as stat8 } from "node:fs/promises";
|
|
5294
5978
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep2, win32 as win322, posix as posix2 } from "node:path";
|
|
5295
5979
|
function insideRoot(root, child, platform = process.platform) {
|
|
5296
5980
|
const p = platform === "win32" ? win322 : posix2;
|
|
5297
|
-
const
|
|
5298
|
-
const r = p.relative(
|
|
5981
|
+
const norm2 = (s) => platform === "win32" ? p.resolve(s).toLowerCase() : p.resolve(s);
|
|
5982
|
+
const r = p.relative(norm2(root), norm2(child));
|
|
5299
5983
|
return r === "" || !r.startsWith("..") && !p.isAbsolute(r);
|
|
5300
5984
|
}
|
|
5301
5985
|
async function resolveMentions(projectPath, refs) {
|
|
@@ -5894,8 +6578,8 @@ var init_proxy = __esm({
|
|
|
5894
6578
|
return;
|
|
5895
6579
|
}
|
|
5896
6580
|
l.lastUsed = this.now();
|
|
5897
|
-
const
|
|
5898
|
-
if (
|
|
6581
|
+
const path4 = (req.url ?? "/").split("?")[0];
|
|
6582
|
+
if (path4 === PREVIEW_PING_PATH) {
|
|
5899
6583
|
const body = g.role === "canary" ? l.canaryNonce : l.nonce;
|
|
5900
6584
|
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "referrer-policy": "no-referrer" });
|
|
5901
6585
|
res.end(req.method === "HEAD" ? void 0 : body);
|
|
@@ -5910,7 +6594,7 @@ var init_proxy = __esm({
|
|
|
5910
6594
|
this.refuse(res, req, 403, "magpie");
|
|
5911
6595
|
return;
|
|
5912
6596
|
}
|
|
5913
|
-
if (
|
|
6597
|
+
if (path4 === PREVIEW_SCRIPT_PATH) {
|
|
5914
6598
|
res.writeHead(200, {
|
|
5915
6599
|
"content-type": "text/javascript; charset=utf-8",
|
|
5916
6600
|
"cache-control": "no-store",
|
|
@@ -6030,12 +6714,12 @@ var init_proxy = __esm({
|
|
|
6030
6714
|
return;
|
|
6031
6715
|
}
|
|
6032
6716
|
const h = rewriteResponseHeaders(ur.headers, l.target, l.proxyOrigin);
|
|
6033
|
-
const
|
|
6717
|
+
const status2 = ur.statusCode ?? 502;
|
|
6034
6718
|
const ctype = String(ur.headers["content-type"] ?? "").toLowerCase();
|
|
6035
6719
|
const encoded = !!ur.headers["content-encoding"] && ur.headers["content-encoding"] !== "identity";
|
|
6036
|
-
const html = ctype.includes("text/html") && !encoded && req.method !== "HEAD" &&
|
|
6720
|
+
const html = ctype.includes("text/html") && !encoded && req.method !== "HEAD" && status2 !== 204 && status2 !== 304;
|
|
6037
6721
|
if (!html) {
|
|
6038
|
-
res.writeHead(
|
|
6722
|
+
res.writeHead(status2, h);
|
|
6039
6723
|
ur.pipe(res);
|
|
6040
6724
|
return;
|
|
6041
6725
|
}
|
|
@@ -6048,7 +6732,7 @@ var init_proxy = __esm({
|
|
|
6048
6732
|
chunks.push(c);
|
|
6049
6733
|
if (size > this.maxHtml) {
|
|
6050
6734
|
passthrough = true;
|
|
6051
|
-
res.writeHead(
|
|
6735
|
+
res.writeHead(status2, h);
|
|
6052
6736
|
for (const x of chunks) res.write(x);
|
|
6053
6737
|
ur.removeAllListeners("data");
|
|
6054
6738
|
ur.removeAllListeners("end");
|
|
@@ -6059,7 +6743,7 @@ var init_proxy = __esm({
|
|
|
6059
6743
|
if (passthrough) return;
|
|
6060
6744
|
const out = Buffer.from(injectScriptTag(Buffer.concat(chunks).toString("latin1")), "latin1");
|
|
6061
6745
|
h["content-length"] = String(out.length);
|
|
6062
|
-
res.writeHead(
|
|
6746
|
+
res.writeHead(status2, h);
|
|
6063
6747
|
res.end(out);
|
|
6064
6748
|
});
|
|
6065
6749
|
ur.on("error", () => res.destroy());
|
|
@@ -6096,7 +6780,7 @@ connection: close\r
|
|
|
6096
6780
|
return;
|
|
6097
6781
|
}
|
|
6098
6782
|
const up = l.target.secure ? tlsConnect({ host: addr, port: l.target.port, rejectUnauthorized: false, servername: this.servername(l) }) : netConnect({ host: addr, port: l.target.port });
|
|
6099
|
-
const
|
|
6783
|
+
const ready2 = l.target.secure ? "secureConnect" : "connect";
|
|
6100
6784
|
l.tunnels++;
|
|
6101
6785
|
let counted = true;
|
|
6102
6786
|
const done = () => {
|
|
@@ -6108,7 +6792,7 @@ connection: close\r
|
|
|
6108
6792
|
up.destroy();
|
|
6109
6793
|
socket.destroy();
|
|
6110
6794
|
};
|
|
6111
|
-
up.once(
|
|
6795
|
+
up.once(ready2, () => {
|
|
6112
6796
|
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/1.1`];
|
|
6113
6797
|
const hs = rewriteRequestHeaders(req.headers, l.target, l.proxyOrigin, true);
|
|
6114
6798
|
for (const [k, v] of Object.entries(hs)) {
|
|
@@ -6147,9 +6831,9 @@ var init_host = __esm({
|
|
|
6147
6831
|
|
|
6148
6832
|
// daemon/src/ws/methods.ts
|
|
6149
6833
|
import { z } from "zod";
|
|
6150
|
-
import { existsSync as existsSync9, statSync as
|
|
6834
|
+
import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
|
|
6151
6835
|
import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
6152
|
-
import { homedir as
|
|
6836
|
+
import { homedir as homedir9 } from "node:os";
|
|
6153
6837
|
import { dirname as dirname3, join as join12, win32 as win32Path } from "node:path";
|
|
6154
6838
|
import { execFile as execFile4 } from "node:child_process";
|
|
6155
6839
|
function wrap(schema, fn) {
|
|
@@ -6173,6 +6857,14 @@ function registerMethods(gw, svc) {
|
|
|
6173
6857
|
pid: process.pid,
|
|
6174
6858
|
port: svc.cfg.port
|
|
6175
6859
|
})));
|
|
6860
|
+
gw.method("shell.status", wrap(zEmpty, async () => {
|
|
6861
|
+
const st = agentShellStatus();
|
|
6862
|
+
return { ...st, platform: process.platform, downloadUrl: GIT_FOR_WINDOWS_URL };
|
|
6863
|
+
}));
|
|
6864
|
+
gw.method("shell.check", wrap(z.object({ path: z.string().max(1024) }), (p) => {
|
|
6865
|
+
if (process.platform !== "win32") return { ok: false, reason: "notWindows" };
|
|
6866
|
+
return checkBashPath(p.path);
|
|
6867
|
+
}));
|
|
6176
6868
|
gw.method("app.shutdown", wrap(zEmpty, () => {
|
|
6177
6869
|
setTimeout(() => process.exit(0), 100);
|
|
6178
6870
|
return { ok: true };
|
|
@@ -6210,6 +6902,10 @@ function registerMethods(gw, svc) {
|
|
|
6210
6902
|
if (!checked.ok) throw new ApiError("bad_params", `Invalid setting: ${checked.error}`);
|
|
6211
6903
|
const before = await svc.settings.get();
|
|
6212
6904
|
const next = await svc.settings.set(checked.patch);
|
|
6905
|
+
if ("defaultMode" in checked.patch) await endLegacyDefault(svc.settings);
|
|
6906
|
+
if ("bashPath" in checked.patch && (before.bashPath ?? "") !== (next.bashPath ?? "")) {
|
|
6907
|
+
await refreshAgentShell(typeof next.bashPath === "string" ? next.bashPath : "");
|
|
6908
|
+
}
|
|
6213
6909
|
for (const id2 of Object.keys(checked.patch.providerCommand ?? {})) {
|
|
6214
6910
|
if (JSON.stringify(before.providerCommand?.[id2] ?? null) !== JSON.stringify(next.providerCommand?.[id2] ?? null)) {
|
|
6215
6911
|
clearProviderCache(id2);
|
|
@@ -6319,7 +7015,7 @@ function registerMethods(gw, svc) {
|
|
|
6319
7015
|
let isDir2 = false;
|
|
6320
7016
|
try {
|
|
6321
7017
|
exists = existsSync9(p.path);
|
|
6322
|
-
isDir2 = exists &&
|
|
7018
|
+
isDir2 = exists && statSync5(p.path).isDirectory();
|
|
6323
7019
|
} catch {
|
|
6324
7020
|
}
|
|
6325
7021
|
return { exists, isDir: isDir2 };
|
|
@@ -6356,8 +7052,10 @@ function registerMethods(gw, svc) {
|
|
|
6356
7052
|
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: cat.name }));
|
|
6357
7053
|
}
|
|
6358
7054
|
const provider = getProvider(p.provider);
|
|
6359
|
-
const
|
|
7055
|
+
const guard = await guardLegacyMode(svc.settings, p.provider, clampMode(provider?.capabilities.modes ?? [], p.mode));
|
|
7056
|
+
const mode2 = guard.mode;
|
|
6360
7057
|
const meta = await svc.threads.create({ ...p, mode: mode2 });
|
|
7058
|
+
if (guard.notice) await svc.threads.appendEvent(meta.id, upgradeNotice());
|
|
6361
7059
|
gw.publish("sidebar", { type: "thread.upsert", thread: meta });
|
|
6362
7060
|
return meta;
|
|
6363
7061
|
}
|
|
@@ -6501,8 +7199,8 @@ function registerMethods(gw, svc) {
|
|
|
6501
7199
|
gw.method("terminal.create", wrap(
|
|
6502
7200
|
z.object({ projectId: z.string(), cols: z.number().int().min(20).max(500).default(80), rows: z.number().int().min(5).max(200).default(24) }),
|
|
6503
7201
|
async (p) => {
|
|
6504
|
-
const
|
|
6505
|
-
const r = await svc.terminals.create(p.projectId,
|
|
7202
|
+
const path4 = await projectPath(p.projectId);
|
|
7203
|
+
const r = await svc.terminals.create(p.projectId, path4, p.cols, p.rows);
|
|
6506
7204
|
if ("error" in r) throw new ApiError("bad_params", r.error);
|
|
6507
7205
|
return r;
|
|
6508
7206
|
}
|
|
@@ -6529,10 +7227,12 @@ function registerMethods(gw, svc) {
|
|
|
6529
7227
|
attachments: z.array(z.object({ path: z.string(), mime: z.string(), name: z.string().optional() })).optional(),
|
|
6530
7228
|
// 1.2: paths dragged from the Files panel, relative to the project (the daemon resolves and checks them)
|
|
6531
7229
|
mentions: z.array(z.object({ rel: z.string().min(1).max(4096), isDir: z.boolean().optional() })).max(MAX_MENTIONS).optional(),
|
|
6532
|
-
behavior: z.enum(["queue", "steer"]).optional()
|
|
7230
|
+
behavior: z.enum(["queue", "steer"]).optional(),
|
|
7231
|
+
// 1.3: the page's id for its optimistic bubble, echoed on the user_message event (dedupe, no jump)
|
|
7232
|
+
clientId: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/).optional()
|
|
6533
7233
|
}),
|
|
6534
7234
|
async (p) => {
|
|
6535
|
-
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments, mentions: p.mentions }, p.behavior ?? "queue");
|
|
7235
|
+
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments, mentions: p.mentions, clientId: p.clientId }, p.behavior ?? "queue");
|
|
6536
7236
|
return { ok: true, delivered: delivered ?? "started" };
|
|
6537
7237
|
}
|
|
6538
7238
|
));
|
|
@@ -6650,7 +7350,7 @@ ${e.text}
|
|
|
6650
7350
|
const markdown = L.join("\n");
|
|
6651
7351
|
const safe = meta.title.replace(/[\\/:*?"<>|\s]+/g, "_").slice(0, 60) || "thread";
|
|
6652
7352
|
const filename = `${safe}.md`;
|
|
6653
|
-
let dir = join12(
|
|
7353
|
+
let dir = join12(homedir9(), "Downloads");
|
|
6654
7354
|
try {
|
|
6655
7355
|
await mkdir3(dir, { recursive: true });
|
|
6656
7356
|
} catch {
|
|
@@ -6696,9 +7396,12 @@ var init_methods = __esm({
|
|
|
6696
7396
|
init_infoCache();
|
|
6697
7397
|
init_launch();
|
|
6698
7398
|
init_modes();
|
|
7399
|
+
init_autoApprove13();
|
|
6699
7400
|
init_i18n();
|
|
6700
7401
|
init_mentions();
|
|
6701
7402
|
init_openExternal();
|
|
7403
|
+
init_agentShell();
|
|
7404
|
+
init_gitBash();
|
|
6702
7405
|
init_proxy();
|
|
6703
7406
|
init_url();
|
|
6704
7407
|
init_host();
|
|
@@ -6814,7 +7517,8 @@ var init_settings = __esm({
|
|
|
6814
7517
|
z2.string().max(64),
|
|
6815
7518
|
z2.object({ executable: z2.string().max(4096).optional(), args: z2.array(z2.string().max(4096)).max(64).optional() }).strict()
|
|
6816
7519
|
),
|
|
6817
|
-
v1Migrated: z2.boolean()
|
|
7520
|
+
v1Migrated: z2.boolean(),
|
|
7521
|
+
bashPath: z2.string().max(1024)
|
|
6818
7522
|
}).partial().strict();
|
|
6819
7523
|
SettingsStore = class {
|
|
6820
7524
|
file;
|
|
@@ -7177,8 +7881,8 @@ import { dirname, join } from "node:path";
|
|
|
7177
7881
|
import { fileURLToPath } from "node:url";
|
|
7178
7882
|
function resolveDaemonMainJs() {
|
|
7179
7883
|
const self = fileURLToPath(import.meta.url);
|
|
7180
|
-
const
|
|
7181
|
-
if (
|
|
7884
|
+
const norm2 = self.replace(/\\/g, "/");
|
|
7885
|
+
if (norm2.endsWith("/daemon/dist/main.js") || norm2.endsWith("/dist/main.js")) return self;
|
|
7182
7886
|
const here = dirname(self);
|
|
7183
7887
|
const candidates = [
|
|
7184
7888
|
join(here, "..", "..", "dist", "main.js"),
|
|
@@ -7516,13 +8220,14 @@ var AttachmentStore = class {
|
|
|
7516
8220
|
|
|
7517
8221
|
// daemon/src/main.ts
|
|
7518
8222
|
init_settings();
|
|
8223
|
+
init_agentShell();
|
|
7519
8224
|
|
|
7520
8225
|
// daemon/src/store/projects.ts
|
|
7521
8226
|
init_atomic();
|
|
7522
8227
|
init_paths();
|
|
7523
8228
|
init_methods();
|
|
7524
8229
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
7525
|
-
import { existsSync as existsSync10, statSync as
|
|
8230
|
+
import { existsSync as existsSync10, statSync as statSync6 } from "node:fs";
|
|
7526
8231
|
import { join as join14 } from "node:path";
|
|
7527
8232
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
7528
8233
|
function newId() {
|
|
@@ -7555,19 +8260,19 @@ var ProjectsStore = class {
|
|
|
7555
8260
|
this.writing = this.writing.then(() => writeJsonAtomic(this.file, snap));
|
|
7556
8261
|
await this.writing;
|
|
7557
8262
|
}
|
|
7558
|
-
async create(
|
|
7559
|
-
if (!
|
|
7560
|
-
if (!existsSync10(
|
|
8263
|
+
async create(path4, name) {
|
|
8264
|
+
if (!path4 || typeof path4 !== "string") throw new ApiError("bad_params", "\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8265
|
+
if (!existsSync10(path4) || !statSync6(path4).isDirectory()) {
|
|
7561
8266
|
throw new ApiError("bad_path", "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939");
|
|
7562
8267
|
}
|
|
7563
|
-
const np = normPath(
|
|
8268
|
+
const np = normPath(path4);
|
|
7564
8269
|
const list = await this.list();
|
|
7565
8270
|
const dup = list.find((p) => normPath(p.path) === np);
|
|
7566
8271
|
if (dup) return dup;
|
|
7567
8272
|
const proj = {
|
|
7568
8273
|
id: newId(),
|
|
7569
|
-
name: name?.trim() ||
|
|
7570
|
-
path:
|
|
8274
|
+
name: name?.trim() || path4.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || path4,
|
|
8275
|
+
path: path4,
|
|
7571
8276
|
createdAt: Date.now()
|
|
7572
8277
|
};
|
|
7573
8278
|
list.push(proj);
|
|
@@ -7616,6 +8321,15 @@ var ThreadsStore = class {
|
|
|
7616
8321
|
});
|
|
7617
8322
|
return p;
|
|
7618
8323
|
}
|
|
8324
|
+
/** Resolves once no meta / timeline write is queued or in flight (tests remove the data dir only after this) */
|
|
8325
|
+
async idle() {
|
|
8326
|
+
for (; ; ) {
|
|
8327
|
+
const q = this.writeQ;
|
|
8328
|
+
await q;
|
|
8329
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
8330
|
+
if (q === this.writeQ) return;
|
|
8331
|
+
}
|
|
8332
|
+
}
|
|
7619
8333
|
constructor(dataDir) {
|
|
7620
8334
|
this.dir = join15(dataDir, "threads");
|
|
7621
8335
|
this.trashDir = join15(dataDir, "trash", "threads");
|
|
@@ -7958,6 +8672,8 @@ init_registry();
|
|
|
7958
8672
|
// daemon/src/agents/manager.ts
|
|
7959
8673
|
init_launch();
|
|
7960
8674
|
init_modes();
|
|
8675
|
+
init_autoApprove13();
|
|
8676
|
+
init_autoApprove();
|
|
7961
8677
|
init_attachmentRefs();
|
|
7962
8678
|
init_registry();
|
|
7963
8679
|
init_catalog();
|
|
@@ -8007,8 +8723,16 @@ var Coalescer = class {
|
|
|
8007
8723
|
init_methods();
|
|
8008
8724
|
init_log();
|
|
8009
8725
|
init_i18n();
|
|
8726
|
+
init_agentShell();
|
|
8010
8727
|
init_mentions();
|
|
8011
8728
|
init_commands();
|
|
8729
|
+
function titleText(text) {
|
|
8730
|
+
const blocks = text.trim().split(/\n{2,}/);
|
|
8731
|
+
let i = 0;
|
|
8732
|
+
while (i < blocks.length && blocks[i].split("\n").every((l) => l === ">" || l.startsWith("> "))) i++;
|
|
8733
|
+
const rest = blocks.slice(i).join(" ").trim();
|
|
8734
|
+
return (rest || text.trim().replace(/^>\s?/gm, "")).replace(/\s+/g, " ").trim();
|
|
8735
|
+
}
|
|
8012
8736
|
var SLASH_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
8013
8737
|
var MAX_WARM_IDLE = 3;
|
|
8014
8738
|
var DELTA_TYPES = ["assistant_delta", "reasoning_delta", "tool_output_delta"];
|
|
@@ -8115,6 +8839,8 @@ var AgentManager = class {
|
|
|
8115
8839
|
if (!rt) return null;
|
|
8116
8840
|
return {
|
|
8117
8841
|
status: rt.status,
|
|
8842
|
+
// true while startTurn runs (until session.send has returned): a "steer" sent then is queued instead
|
|
8843
|
+
starting: rt.starting,
|
|
8118
8844
|
queue: rt.queue,
|
|
8119
8845
|
pending: [...rt.pending.values()].map((p) => ({
|
|
8120
8846
|
requestId: p.requestId,
|
|
@@ -8159,6 +8885,8 @@ var AgentManager = class {
|
|
|
8159
8885
|
for (const t of DELTA_TYPES) rt.coalescer.flushItem(deltaKey(t, itemId));
|
|
8160
8886
|
}
|
|
8161
8887
|
// ---- send ----
|
|
8888
|
+
/** clientId: the page's id for its optimistic bubble; it rides on the user_message event so the page can swap the
|
|
8889
|
+
* bubble for the real one without a duplicate or a jump */
|
|
8162
8890
|
async send(threadId, input, behavior = "queue") {
|
|
8163
8891
|
const meta = await this.mustThread(threadId);
|
|
8164
8892
|
const mentions = await this.resolveMentionRefs(threadId, meta.projectId, input.mentions);
|
|
@@ -8191,56 +8919,66 @@ var AgentManager = class {
|
|
|
8191
8919
|
text: input.text,
|
|
8192
8920
|
steered: true,
|
|
8193
8921
|
attachments: this.attachmentLabels(attachmentRefs),
|
|
8194
|
-
...mentions.length ? { mentions } : {}
|
|
8922
|
+
...mentions.length ? { mentions } : {},
|
|
8923
|
+
...input.clientId ? { clientId: input.clientId } : {}
|
|
8195
8924
|
});
|
|
8196
8925
|
return "steered";
|
|
8197
8926
|
} catch (e) {
|
|
8198
8927
|
log("info", `thread ${threadId} \u63D2\u8BDD\u5931\u8D25\uFF0C\u6539\u4E3A\u6392\u961F\uFF1A${String(e)}`);
|
|
8199
8928
|
}
|
|
8200
8929
|
}
|
|
8201
|
-
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs, ...mentions.length ? { mentions } : {} };
|
|
8930
|
+
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs, ...mentions.length ? { mentions } : {}, ...input.clientId ? { clientId: input.clientId } : {} };
|
|
8202
8931
|
rt.queue.push(q);
|
|
8203
8932
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
8204
8933
|
return "queued";
|
|
8205
8934
|
}
|
|
8206
|
-
await this.startTurn(threadId, input.text, attachmentRefs, mentions);
|
|
8935
|
+
await this.startTurn(threadId, input.text, attachmentRefs, mentions, input.clientId);
|
|
8207
8936
|
return "started";
|
|
8208
8937
|
}
|
|
8209
|
-
async startTurn(threadId, text, attachmentRefs, mentions = []) {
|
|
8938
|
+
async startTurn(threadId, text, attachmentRefs, mentions = [], clientId) {
|
|
8210
8939
|
const rt = this.rt(threadId);
|
|
8211
8940
|
rt.starting = true;
|
|
8212
8941
|
try {
|
|
8213
|
-
await this.startTurnInner(threadId, text, attachmentRefs, mentions);
|
|
8942
|
+
await this.startTurnInner(threadId, text, attachmentRefs, mentions, clientId);
|
|
8214
8943
|
} finally {
|
|
8215
8944
|
rt.starting = false;
|
|
8216
8945
|
}
|
|
8217
8946
|
}
|
|
8218
|
-
async startTurnInner(threadId, text, attachmentRefs, mentions) {
|
|
8947
|
+
async startTurnInner(threadId, text, attachmentRefs, mentions, clientId) {
|
|
8219
8948
|
const meta = await this.mustThread(threadId);
|
|
8220
8949
|
const rt = this.rt(threadId);
|
|
8221
|
-
const
|
|
8950
|
+
const allowDisabled = !!meta.providerSessionId;
|
|
8951
|
+
this.assertProviderAllowed(meta.provider, await this.settings.get(), { allowDisabled });
|
|
8952
|
+
await this.appendRaw(threadId, {
|
|
8953
|
+
type: "user_message",
|
|
8954
|
+
text,
|
|
8955
|
+
attachments: this.attachmentLabels(attachmentRefs),
|
|
8956
|
+
...mentions.length ? { mentions } : {},
|
|
8957
|
+
...clientId ? { clientId } : {}
|
|
8958
|
+
});
|
|
8959
|
+
const trimmed = titleText(text);
|
|
8960
|
+
if (meta.titleAuto && trimmed) {
|
|
8961
|
+
await this.threads.patch(threadId, { title: trimmed.slice(0, 40), titleAuto: true });
|
|
8962
|
+
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
8963
|
+
}
|
|
8222
8964
|
if (!rt.session) {
|
|
8223
8965
|
await this.setStatus(threadId, "initializing");
|
|
8224
8966
|
try {
|
|
8967
|
+
await this.assertProviderUsable(meta.provider, { allowDisabled });
|
|
8225
8968
|
await this.createSessionIfNeeded(threadId, { updateStatus: true });
|
|
8226
8969
|
} catch (e) {
|
|
8227
8970
|
await this.appendRaw(threadId, { type: "notice", level: "error", message: tr("notice.sessionStartFailed", { detail: String(e?.message ?? e) }) });
|
|
8228
8971
|
await this.appendRaw(threadId, { type: "turn_ended", status: "failed", error: String(e?.message ?? e) });
|
|
8972
|
+
await this.threads.patch(threadId, { lastTurnStatus: "failed" });
|
|
8229
8973
|
await this.setStatus(threadId, "error");
|
|
8230
8974
|
throw e;
|
|
8231
8975
|
}
|
|
8232
8976
|
}
|
|
8233
|
-
|
|
8234
|
-
if (
|
|
8235
|
-
|
|
8236
|
-
this.
|
|
8977
|
+
await agentShellReady();
|
|
8978
|
+
if (!rt.bashHinted && bashMissingFor(meta.provider)) {
|
|
8979
|
+
rt.bashHinted = true;
|
|
8980
|
+
await this.appendRaw(threadId, { type: "notice", level: "warn", code: "bash_missing", message: tr("notice.bashMissing") });
|
|
8237
8981
|
}
|
|
8238
|
-
await this.appendRaw(threadId, {
|
|
8239
|
-
type: "user_message",
|
|
8240
|
-
text,
|
|
8241
|
-
attachments: this.attachmentLabels(attachmentRefs),
|
|
8242
|
-
...mentions.length ? { mentions } : {}
|
|
8243
|
-
});
|
|
8244
8982
|
await this.setStatus(threadId, "running");
|
|
8245
8983
|
try {
|
|
8246
8984
|
const images = await this.resolveAttachments(attachmentRefs);
|
|
@@ -8342,9 +9080,21 @@ var AgentManager = class {
|
|
|
8342
9080
|
},
|
|
8343
9081
|
event: ev
|
|
8344
9082
|
};
|
|
9083
|
+
if (ev.type === "permission_request") {
|
|
9084
|
+
const meta = await this.threads.get(threadId);
|
|
9085
|
+
const auto = meta ? autoApproveOption(meta.mode, meta.provider, ev) : null;
|
|
9086
|
+
if (auto) {
|
|
9087
|
+
log("info", `thread ${threadId} \u81EA\u52A8\u540C\u610F ${ev.title.slice(0, 80)} \u2192 ${auto.id}`);
|
|
9088
|
+
await this.appendRaw(threadId, { ...ev, auto: true });
|
|
9089
|
+
await this.appendRaw(threadId, { type: "permission_resolved", requestId: ev.requestId, optionId: auto.id, auto: true });
|
|
9090
|
+
rt.session?.respond(ev.requestId, { optionId: auto.id });
|
|
9091
|
+
return;
|
|
9092
|
+
}
|
|
9093
|
+
}
|
|
8345
9094
|
const toolName = ev.detail?.toolName;
|
|
8346
9095
|
if (toolName && rt.allowAlways.has(toolName)) {
|
|
8347
|
-
|
|
9096
|
+
const allow = options.find((o) => o.kind === "allow_once") ?? options.find((o) => o.kind === "allow_always");
|
|
9097
|
+
rt.session?.respond(ev.requestId, { optionId: allow?.id ?? "accept" });
|
|
8348
9098
|
return;
|
|
8349
9099
|
}
|
|
8350
9100
|
rt.pending.set(ev.requestId, pending);
|
|
@@ -8375,7 +9125,7 @@ var AgentManager = class {
|
|
|
8375
9125
|
});
|
|
8376
9126
|
if (next) {
|
|
8377
9127
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
8378
|
-
await this.startTurn(threadId, next.text, next.attachments, next.mentions ?? []).catch((e) => {
|
|
9128
|
+
await this.startTurn(threadId, next.text, next.attachments, next.mentions ?? [], next.clientId).catch((e) => {
|
|
8379
9129
|
log("warn", `thread ${threadId} \u6392\u961F\u6D88\u606F\u542F\u52A8\u5931\u8D25\uFF1A${String(e)}`);
|
|
8380
9130
|
});
|
|
8381
9131
|
}
|
|
@@ -8426,6 +9176,22 @@ var AgentManager = class {
|
|
|
8426
9176
|
}
|
|
8427
9177
|
rt.session?.respond(requestId, answer);
|
|
8428
9178
|
}
|
|
9179
|
+
/** Answer every waiting tool approval with its allow-once option (full-auto just turned on). Returns how many. */
|
|
9180
|
+
async autoApprovePending(threadId, providerId, mode2) {
|
|
9181
|
+
const rt = this.rt(threadId);
|
|
9182
|
+
let n = 0;
|
|
9183
|
+
for (const p of [...rt.pending.values()]) {
|
|
9184
|
+
if (p.kind !== "permission") continue;
|
|
9185
|
+
const ev = p.event;
|
|
9186
|
+
const opt = autoApproveOption(mode2, providerId, { kind: ev.kind, options: p.options });
|
|
9187
|
+
if (!opt) continue;
|
|
9188
|
+
rt.pending.delete(p.requestId);
|
|
9189
|
+
await this.appendRaw(threadId, { type: "permission_resolved", requestId: p.requestId, optionId: opt.id, auto: true });
|
|
9190
|
+
rt.session?.respond(p.requestId, { optionId: opt.id });
|
|
9191
|
+
n++;
|
|
9192
|
+
}
|
|
9193
|
+
return n;
|
|
9194
|
+
}
|
|
8429
9195
|
// ---- interrupt ----
|
|
8430
9196
|
async interrupt(threadId) {
|
|
8431
9197
|
const rt = this.rt(threadId);
|
|
@@ -8470,8 +9236,9 @@ var AgentManager = class {
|
|
|
8470
9236
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
8471
9237
|
}
|
|
8472
9238
|
async setMode(threadId, mode2) {
|
|
8473
|
-
await this.threads.patch(threadId, { mode: mode2 });
|
|
9239
|
+
const meta = await this.threads.patch(threadId, { mode: mode2 });
|
|
8474
9240
|
const rt = this.rt(threadId);
|
|
9241
|
+
if (mode2 === FULL_AUTO) await this.autoApprovePending(threadId, meta.provider, mode2);
|
|
8475
9242
|
if (rt.session) await rt.session.setMode(mode2);
|
|
8476
9243
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
8477
9244
|
}
|
|
@@ -8510,7 +9277,8 @@ var AgentManager = class {
|
|
|
8510
9277
|
throw new ApiError("busy", tr("err.busySwitch"));
|
|
8511
9278
|
}
|
|
8512
9279
|
const events = await this.threads.readEvents(threadId, 0, 5e3);
|
|
8513
|
-
|
|
9280
|
+
const neverRan = !meta.providerSessionId && !events.some((e) => e.type === "turn_started" || e.type === "assistant_message" || e.type === "tool_call");
|
|
9281
|
+
if (events.some((e) => e.type === "user_message") && !neverRan) {
|
|
8514
9282
|
throw new ApiError("bad_params", tr("err.hasMessages"));
|
|
8515
9283
|
}
|
|
8516
9284
|
const p = await this.assertProviderUsable(providerId);
|
|
@@ -8523,14 +9291,16 @@ var AgentManager = class {
|
|
|
8523
9291
|
rt.commands = [];
|
|
8524
9292
|
}
|
|
8525
9293
|
rt.status = "idle";
|
|
9294
|
+
const guard = await guardLegacyMode(this.settings, providerId, clampMode(p.capabilities.modes, meta.mode));
|
|
8526
9295
|
await this.threads.patch(threadId, {
|
|
8527
9296
|
provider: providerId,
|
|
8528
9297
|
model: null,
|
|
8529
|
-
mode:
|
|
9298
|
+
mode: guard.mode,
|
|
8530
9299
|
effort: null,
|
|
8531
9300
|
providerSessionId: null,
|
|
8532
9301
|
status: "idle"
|
|
8533
9302
|
});
|
|
9303
|
+
if (guard.notice) await this.appendRaw(threadId, upgradeNotice());
|
|
8534
9304
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
8535
9305
|
}
|
|
8536
9306
|
async close(threadId) {
|
|
@@ -8647,6 +9417,7 @@ var AgentManager = class {
|
|
|
8647
9417
|
const settings = await this.settings.get();
|
|
8648
9418
|
const launch = launchFromSettings(settings, meta.provider);
|
|
8649
9419
|
rt.deltasOnly = !!provider.capabilities.deltasOnly;
|
|
9420
|
+
await agentShellReady();
|
|
8650
9421
|
rt.session = await provider.createSession({
|
|
8651
9422
|
threadId,
|
|
8652
9423
|
cwd: proj.path,
|
|
@@ -8675,10 +9446,10 @@ var AgentManager = class {
|
|
|
8675
9446
|
providerOf(meta) {
|
|
8676
9447
|
return getProvider(meta.provider);
|
|
8677
9448
|
}
|
|
8678
|
-
|
|
9449
|
+
/** The checks that start nothing: a known Agent, enabled (unless allowDisabled), not a planned one */
|
|
9450
|
+
assertProviderAllowed(providerId, s, opts = {}) {
|
|
8679
9451
|
const p = getProvider(providerId);
|
|
8680
9452
|
if (!p) throw new ApiError("bad_params", tr("provider.unknown", { id: providerId }));
|
|
8681
|
-
const s = await this.settings.get();
|
|
8682
9453
|
if (!opts.allowDisabled && !isProviderEnabled(s, providerId)) {
|
|
8683
9454
|
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: p.name }));
|
|
8684
9455
|
}
|
|
@@ -8686,16 +9457,21 @@ var AgentManager = class {
|
|
|
8686
9457
|
if (entry && !providerCreatable(entry.status)) {
|
|
8687
9458
|
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: p.name }));
|
|
8688
9459
|
}
|
|
9460
|
+
return p;
|
|
9461
|
+
}
|
|
9462
|
+
async assertProviderUsable(providerId, opts = {}) {
|
|
9463
|
+
const s = await this.settings.get();
|
|
9464
|
+
const p = this.assertProviderAllowed(providerId, s, opts);
|
|
8689
9465
|
const det = await cachedDetect(p, launchFromSettings(s, providerId));
|
|
8690
9466
|
if (!det.available) throw new ApiError("bad_params", det.reason ?? tr("provider.unavailable", { name: p.name }));
|
|
8691
9467
|
return p;
|
|
8692
9468
|
}
|
|
8693
|
-
async setStatus(threadId,
|
|
9469
|
+
async setStatus(threadId, status2) {
|
|
8694
9470
|
const rt = this.rt(threadId);
|
|
8695
|
-
rt.status =
|
|
8696
|
-
const meta = await this.threads.patch(threadId, { status });
|
|
9471
|
+
rt.status = status2;
|
|
9472
|
+
const meta = await this.threads.patch(threadId, { status: status2 });
|
|
8697
9473
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: meta });
|
|
8698
|
-
this.gw.publish(`thread:${threadId}`, { type: "status", status });
|
|
9474
|
+
this.gw.publish(`thread:${threadId}`, { type: "status", status: status2 });
|
|
8699
9475
|
}
|
|
8700
9476
|
// Append an event: seq is assigned in the store's serialized section, persisted, then pushed to thread:<id>
|
|
8701
9477
|
async appendRaw(threadId, ev) {
|
|
@@ -8740,6 +9516,7 @@ var AgentManager = class {
|
|
|
8740
9516
|
|
|
8741
9517
|
// daemon/src/git/service.ts
|
|
8742
9518
|
init_i18n();
|
|
9519
|
+
init_decode();
|
|
8743
9520
|
import { execFile as execFile5 } from "node:child_process";
|
|
8744
9521
|
import { existsSync as existsSync11 } from "node:fs";
|
|
8745
9522
|
import { cp, mkdir as mkdir7, readFile as readFile16, rename as rename4, rm, stat as stat10 } from "node:fs/promises";
|
|
@@ -8748,10 +9525,11 @@ var TIMEOUT2 = 15e3;
|
|
|
8748
9525
|
var DIFF_LIMIT = 1024 * 1024;
|
|
8749
9526
|
function runGit(cwd, args) {
|
|
8750
9527
|
return new Promise((res) => {
|
|
8751
|
-
execFile5("git", ["-c", "core.quotepath=false", ...args], { cwd, timeout: TIMEOUT2, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (e, stdout, stderr) => {
|
|
9528
|
+
execFile5("git", ["-c", "core.quotepath=false", ...args], { cwd, timeout: TIMEOUT2, maxBuffer: 8 * 1024 * 1024, windowsHide: true, encoding: "buffer" }, (e, stdout, stderr) => {
|
|
8752
9529
|
const code = e && typeof e.code === "number" ? e.code : e ? 1 : 0;
|
|
8753
9530
|
const missing = e?.code === "ENOENT";
|
|
8754
|
-
|
|
9531
|
+
const errText = stderr?.length ? decodeOutput(stderr) : "";
|
|
9532
|
+
res({ code, out: stdout ? stdout.toString("utf8") : "", err: missing ? tr("git.missing") : errText || (e?.message ?? "") });
|
|
8755
9533
|
});
|
|
8756
9534
|
});
|
|
8757
9535
|
}
|
|
@@ -8788,7 +9566,7 @@ var GitService = class {
|
|
|
8788
9566
|
if (!rec5 || rec5.length < 4) continue;
|
|
8789
9567
|
const index = rec5[0];
|
|
8790
9568
|
const worktree = rec5[1];
|
|
8791
|
-
let
|
|
9569
|
+
let path4 = rec5.slice(3);
|
|
8792
9570
|
let kind = "modified";
|
|
8793
9571
|
if (index === "?" && worktree === "?") kind = "untracked";
|
|
8794
9572
|
else if (index === "R" || worktree === "R") {
|
|
@@ -8796,7 +9574,7 @@ var GitService = class {
|
|
|
8796
9574
|
i++;
|
|
8797
9575
|
} else if (index === "A" || worktree === "A") kind = "added";
|
|
8798
9576
|
else if (index === "D" || worktree === "D") kind = "deleted";
|
|
8799
|
-
files.push({ path:
|
|
9577
|
+
files.push({ path: path4, index, worktree, kind });
|
|
8800
9578
|
}
|
|
8801
9579
|
return { isRepo: true, branch: br.out.trim() || void 0, files };
|
|
8802
9580
|
}
|
|
@@ -8998,19 +9776,19 @@ var FileService = class {
|
|
|
8998
9776
|
init_pathscan();
|
|
8999
9777
|
init_log();
|
|
9000
9778
|
init_i18n();
|
|
9001
|
-
|
|
9002
|
-
import {
|
|
9779
|
+
init_decode();
|
|
9780
|
+
import { spawn as spawn4, execFileSync as execFileSync2 } from "node:child_process";
|
|
9781
|
+
import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as existsSync12, statSync as statSync7 } from "node:fs";
|
|
9003
9782
|
import { createRequire as moduleRequire } from "node:module";
|
|
9004
9783
|
import { release } from "node:os";
|
|
9005
9784
|
import { dirname as dirname6, join as join18 } from "node:path";
|
|
9006
|
-
import { StringDecoder } from "node:string_decoder";
|
|
9007
9785
|
var MAX_TERMS_PER_PROJECT = 8;
|
|
9008
9786
|
var BUF_MAX = 2 * 1024 * 1024;
|
|
9009
9787
|
var SNAP_LINES = 5e3;
|
|
9010
9788
|
function isExecFile(file) {
|
|
9011
9789
|
try {
|
|
9012
9790
|
accessSync2(file, fsc.X_OK);
|
|
9013
|
-
return
|
|
9791
|
+
return statSync7(file).isFile();
|
|
9014
9792
|
} catch {
|
|
9015
9793
|
return false;
|
|
9016
9794
|
}
|
|
@@ -9127,7 +9905,7 @@ var TerminalService = class {
|
|
|
9127
9905
|
if (!term.pty) {
|
|
9128
9906
|
term.simple = true;
|
|
9129
9907
|
const args = simpleShellArgs(shell);
|
|
9130
|
-
const proc =
|
|
9908
|
+
const proc = spawn4(shell, args, {
|
|
9131
9909
|
cwd,
|
|
9132
9910
|
stdio: "pipe",
|
|
9133
9911
|
windowsHide: true,
|
|
@@ -9135,8 +9913,8 @@ var TerminalService = class {
|
|
|
9135
9913
|
detached: process.platform !== "win32"
|
|
9136
9914
|
});
|
|
9137
9915
|
term.proc = proc;
|
|
9138
|
-
const outDec = new
|
|
9139
|
-
const errDec = new
|
|
9916
|
+
const outDec = new OutputDecoder();
|
|
9917
|
+
const errDec = new OutputDecoder();
|
|
9140
9918
|
proc.stdout.on("data", (d) => this.onData(term, outDec.write(d)));
|
|
9141
9919
|
proc.stderr.on("data", (d) => this.onData(term, errDec.write(d)));
|
|
9142
9920
|
proc.on("exit", () => this.onExit(term));
|
|
@@ -9195,7 +9973,7 @@ var TerminalService = class {
|
|
|
9195
9973
|
const args = ["/pid", String(pid), "/T", "/F"];
|
|
9196
9974
|
try {
|
|
9197
9975
|
if (sync) execFileSync2("taskkill", args, { windowsHide: true, stdio: "ignore", timeout: 5e3 });
|
|
9198
|
-
else
|
|
9976
|
+
else spawn4("taskkill", args, { windowsHide: true, stdio: "ignore" }).on("error", () => {
|
|
9199
9977
|
});
|
|
9200
9978
|
} catch {
|
|
9201
9979
|
}
|
|
@@ -9284,12 +10062,12 @@ function augmentPath(env, nodeDir, platform = process.platform, home) {
|
|
|
9284
10062
|
const sep5 = win ? ";" : platform === process.platform ? delimiter : ":";
|
|
9285
10063
|
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
9286
10064
|
const cur = (env[key] ?? "").split(sep5).filter(Boolean);
|
|
9287
|
-
const
|
|
9288
|
-
const seen = new Set(cur.map(
|
|
10065
|
+
const norm2 = (d) => win ? d.replace(/[\\/]+$/, "").toLowerCase() : d.replace(/\/+$/, "");
|
|
10066
|
+
const seen = new Set(cur.map(norm2));
|
|
9289
10067
|
const out = [...cur];
|
|
9290
10068
|
for (const d of [nodeDir, ...extraBinDirs({ platform, env, home })]) {
|
|
9291
|
-
if (!d || seen.has(
|
|
9292
|
-
seen.add(
|
|
10069
|
+
if (!d || seen.has(norm2(d))) continue;
|
|
10070
|
+
seen.add(norm2(d));
|
|
9293
10071
|
out.push(d);
|
|
9294
10072
|
}
|
|
9295
10073
|
return out.join(sep5);
|
|
@@ -9300,7 +10078,7 @@ init_proxy();
|
|
|
9300
10078
|
|
|
9301
10079
|
// daemon/src/preview/ports.ts
|
|
9302
10080
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
9303
|
-
import { homedir as
|
|
10081
|
+
import { homedir as homedir10 } from "node:os";
|
|
9304
10082
|
import { basename as basename5, dirname as dirname7, isAbsolute as isAbsolute3, join as join19 } from "node:path";
|
|
9305
10083
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9306
10084
|
var MAGPIE_DEFAULT_PORTS = [3425, 3430];
|
|
@@ -9312,8 +10090,8 @@ function portOf(addr) {
|
|
|
9312
10090
|
return Number.isInteger(n) && n > 0 && n <= 65535 ? n : null;
|
|
9313
10091
|
}
|
|
9314
10092
|
function magpieDirFromInstall(p) {
|
|
9315
|
-
const
|
|
9316
|
-
const i =
|
|
10093
|
+
const norm2 = p.replace(/\\/g, "/");
|
|
10094
|
+
const i = norm2.lastIndexOf("/plugins/node_modules/");
|
|
9317
10095
|
if (i <= 0) return null;
|
|
9318
10096
|
return p.slice(0, i);
|
|
9319
10097
|
}
|
|
@@ -9336,7 +10114,7 @@ function magpieConfigDirs(env = process.env, where = {}) {
|
|
|
9336
10114
|
const x = env.XDG_CONFIG_HOME;
|
|
9337
10115
|
if (x && isAbsolute3(x)) add(join19(x, "magpie"));
|
|
9338
10116
|
const platform = where.platform ?? process.platform;
|
|
9339
|
-
const home = where.home ?? (env === process.env ?
|
|
10117
|
+
const home = where.home ?? (env === process.env ? homedir10() : platform === "win32" ? env.USERPROFILE : env.HOME);
|
|
9340
10118
|
if (home) add(join19(home, ".config", "magpie"));
|
|
9341
10119
|
return out;
|
|
9342
10120
|
}
|
|
@@ -9410,6 +10188,12 @@ async function main() {
|
|
|
9410
10188
|
} catch (e) {
|
|
9411
10189
|
log("error", "\u65E7\u6570\u636E\u8FC1\u79FB\u5931\u8D25", { err: String(e) });
|
|
9412
10190
|
}
|
|
10191
|
+
try {
|
|
10192
|
+
const { migrateAutoApprove13: migrateAutoApprove132 } = await Promise.resolve().then(() => (init_autoApprove13(), autoApprove13_exports));
|
|
10193
|
+
await migrateAutoApprove132({ settings, threads });
|
|
10194
|
+
} catch (e) {
|
|
10195
|
+
log("error", "\u5168\u90E8\u81EA\u52A8\u540C\u610F\u8FC1\u79FB\u5931\u8D25", { err: String(e) });
|
|
10196
|
+
}
|
|
9413
10197
|
const manager = new AgentManager(gateway, threads, projects, settings, attachments);
|
|
9414
10198
|
await manager.recoverOnStart();
|
|
9415
10199
|
const terminals = new TerminalService(gateway);
|
|
@@ -9459,6 +10243,7 @@ async function main() {
|
|
|
9459
10243
|
const bound = server.address();
|
|
9460
10244
|
if (bound && typeof bound === "object") cfg.port = bound.port;
|
|
9461
10245
|
process.stdout.write(JSON.stringify({ agentConsoleReady: true, port: cfg.port, pid: process.pid }) + "\n");
|
|
10246
|
+
void settings.get().then((s) => refreshAgentShell(typeof s.bashPath === "string" ? s.bashPath : ""));
|
|
9462
10247
|
void (async () => {
|
|
9463
10248
|
const { warmProviderCache: warmProviderCache2 } = await Promise.resolve().then(() => (init_warm(), warm_exports));
|
|
9464
10249
|
await warmProviderCache2(await settings.get());
|