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.
@@ -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 path2 from "node:path";
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 = spawn(a.cmd, a.args, {
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 ? path2.win32 : path2;
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 ? path2.win32 : path2.posix;
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 = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
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 norm = (x) => x.replace(/\\/g, "/").replace(/\/+$/, "");
1369
- const a = norm(s);
1370
- const b = norm(cwd);
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 + d.toString()).slice(-4096);
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 homedir3 } from "node:os";
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(path3) {
2355
- const text = await readFile5(path3, "utf8").catch(() => "");
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 path3 = join6(dirPath, f);
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(path3)) {
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(path3).catch(() => null);
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 path3 = join6(CLAUDE_ROOT, d, `${sessionId}.jsonl`);
2399
- if (!existsSync7(path3)) continue;
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(path3)) {
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(homedir3(), ".claude", "projects");
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
- p.stdout?.on("data", (c) => out += c.toString());
2444
- p.stderr?.on("data", (c) => out += c.toString());
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 homedir4 } from "node:os";
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 path3 of walk(CODEX_ROOT)) {
2559
- const m = /rollout-.*-([0-9a-f-]{36})\.jsonl$/i.exec(path3);
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(path3, "utf8").catch(() => "");
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(path3).catch(() => null);
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(homedir4(), ".codex", "sessions");
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 spawn2 } from "node:child_process";
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 = spawn2(argv.cmd, argv.args, {
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
- this.proc.stderr.setEncoding("utf8");
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 status = str4(step.status) ?? "pending";
3628
+ const status2 = str4(step.status) ?? "pending";
3055
3629
  return {
3056
3630
  text: str4(step.step) ?? "",
3057
- status: status === "completed" ? "done" : status === "inProgress" || status === "in_progress" ? "in_progress" : "pending"
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 status = str4(turn.status) ?? "completed";
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: status === "completed" ? "completed" : status === "interrupted" ? "interrupted" : "failed",
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 homedir5 } from "node:os";
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 path3 = join8(dir, f);
3725
+ const path4 = join8(dir, f);
3152
3726
  try {
3153
- const st = await stat6(path3);
3727
+ const st = await stat6(path4);
3154
3728
  if (!st.isFile()) continue;
3155
- const raw = await readFile7(path3, "utf8");
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 = homedir5();
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 homedir6 } from "node:os";
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
- p.stdout?.on("data", (c) => out += c.toString());
3554
- p.stderr?.on("data", (c) => out += c.toString());
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(homedir6(), ".codex");
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(status) {
3751
- return status !== "planned";
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 = d.toString().trim();
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
- p.stdout?.on("data", (c) => out += c.toString());
4551
- p.stderr?.on("data", (c) => out += c.toString());
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
- modes: ["default", "plan", "ask"]
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.toString();
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
- modeMap: { plan: "plan", default: "build" },
4914
- modeLabels: { plan: "Plan", default: "Build" }
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 statSync3 } from "node:fs";
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) && statSync3(p).isDirectory();
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 homedir7 } from "node:os";
5675
+ import { homedir as homedir8 } from "node:os";
5092
5676
  import { join as join11 } from "node:path";
5093
- async function readJson(path3) {
5677
+ async function readJson(path4) {
5094
5678
  try {
5095
- return JSON.parse(await readFile11(path3, "utf8"));
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(homedir7(), ".config", "magpie", "applied.json")) ?? {};
5685
+ return await readJson(join11(homedir8(), ".config", "magpie", "applied.json")) ?? {};
5102
5686
  }
5103
5687
  async function readClaudeEnv() {
5104
- const s = await readJson(join11(homedir7(), ".claude", "settings.json"));
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 withTimeout(p, ms, fb) {
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 = withTimeout(
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 = withTimeout(p.listModels(launch).catch(() => []), MODELS_TIMEOUT_MS, null).then(async (v) => {
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 norm = (s) => platform === "win32" ? p.resolve(s).toLowerCase() : p.resolve(s);
5298
- const r = p.relative(norm(root), norm(child));
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 path3 = (req.url ?? "/").split("?")[0];
5898
- if (path3 === PREVIEW_PING_PATH) {
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 (path3 === PREVIEW_SCRIPT_PATH) {
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 status = ur.statusCode ?? 502;
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" && status !== 204 && status !== 304;
6720
+ const html = ctype.includes("text/html") && !encoded && req.method !== "HEAD" && status2 !== 204 && status2 !== 304;
6037
6721
  if (!html) {
6038
- res.writeHead(status, h);
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(status, h);
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(status, h);
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 ready = l.target.secure ? "secureConnect" : "connect";
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(ready, () => {
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 statSync4 } from "node:fs";
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 homedir8 } from "node:os";
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 && statSync4(p.path).isDirectory();
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 mode2 = clampMode(provider?.capabilities.modes ?? [], p.mode);
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 path3 = await projectPath(p.projectId);
6505
- const r = await svc.terminals.create(p.projectId, path3, p.cols, p.rows);
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(homedir8(), "Downloads");
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 norm = self.replace(/\\/g, "/");
7181
- if (norm.endsWith("/daemon/dist/main.js") || norm.endsWith("/dist/main.js")) return self;
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 statSync5 } from "node:fs";
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(path3, name) {
7559
- if (!path3 || typeof path3 !== "string") throw new ApiError("bad_params", "\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
7560
- if (!existsSync10(path3) || !statSync5(path3).isDirectory()) {
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(path3);
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() || path3.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || path3,
7570
- path: path3,
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 provider = await this.assertProviderUsable(meta.provider, { allowDisabled: !!meta.providerSessionId });
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
- const trimmed = text.trim();
8234
- if (meta.titleAuto && trimmed) {
8235
- await this.threads.patch(threadId, { title: trimmed.slice(0, 40), titleAuto: true });
8236
- this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
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
- rt.session?.respond(ev.requestId, { optionId: "accept" });
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
- if (events.some((e) => e.type === "user_message")) {
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: clampMode(p.capabilities.modes, meta.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
- async assertProviderUsable(providerId, opts = {}) {
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, status) {
9469
+ async setStatus(threadId, status2) {
8694
9470
  const rt = this.rt(threadId);
8695
- rt.status = 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
- res({ code, out: String(stdout ?? ""), err: missing ? tr("git.missing") : String(stderr || (e?.message ?? "")) });
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 path3 = rec5.slice(3);
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: path3, index, worktree, kind });
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
- import { spawn as spawn3, execFileSync as execFileSync2 } from "node:child_process";
9002
- import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as existsSync12, statSync as statSync6 } from "node:fs";
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 statSync6(file).isFile();
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 = spawn3(shell, args, {
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 StringDecoder("utf8");
9139
- const errDec = new StringDecoder("utf8");
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 spawn3("taskkill", args, { windowsHide: true, stdio: "ignore" }).on("error", () => {
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 norm = (d) => win ? d.replace(/[\\/]+$/, "").toLowerCase() : d.replace(/\/+$/, "");
9288
- const seen = new Set(cur.map(norm));
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(norm(d))) continue;
9292
- seen.add(norm(d));
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 homedir9 } from "node:os";
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 norm = p.replace(/\\/g, "/");
9316
- const i = norm.lastIndexOf("/plugins/node_modules/");
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 ? homedir9() : platform === "win32" ? env.USERPROFILE : env.HOME);
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());