magpie-agent-console 1.1.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -8
- package/app/app.css +1 -1
- package/app/app.js +113 -90
- package/daemon/dist/main.js +2152 -293
- package/daemon/dist/main.js.map +1 -1
- package/index.mjs +4 -0
- package/lib/gate.mjs +25 -5
- package/lib/wrapper.mjs +184 -0
- package/package.json +3 -2
- package/page/boot.js +4 -0
package/daemon/dist/main.js
CHANGED
|
@@ -91,6 +91,31 @@ var init_log = __esm({
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
// daemon/src/security.ts
|
|
95
|
+
import { timingSafeEqual, createHash } from "node:crypto";
|
|
96
|
+
function checkHost(host, port) {
|
|
97
|
+
if (!host) return { ok: false, code: 421, message: "Host \u4E0D\u5BF9" };
|
|
98
|
+
const h = host.toLowerCase();
|
|
99
|
+
if (h === `127.0.0.1:${port}` || h === `localhost:${port}`) return { ok: true };
|
|
100
|
+
return { ok: false, code: 421, message: "Host \u4E0D\u5BF9" };
|
|
101
|
+
}
|
|
102
|
+
function classifyOrigin(origin, port) {
|
|
103
|
+
if (origin === void 0 || origin === "") return "none";
|
|
104
|
+
if (origin === "null") return "opaque";
|
|
105
|
+
const o = origin.toLowerCase();
|
|
106
|
+
if (o === `http://127.0.0.1:${port}` || o === `http://localhost:${port}`) return "self";
|
|
107
|
+
return "foreign";
|
|
108
|
+
}
|
|
109
|
+
function tokenEqual(a, b) {
|
|
110
|
+
const ha = createHash("sha256").update(a).digest();
|
|
111
|
+
const hb = createHash("sha256").update(b).digest();
|
|
112
|
+
return timingSafeEqual(ha, hb);
|
|
113
|
+
}
|
|
114
|
+
var init_security = __esm({
|
|
115
|
+
"daemon/src/security.ts"() {
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
94
119
|
// daemon/src/util/i18n.ts
|
|
95
120
|
function normalizeLang(raw) {
|
|
96
121
|
const s = String(raw ?? "").trim().toLowerCase();
|
|
@@ -121,6 +146,9 @@ var init_i18n = __esm({
|
|
|
121
146
|
"notice.daemonRestart": "\u540E\u53F0\u5DF2\u91CD\u542F\uFF0C\u672C\u8F6E\u5DF2\u4E2D\u65AD",
|
|
122
147
|
"notice.sessionStartFailed": "\u4F1A\u8BDD\u542F\u52A8\u5931\u8D25\uFF1A{detail}",
|
|
123
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",
|
|
124
152
|
"notice.effortNextTurn": "\u601D\u8003\u5F3A\u5EA6\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548",
|
|
125
153
|
"notice.effortResetOnModel": "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u601D\u8003\u5F3A\u5EA6\uFF0C\u5DF2\u6062\u590D\u4E3A Default",
|
|
126
154
|
"turn.agentExit": "Agent \u8FDB\u7A0B\u610F\u5916\u9000\u51FA",
|
|
@@ -129,6 +157,7 @@ var init_i18n = __esm({
|
|
|
129
157
|
"notice.acpImageByPathSwitched": "{agent} \u662F\u5728\u53E6\u4E00\u4E2A\u6A21\u578B\u4E0A\u542F\u52A8\u7684\uFF0C\u53EA\u5BF9\u90A3\u4E2A\u6A21\u578B\u63A5\u6536\u56FE\u7247\uFF0C\u8FD9\u6B21\u6539\u4E3A\u53D1\u9001\u56FE\u7247\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u5F53\u524D\u6A21\u578B\u80FD\u770B\u56FE\u65F6\u5B83\u4F1A\u7528 read_image \u6253\u5F00\uFF09\uFF1A{paths}",
|
|
130
158
|
"notice.acpSessionNotRestored": "{agent} \u4E0D\u80FD\u6062\u590D\u4E0A\u4E00\u8F6E\u5BF9\u8BDD\uFF08\u5B83\u7684 ACP \u65E2\u4E0D\u652F\u6301 session/load \u4E5F\u4E0D\u652F\u6301 session/resume\uFF09\uFF0C\u5DF2\u65B0\u5F00\u4E00\u8F6E\uFF1B\u5148\u524D\u7684\u4E0A\u4E0B\u6587\u4E0D\u4F1A\u5E26\u56DE\u3002",
|
|
131
159
|
"notice.attachmentIssue": "\u9644\u4EF6\u95EE\u9898\uFF1A{detail}",
|
|
160
|
+
"notice.mentionSkipped": "\u8FD9\u4E9B\u8DEF\u5F84\u4E0D\u5728\u9879\u76EE\u91CC\u6216\u5DF2\u4E0D\u5B58\u5728\uFF0C\u6CA1\u6709\u53D1\u7ED9 Agent\uFF1A{paths}",
|
|
132
161
|
"claude.notFound": "\u627E\u4E0D\u5230 claude\uFF08{detail}\uFF09\u3002\u672C\u63D2\u4EF6\u4E0D\u9644\u5E26 Claude Code\uFF0C\u8BF7\u5148\u5B89\u88C5\uFF1A{hint}",
|
|
133
162
|
"claude.startFailed": "\u65E0\u6CD5\u542F\u52A8 claude\uFF1A{detail}",
|
|
134
163
|
"claude.exited": "claude \u8FDB\u7A0B\u610F\u5916\u9000\u51FA\uFF08{code}\uFF09",
|
|
@@ -146,6 +175,9 @@ var init_i18n = __esm({
|
|
|
146
175
|
"provider.unavailable": "{name} \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u7528",
|
|
147
176
|
"provider.unknown": "\u672A\u77E5 Agent\uFF1A{id}",
|
|
148
177
|
"provider.timeout": "\u68C0\u6D4B\u8D85\u65F6",
|
|
178
|
+
"err.previewUrl": "\u9884\u89C8\u53EA\u652F\u6301\u672C\u673A\u5730\u5740\uFF08localhost\u3001127.0.0.1 \u7B49\uFF09",
|
|
179
|
+
"err.previewDenied": "\u8FD9\u4E2A\u7AEF\u53E3\u4E0D\u80FD\u9884\u89C8\uFF08\u662F Agent \u63A7\u5236\u53F0\u6216 Magpie \u81EA\u5DF1\u7684\u7AEF\u53E3\uFF09",
|
|
180
|
+
"err.previewUnavailable": "\u9884\u89C8\u4E0D\u53EF\u7528",
|
|
149
181
|
"err.projectMissing": "\u9879\u76EE\u4E0D\u5B58\u5728",
|
|
150
182
|
"err.threadMissing": "\u5BF9\u8BDD\u4E0D\u5B58\u5728",
|
|
151
183
|
"err.dirMissing": "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939",
|
|
@@ -227,6 +259,9 @@ var init_i18n = __esm({
|
|
|
227
259
|
"notice.daemonRestart": "Daemon restarted \u2014 this turn was interrupted",
|
|
228
260
|
"notice.sessionStartFailed": "Session start failed: {detail}",
|
|
229
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.",
|
|
230
265
|
"notice.effortNextTurn": "Reasoning effort saved \u2014 takes effect on the next turn",
|
|
231
266
|
"notice.effortResetOnModel": "This model does not support that effort level; reset to Default",
|
|
232
267
|
"turn.agentExit": "Agent process exited unexpectedly",
|
|
@@ -235,6 +270,7 @@ var init_i18n = __esm({
|
|
|
235
270
|
"notice.acpImageByPathSwitched": "{agent} was started on another model and takes image blocks only for that one, so the image went as its file path (it opens it with read_image when this model sees images): {paths}",
|
|
236
271
|
"notice.acpSessionNotRestored": "{agent} can't restore the previous conversation (its ACP advertises neither session/load nor session/resume); started a new one \u2014 prior context won't come back.",
|
|
237
272
|
"notice.attachmentIssue": "Attachment issue: {detail}",
|
|
273
|
+
"notice.mentionSkipped": "Not sent to the Agent (outside the project or gone): {paths}",
|
|
238
274
|
"claude.notFound": "claude was not found ({detail}). Claude Code is not bundled with this plugin; install it first: {hint}",
|
|
239
275
|
"claude.startFailed": "Could not start claude: {detail}",
|
|
240
276
|
"claude.exited": "claude exited unexpectedly ({code})",
|
|
@@ -252,6 +288,9 @@ var init_i18n = __esm({
|
|
|
252
288
|
"provider.unavailable": "{name} is not installed or not available",
|
|
253
289
|
"provider.unknown": "Unknown agent: {id}",
|
|
254
290
|
"provider.timeout": "Detection timed out",
|
|
291
|
+
"err.previewUrl": "Preview only supports local addresses (localhost, 127.0.0.1, \u2026)",
|
|
292
|
+
"err.previewDenied": "This port can't be previewed (it belongs to Agent Console or Magpie)",
|
|
293
|
+
"err.previewUnavailable": "Preview is unavailable",
|
|
255
294
|
"err.projectMissing": "Project not found",
|
|
256
295
|
"err.threadMissing": "Conversation not found",
|
|
257
296
|
"err.dirMissing": "The folder does not exist or is not a folder",
|
|
@@ -678,12 +717,7 @@ async function scriptPlan(ctx) {
|
|
|
678
717
|
emit({
|
|
679
718
|
type: "plan",
|
|
680
719
|
itemId: "plan1",
|
|
681
|
-
markdown: "## \u5B9E\u65BD\u8BA1\u5212\n\n1. \u6539 `src/index.ts` \u5165\u53E3\n2. \u66F4\u65B0\u6D4B\u8BD5\n3. \u8DD1\u4E00\u904D e2e"
|
|
682
|
-
entries: [
|
|
683
|
-
{ step: "\u6539 src/index.ts \u5165\u53E3", status: "pending" },
|
|
684
|
-
{ step: "\u66F4\u65B0\u6D4B\u8BD5", status: "pending" },
|
|
685
|
-
{ step: "\u8DD1\u4E00\u904D e2e", status: "pending" }
|
|
686
|
-
]
|
|
720
|
+
markdown: "## \u5B9E\u65BD\u8BA1\u5212\n\n1. \u6539 `src/index.ts` \u5165\u53E3\n2. \u66F4\u65B0\u6D4B\u8BD5\n3. \u8DD1\u4E00\u904D e2e"
|
|
687
721
|
});
|
|
688
722
|
const requestId = "perm-" + rid();
|
|
689
723
|
emit({
|
|
@@ -774,6 +808,11 @@ function pickScript(text) {
|
|
|
774
808
|
if (t.startsWith("/error")) return scriptError;
|
|
775
809
|
if (t.startsWith("/long")) return scriptLong;
|
|
776
810
|
if (t.startsWith("/exit")) return scriptExit;
|
|
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;
|
|
777
816
|
return scriptDefault;
|
|
778
817
|
}
|
|
779
818
|
async function sleepAbortable(ms, ctx) {
|
|
@@ -789,6 +828,185 @@ function chunkText(s, n) {
|
|
|
789
828
|
for (let i = 0; i < s.length; i += n) out.push(s.slice(i, i + n));
|
|
790
829
|
return out;
|
|
791
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
|
+
}
|
|
991
|
+
async function scriptServe(ctx) {
|
|
992
|
+
const emit = (e) => ctx.emit(e);
|
|
993
|
+
const reply = [
|
|
994
|
+
"\u5F00\u53D1\u670D\u52A1\u5668\u5DF2\u7ECF\u542F\u52A8\uFF1Ahttp://localhost:5173/ \uFF0C\u63A5\u53E3\u5728 127.0.0.1:5173/api.json\u3002",
|
|
995
|
+
"",
|
|
996
|
+
"- \u5728\u9884\u89C8\u91CC\u6253\u5F00 [\u9996\u9875](http://localhost:5173/) \u6216 [about](http://localhost:5173/about.html)",
|
|
997
|
+
"- \u6587\u6863\uFF1A[Vite](https://vite.dev/guide/)",
|
|
998
|
+
"",
|
|
999
|
+
"```",
|
|
1000
|
+
" VITE v5.4.21 ready in 139 ms",
|
|
1001
|
+
" \u279C Local: http://localhost:5173/",
|
|
1002
|
+
"```"
|
|
1003
|
+
].join("\n");
|
|
1004
|
+
for (const ch of chunkText(reply, 40)) {
|
|
1005
|
+
if (await sleepAbortable(20, ctx)) return;
|
|
1006
|
+
emit({ type: "assistant_delta", itemId: "sv", text: ch });
|
|
1007
|
+
}
|
|
1008
|
+
emit({ type: "assistant_message", itemId: "sv", text: reply });
|
|
1009
|
+
}
|
|
792
1010
|
function mockReply(ctx) {
|
|
793
1011
|
const base = [
|
|
794
1012
|
"\u597D\u7684\uFF0C\u6211\u5DF2\u7ECF\u6309\u4F60\u7684\u8981\u6C42\u6539\u597D\u4E86\u3002",
|
|
@@ -818,6 +1036,8 @@ function createMockProvider() {
|
|
|
818
1036
|
name: "Mock\uFF08\u6F14\u793A\uFF09",
|
|
819
1037
|
capabilities: mockCapabilities,
|
|
820
1038
|
async detect() {
|
|
1039
|
+
const d = Number(process.env.AGENT_CONSOLE_MOCK_DETECT_MS) || 0;
|
|
1040
|
+
if (d > 0) await sleep(d);
|
|
821
1041
|
return { available: true, bin: "mock", version: "demo" };
|
|
822
1042
|
},
|
|
823
1043
|
async listModels() {
|
|
@@ -828,6 +1048,8 @@ function createMockProvider() {
|
|
|
828
1048
|
},
|
|
829
1049
|
async createSession(o) {
|
|
830
1050
|
mockProviderMetrics.createSessionCalls++;
|
|
1051
|
+
const d = Number(process.env.AGENT_CONSOLE_MOCK_START_MS) || 0;
|
|
1052
|
+
if (d > 0) await sleep(d);
|
|
831
1053
|
return new MockSession(o);
|
|
832
1054
|
}
|
|
833
1055
|
};
|
|
@@ -931,21 +1153,21 @@ var init_mock = __esm({
|
|
|
931
1153
|
}
|
|
932
1154
|
}
|
|
933
1155
|
respond(requestId, answer) {
|
|
934
|
-
const
|
|
935
|
-
if (
|
|
1156
|
+
const resolve4 = this.pendingReq.get(requestId);
|
|
1157
|
+
if (resolve4) {
|
|
936
1158
|
this.pendingReq.delete(requestId);
|
|
937
|
-
|
|
1159
|
+
resolve4(answer);
|
|
938
1160
|
}
|
|
939
1161
|
}
|
|
940
1162
|
waitFor(requestId) {
|
|
941
|
-
return new Promise((
|
|
942
|
-
this.pendingReq.set(requestId,
|
|
1163
|
+
return new Promise((resolve4) => {
|
|
1164
|
+
this.pendingReq.set(requestId, resolve4);
|
|
943
1165
|
});
|
|
944
1166
|
}
|
|
945
1167
|
async interrupt() {
|
|
946
1168
|
this.interruptFlag = true;
|
|
947
|
-
for (const [id2,
|
|
948
|
-
|
|
1169
|
+
for (const [id2, resolve4] of this.pendingReq) {
|
|
1170
|
+
resolve4({ cancelled: true });
|
|
949
1171
|
this.pendingReq.delete(id2);
|
|
950
1172
|
}
|
|
951
1173
|
await sleep(120);
|
|
@@ -973,7 +1195,7 @@ var init_mock = __esm({
|
|
|
973
1195
|
}
|
|
974
1196
|
async close() {
|
|
975
1197
|
this.closed = true;
|
|
976
|
-
for (const [,
|
|
1198
|
+
for (const [, resolve4] of this.pendingReq) resolve4({ cancelled: true });
|
|
977
1199
|
this.pendingReq.clear();
|
|
978
1200
|
this.q.close();
|
|
979
1201
|
}
|
|
@@ -1037,8 +1259,8 @@ function extraBinDirs({ platform = process.platform, env = process.env, home = h
|
|
|
1037
1259
|
return dirs;
|
|
1038
1260
|
}
|
|
1039
1261
|
function searchDirs({ platform = process.platform, env = process.env, home = homedir2(), extra = true } = {}) {
|
|
1040
|
-
const
|
|
1041
|
-
const raw = String(envGet(env, "PATH") ?? "").split(
|
|
1262
|
+
const sep5 = platform === "win32" ? ";" : ":";
|
|
1263
|
+
const raw = String(envGet(env, "PATH") ?? "").split(sep5).map((d) => d.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean);
|
|
1042
1264
|
const all = extra ? [...raw, ...extraBinDirs({ platform, env, home })] : raw;
|
|
1043
1265
|
const seen = /* @__PURE__ */ new Set();
|
|
1044
1266
|
const out = [];
|
|
@@ -1088,11 +1310,251 @@ var init_pathscan = __esm({
|
|
|
1088
1310
|
}
|
|
1089
1311
|
});
|
|
1090
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
|
+
|
|
1091
1553
|
// daemon/src/util/bin.ts
|
|
1092
|
-
import { execFileSync, spawn } from "node:child_process";
|
|
1554
|
+
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
1093
1555
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
1094
1556
|
import { existsSync as existsSync4 } from "node:fs";
|
|
1095
|
-
import
|
|
1557
|
+
import path3 from "node:path";
|
|
1096
1558
|
function which(name) {
|
|
1097
1559
|
return findOnPath(name);
|
|
1098
1560
|
}
|
|
@@ -1138,9 +1600,11 @@ function trackTree(child) {
|
|
|
1138
1600
|
function spawnBin(r, args, opts = {}) {
|
|
1139
1601
|
const a = argvFor(r, args);
|
|
1140
1602
|
const { group, ...rest } = opts;
|
|
1141
|
-
const child =
|
|
1603
|
+
const child = spawn2(a.cmd, a.args, {
|
|
1142
1604
|
windowsHide: true,
|
|
1143
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),
|
|
1144
1608
|
...a.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
|
|
1145
1609
|
...group && !IS_WIN ? { detached: true } : {}
|
|
1146
1610
|
});
|
|
@@ -1149,7 +1613,7 @@ function spawnBin(r, args, opts = {}) {
|
|
|
1149
1613
|
}
|
|
1150
1614
|
function shimTarget(cmdText, cmdDir, deps = {}) {
|
|
1151
1615
|
const exists = deps.exists ?? existsSync4;
|
|
1152
|
-
const P = deps.win ?? IS_WIN ?
|
|
1616
|
+
const P = deps.win ?? IS_WIN ? path3.win32 : path3;
|
|
1153
1617
|
const cands = [];
|
|
1154
1618
|
const re = /"([^"]+?\.(?:js|mjs|cjs|exe|cmd|bat))"/gi;
|
|
1155
1619
|
let m;
|
|
@@ -1200,7 +1664,7 @@ function resolveBinUncached(name) {
|
|
|
1200
1664
|
async function resolveBinWith(name, deps = {}) {
|
|
1201
1665
|
const platform = deps.platform ?? process.platform;
|
|
1202
1666
|
const win = platform === "win32";
|
|
1203
|
-
const P = win ?
|
|
1667
|
+
const P = win ? path3.win32 : path3.posix;
|
|
1204
1668
|
const exists = deps.exists ?? existsSync4;
|
|
1205
1669
|
const hits = (deps.find ?? which)(name);
|
|
1206
1670
|
if (!hits.length) return { ok: false, reason: tr("bin.notOnPath", { name }) };
|
|
@@ -1241,7 +1705,7 @@ function killTree(child, { graceMs = 3e3 } = {}) {
|
|
|
1241
1705
|
const pid = child.pid;
|
|
1242
1706
|
if (IS_WIN) {
|
|
1243
1707
|
return new Promise((resolvePromise) => {
|
|
1244
|
-
const tk =
|
|
1708
|
+
const tk = spawn2("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
|
|
1245
1709
|
tk.on("error", () => {
|
|
1246
1710
|
try {
|
|
1247
1711
|
child.kill();
|
|
@@ -1296,6 +1760,7 @@ var init_bin = __esm({
|
|
|
1296
1760
|
"daemon/src/util/bin.ts"() {
|
|
1297
1761
|
init_pathscan();
|
|
1298
1762
|
init_i18n();
|
|
1763
|
+
init_agentShell();
|
|
1299
1764
|
IS_WIN = process.platform === "win32";
|
|
1300
1765
|
cache = /* @__PURE__ */ new Map();
|
|
1301
1766
|
CACHE_MS = 60 * 1e3;
|
|
@@ -1306,17 +1771,28 @@ var init_bin = __esm({
|
|
|
1306
1771
|
});
|
|
1307
1772
|
|
|
1308
1773
|
// daemon/src/agents/kinds.ts
|
|
1774
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1775
|
+
import { isAbsolute, resolve as resolve2 } from "node:path";
|
|
1309
1776
|
function relPath(p, cwd) {
|
|
1310
1777
|
if (!p) return "";
|
|
1311
1778
|
const s = String(p);
|
|
1312
1779
|
if (!cwd) return s;
|
|
1313
|
-
const
|
|
1314
|
-
const a =
|
|
1315
|
-
const b =
|
|
1780
|
+
const norm2 = (x) => x.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1781
|
+
const a = norm2(s);
|
|
1782
|
+
const b = norm2(cwd);
|
|
1316
1783
|
if (a.toLowerCase().startsWith(b.toLowerCase() + "/")) return a.slice(b.length + 1);
|
|
1317
1784
|
if (a.toLowerCase() === b.toLowerCase()) return ".";
|
|
1318
1785
|
return s;
|
|
1319
1786
|
}
|
|
1787
|
+
function targetExists(input, cwd = "") {
|
|
1788
|
+
const p = String(input.file_path ?? input.path ?? "");
|
|
1789
|
+
if (!p) return false;
|
|
1790
|
+
try {
|
|
1791
|
+
return existsSync5(isAbsolute(p) || !cwd ? p : resolve2(cwd, p));
|
|
1792
|
+
} catch {
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1320
1796
|
function callInfo(name, input = {}, cwd = "") {
|
|
1321
1797
|
const t = String(name || "");
|
|
1322
1798
|
const f = (kind, title, detail) => ({ kind, title, detail });
|
|
@@ -1521,7 +1997,7 @@ function mapToolUse(blk, cwd) {
|
|
|
1521
1997
|
const input = rec(blk.input);
|
|
1522
1998
|
const name = str(blk.name) ?? "tool";
|
|
1523
1999
|
const id2 = str(blk.id) ?? "";
|
|
1524
|
-
const info = callInfo(name, { ...input, _exists: true }, cwd);
|
|
2000
|
+
const info = callInfo(name, { ...input, _exists: name === "Write" ? targetExists(input, cwd) : true }, cwd);
|
|
1525
2001
|
let detail = info.detail;
|
|
1526
2002
|
if (info.kind === "edit" && (input.old_string !== void 0 || Array.isArray(input.edits))) {
|
|
1527
2003
|
detail = editDetail({ path: info.detail.path, old: input.old_string, new: input.new_string, edits: input.edits });
|
|
@@ -1573,7 +2049,7 @@ __export(attachmentRefs_exports, {
|
|
|
1573
2049
|
resolveAttachmentRefs: () => resolveAttachmentRefs,
|
|
1574
2050
|
resolveImagePath: () => resolveImagePath
|
|
1575
2051
|
});
|
|
1576
|
-
import { existsSync as
|
|
2052
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1577
2053
|
function bindAttachmentStore(store) {
|
|
1578
2054
|
globalAttachmentStore = store;
|
|
1579
2055
|
}
|
|
@@ -1596,7 +2072,7 @@ async function resolveAttachmentRefs(store, refs) {
|
|
|
1596
2072
|
out.push({ path: viaGlobal, mime: a.mime });
|
|
1597
2073
|
continue;
|
|
1598
2074
|
}
|
|
1599
|
-
if (
|
|
2075
|
+
if (existsSync6(a.path)) {
|
|
1600
2076
|
out.push(a);
|
|
1601
2077
|
continue;
|
|
1602
2078
|
}
|
|
@@ -1605,7 +2081,7 @@ async function resolveAttachmentRefs(store, refs) {
|
|
|
1605
2081
|
return out;
|
|
1606
2082
|
}
|
|
1607
2083
|
async function resolveImagePath(ref) {
|
|
1608
|
-
if (
|
|
2084
|
+
if (existsSync6(ref)) return ref;
|
|
1609
2085
|
const p = await lookupAttachmentPath(ref);
|
|
1610
2086
|
if (p) return p;
|
|
1611
2087
|
throw new Error(tr("attachment.missing", { ref }));
|
|
@@ -1716,6 +2192,162 @@ var init_launch = __esm({
|
|
|
1716
2192
|
}
|
|
1717
2193
|
});
|
|
1718
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
|
+
|
|
1719
2351
|
// daemon/src/providers/claude/cli.ts
|
|
1720
2352
|
function claudeArgs(o) {
|
|
1721
2353
|
const args = [
|
|
@@ -1749,6 +2381,7 @@ var init_cli = __esm({
|
|
|
1749
2381
|
init_launch();
|
|
1750
2382
|
init_bin();
|
|
1751
2383
|
init_queue();
|
|
2384
|
+
init_decode();
|
|
1752
2385
|
SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._:\[\]\/@+-]{0,199}$/;
|
|
1753
2386
|
ClaudeCli = class {
|
|
1754
2387
|
constructor(o, canUseTool) {
|
|
@@ -1763,20 +2396,21 @@ var init_cli = __esm({
|
|
|
1763
2396
|
});
|
|
1764
2397
|
this.child.stdout?.setEncoding("utf8");
|
|
1765
2398
|
this.child.stdout?.on("data", (d) => this.onData(d));
|
|
2399
|
+
const errDec = new OutputDecoder();
|
|
1766
2400
|
this.child.stderr?.on("data", (d) => {
|
|
1767
|
-
this.stderrTail = (this.stderrTail +
|
|
2401
|
+
this.stderrTail = (this.stderrTail + errDec.write(d)).slice(-4096);
|
|
1768
2402
|
});
|
|
1769
2403
|
this.child.stdin?.on("error", () => {
|
|
1770
2404
|
});
|
|
1771
|
-
this.exited = new Promise((
|
|
2405
|
+
this.exited = new Promise((resolve4) => {
|
|
1772
2406
|
this.child.on("error", (e) => {
|
|
1773
2407
|
this.spawnError = e;
|
|
1774
2408
|
this.finish();
|
|
1775
|
-
|
|
2409
|
+
resolve4({ code: null, signal: null });
|
|
1776
2410
|
});
|
|
1777
2411
|
this.child.on("close", (code, signal) => {
|
|
1778
2412
|
this.finish();
|
|
1779
|
-
|
|
2413
|
+
resolve4({ code, signal });
|
|
1780
2414
|
});
|
|
1781
2415
|
});
|
|
1782
2416
|
this.initialized = this.request({ subtype: "initialize" }, 6e4).catch(() => null);
|
|
@@ -1893,8 +2527,8 @@ var init_cli = __esm({
|
|
|
1893
2527
|
request(request, timeoutMs = 1e4) {
|
|
1894
2528
|
if (this.dead) return Promise.reject(new Error("claude is not running"));
|
|
1895
2529
|
const id2 = `ac_${++this.seq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1896
|
-
return new Promise((
|
|
1897
|
-
const p = { resolve:
|
|
2530
|
+
return new Promise((resolve4, reject) => {
|
|
2531
|
+
const p = { resolve: resolve4, reject };
|
|
1898
2532
|
p.timer = setTimeout(() => {
|
|
1899
2533
|
if (this.pending.delete(id2)) reject(new Error(`claude did not answer ${String(request.subtype)} in ${Math.round(timeoutMs / 1e3)} s`));
|
|
1900
2534
|
}, timeoutMs);
|
|
@@ -2066,7 +2700,7 @@ var init_session = __esm({
|
|
|
2066
2700
|
if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
|
|
2067
2701
|
return this.handleQuestion(requestId, input, signal);
|
|
2068
2702
|
}
|
|
2069
|
-
const info = callInfo(toolName, { ...input, _exists: true }, this.o.cwd);
|
|
2703
|
+
const info = callInfo(toolName, { ...input, _exists: toolName === "Write" ? targetExists(input, this.o.cwd) : true }, this.o.cwd);
|
|
2070
2704
|
let detail = { ...info.detail, toolName };
|
|
2071
2705
|
if (info.kind === "edit" && (input.old_string !== void 0 || Array.isArray(input.edits))) {
|
|
2072
2706
|
detail = { ...editDetail({ path: info.detail.path, old: input.old_string, new: input.new_string, edits: input.edits }), toolName };
|
|
@@ -2083,12 +2717,12 @@ var init_session = __esm({
|
|
|
2083
2717
|
{ id: "deny", label: tr("perm.deny"), kind: "reject_once" }
|
|
2084
2718
|
]
|
|
2085
2719
|
});
|
|
2086
|
-
return new Promise((
|
|
2087
|
-
this.pending.set(requestId, { kind: "permission", resolve: (a) =>
|
|
2720
|
+
return new Promise((resolve4) => {
|
|
2721
|
+
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve4(this.toPermissionDecision(a, input)) });
|
|
2088
2722
|
signal.addEventListener("abort", () => {
|
|
2089
2723
|
if (this.pending.delete(requestId)) {
|
|
2090
2724
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2091
|
-
|
|
2725
|
+
resolve4({ behavior: "deny", message: "Cancelled by the user", interrupt: true });
|
|
2092
2726
|
}
|
|
2093
2727
|
}, { once: true });
|
|
2094
2728
|
});
|
|
@@ -2115,12 +2749,12 @@ var init_session = __esm({
|
|
|
2115
2749
|
{ id: "reject", label: tr("perm.deny"), kind: "reject_once" }
|
|
2116
2750
|
]
|
|
2117
2751
|
});
|
|
2118
|
-
const answer = await new Promise((
|
|
2119
|
-
this.pending.set(requestId, { kind: "permission", resolve: (a) =>
|
|
2752
|
+
const answer = await new Promise((resolve4) => {
|
|
2753
|
+
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve4(a) });
|
|
2120
2754
|
signal.addEventListener("abort", () => {
|
|
2121
2755
|
if (this.pending.delete(requestId)) {
|
|
2122
2756
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2123
|
-
|
|
2757
|
+
resolve4({ cancelled: true });
|
|
2124
2758
|
}
|
|
2125
2759
|
}, { once: true });
|
|
2126
2760
|
});
|
|
@@ -2138,12 +2772,12 @@ var init_session = __esm({
|
|
|
2138
2772
|
}
|
|
2139
2773
|
async handleQuestion(requestId, input, signal) {
|
|
2140
2774
|
this.emit({ type: "question", requestId, questions: input.questions });
|
|
2141
|
-
const answer = await new Promise((
|
|
2142
|
-
this.pending.set(requestId, { kind: "question", resolve: (a) =>
|
|
2775
|
+
const answer = await new Promise((resolve4) => {
|
|
2776
|
+
this.pending.set(requestId, { kind: "question", resolve: (a) => resolve4(a) });
|
|
2143
2777
|
signal.addEventListener("abort", () => {
|
|
2144
2778
|
if (this.pending.delete(requestId)) {
|
|
2145
2779
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2146
|
-
|
|
2780
|
+
resolve4({ cancelled: true });
|
|
2147
2781
|
}
|
|
2148
2782
|
}, { once: true });
|
|
2149
2783
|
});
|
|
@@ -2265,9 +2899,9 @@ var init_models = __esm({
|
|
|
2265
2899
|
|
|
2266
2900
|
// daemon/src/history/import.ts
|
|
2267
2901
|
import { readdir, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
2268
|
-
import { existsSync as
|
|
2902
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2269
2903
|
import { join as join6 } from "node:path";
|
|
2270
|
-
import { homedir as
|
|
2904
|
+
import { homedir as homedir4 } from "node:os";
|
|
2271
2905
|
function parseLine(line) {
|
|
2272
2906
|
let r;
|
|
2273
2907
|
try {
|
|
@@ -2287,8 +2921,8 @@ function parseLine(line) {
|
|
|
2287
2921
|
}
|
|
2288
2922
|
return { cwd, userText };
|
|
2289
2923
|
}
|
|
2290
|
-
async function* linesOf(
|
|
2291
|
-
const text = await readFile5(
|
|
2924
|
+
async function* linesOf(path4) {
|
|
2925
|
+
const text = await readFile5(path4, "utf8").catch(() => "");
|
|
2292
2926
|
for (const line of text.split("\n")) {
|
|
2293
2927
|
if (line.trim()) yield line;
|
|
2294
2928
|
}
|
|
@@ -2300,11 +2934,11 @@ async function listClaudeHistory(cwd) {
|
|
|
2300
2934
|
const dirPath = join6(CLAUDE_ROOT, d);
|
|
2301
2935
|
const files = (await readdir(dirPath).catch(() => [])).filter((f) => f.endsWith(".jsonl"));
|
|
2302
2936
|
for (const f of files) {
|
|
2303
|
-
const
|
|
2937
|
+
const path4 = join6(dirPath, f);
|
|
2304
2938
|
let match = false;
|
|
2305
2939
|
let title = "";
|
|
2306
2940
|
let count = 0;
|
|
2307
|
-
for await (const line of linesOf(
|
|
2941
|
+
for await (const line of linesOf(path4)) {
|
|
2308
2942
|
const p = parseLine(line);
|
|
2309
2943
|
if (!p) continue;
|
|
2310
2944
|
if (!match) {
|
|
@@ -2316,7 +2950,7 @@ async function listClaudeHistory(cwd) {
|
|
|
2316
2950
|
if (p.userText) count++;
|
|
2317
2951
|
}
|
|
2318
2952
|
if (!match) continue;
|
|
2319
|
-
const s = await stat4(
|
|
2953
|
+
const s = await stat4(path4).catch(() => null);
|
|
2320
2954
|
out.push({
|
|
2321
2955
|
sessionId: f.replace(/\.jsonl$/, ""),
|
|
2322
2956
|
title: title || tr("history.untitled"),
|
|
@@ -2331,11 +2965,11 @@ async function listClaudeHistory(cwd) {
|
|
|
2331
2965
|
async function readClaudeHistory(sessionId, cwd) {
|
|
2332
2966
|
const dirs = await readdir(CLAUDE_ROOT).catch(() => []);
|
|
2333
2967
|
for (const d of dirs) {
|
|
2334
|
-
const
|
|
2335
|
-
if (!
|
|
2968
|
+
const path4 = join6(CLAUDE_ROOT, d, `${sessionId}.jsonl`);
|
|
2969
|
+
if (!existsSync7(path4)) continue;
|
|
2336
2970
|
const msgs = [];
|
|
2337
2971
|
let matched = false;
|
|
2338
|
-
for await (const line of linesOf(
|
|
2972
|
+
for await (const line of linesOf(path4)) {
|
|
2339
2973
|
const p = parseLine(line);
|
|
2340
2974
|
if (!matched) {
|
|
2341
2975
|
if (p?.cwd && samePath(p.cwd, cwd)) matched = true;
|
|
@@ -2358,7 +2992,7 @@ var init_import = __esm({
|
|
|
2358
2992
|
"daemon/src/history/import.ts"() {
|
|
2359
2993
|
init_paths();
|
|
2360
2994
|
init_i18n();
|
|
2361
|
-
CLAUDE_ROOT = join6(
|
|
2995
|
+
CLAUDE_ROOT = join6(homedir4(), ".claude", "projects");
|
|
2362
2996
|
rec2 = (v) => v && typeof v === "object" ? v : {};
|
|
2363
2997
|
str2 = (v) => typeof v === "string" ? v : void 0;
|
|
2364
2998
|
}
|
|
@@ -2376,8 +3010,9 @@ function runVersion(r, args, timeoutMs = 5e3) {
|
|
|
2376
3010
|
}
|
|
2377
3011
|
reject(new Error("timeout"));
|
|
2378
3012
|
}, timeoutMs);
|
|
2379
|
-
|
|
2380
|
-
p.
|
|
3013
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
3014
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
3015
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
2381
3016
|
p.on("error", reject);
|
|
2382
3017
|
p.on("close", (code) => {
|
|
2383
3018
|
clearTimeout(t);
|
|
@@ -2387,14 +3022,14 @@ function runVersion(r, args, timeoutMs = 5e3) {
|
|
|
2387
3022
|
});
|
|
2388
3023
|
}
|
|
2389
3024
|
function createClaudeProvider(opts = {}) {
|
|
2390
|
-
const
|
|
3025
|
+
const resolve4 = opts.resolve ?? resolveBin;
|
|
2391
3026
|
const resolveCustom = opts.resolve ? (l) => opts.resolve(l.cmd) : resolveLaunch;
|
|
2392
3027
|
return {
|
|
2393
3028
|
id: "claude",
|
|
2394
3029
|
name: "Claude Code",
|
|
2395
3030
|
capabilities: claudeCapabilities,
|
|
2396
3031
|
async detect(launch) {
|
|
2397
|
-
const bin = launch ? await resolveCustom(launch) : await
|
|
3032
|
+
const bin = launch ? await resolveCustom(launch) : await resolve4("claude");
|
|
2398
3033
|
if (!bin.ok) return { available: false, reason: bin.reason, installHint: INSTALL_HINT };
|
|
2399
3034
|
try {
|
|
2400
3035
|
const version = await runVersion(bin, [...launch?.prefixArgs ?? [], "--version"]);
|
|
@@ -2407,7 +3042,7 @@ function createClaudeProvider(opts = {}) {
|
|
|
2407
3042
|
return listClaudeModels();
|
|
2408
3043
|
},
|
|
2409
3044
|
async createSession(o) {
|
|
2410
|
-
const bin = o.launch ? await resolveCustom(o.launch) : await
|
|
3045
|
+
const bin = o.launch ? await resolveCustom(o.launch) : await resolve4("claude");
|
|
2411
3046
|
if (!bin.ok) throw new Error(tr("claude.notFound", { detail: bin.reason, hint: INSTALL_HINT }));
|
|
2412
3047
|
return new ClaudeSession({
|
|
2413
3048
|
threadId: o.threadId,
|
|
@@ -2455,6 +3090,7 @@ var init_provider = __esm({
|
|
|
2455
3090
|
init_map();
|
|
2456
3091
|
init_i18n();
|
|
2457
3092
|
init_launch();
|
|
3093
|
+
init_decode();
|
|
2458
3094
|
INSTALL_HINT = "npm i -g @anthropic-ai/claude-code";
|
|
2459
3095
|
claudeCapabilities = {
|
|
2460
3096
|
resume: true,
|
|
@@ -2474,7 +3110,7 @@ var init_provider = __esm({
|
|
|
2474
3110
|
// daemon/src/history/codex.ts
|
|
2475
3111
|
import { readdir as readdir2, readFile as readFile6, stat as stat5 } from "node:fs/promises";
|
|
2476
3112
|
import { join as join7 } from "node:path";
|
|
2477
|
-
import { homedir as
|
|
3113
|
+
import { homedir as homedir5 } from "node:os";
|
|
2478
3114
|
function textOf(content, kind) {
|
|
2479
3115
|
if (!Array.isArray(content)) return "";
|
|
2480
3116
|
return content.filter((p) => p.type === kind || p.type === "input_text" || p.type === "output_text").map((p) => str3(p.text) ?? "").join("\n").trim();
|
|
@@ -2491,11 +3127,11 @@ async function* walk(dir, depth = 0) {
|
|
|
2491
3127
|
}
|
|
2492
3128
|
async function listCodexHistory(cwd) {
|
|
2493
3129
|
const out = [];
|
|
2494
|
-
for await (const
|
|
2495
|
-
const m = /rollout-.*-([0-9a-f-]{36})\.jsonl$/i.exec(
|
|
3130
|
+
for await (const path4 of walk(CODEX_ROOT)) {
|
|
3131
|
+
const m = /rollout-.*-([0-9a-f-]{36})\.jsonl$/i.exec(path4);
|
|
2496
3132
|
const sessionId = m?.[1];
|
|
2497
3133
|
if (!sessionId) continue;
|
|
2498
|
-
const text = await readFile6(
|
|
3134
|
+
const text = await readFile6(path4, "utf8").catch(() => "");
|
|
2499
3135
|
let match = false;
|
|
2500
3136
|
let title = "";
|
|
2501
3137
|
let count = 0;
|
|
@@ -2526,7 +3162,7 @@ async function listCodexHistory(cwd) {
|
|
|
2526
3162
|
}
|
|
2527
3163
|
}
|
|
2528
3164
|
if (!match) continue;
|
|
2529
|
-
const s = await stat5(
|
|
3165
|
+
const s = await stat5(path4).catch(() => null);
|
|
2530
3166
|
out.push({ sessionId, title: title || tr("history.untitled"), updatedAt: s ? Math.round(s.mtimeMs) : 0, messageCount: count });
|
|
2531
3167
|
}
|
|
2532
3168
|
out.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
@@ -2615,7 +3251,7 @@ var init_codex = __esm({
|
|
|
2615
3251
|
"daemon/src/history/codex.ts"() {
|
|
2616
3252
|
init_paths();
|
|
2617
3253
|
init_i18n();
|
|
2618
|
-
CODEX_ROOT = join7(
|
|
3254
|
+
CODEX_ROOT = join7(homedir5(), ".codex", "sessions");
|
|
2619
3255
|
rec3 = (v) => v && typeof v === "object" ? v : {};
|
|
2620
3256
|
str3 = (v) => typeof v === "string" ? v : void 0;
|
|
2621
3257
|
WRAPPERS = /^\s*<(environment_context|user_instructions|skills_instructions|permissions instructions)>/i;
|
|
@@ -2623,12 +3259,14 @@ var init_codex = __esm({
|
|
|
2623
3259
|
});
|
|
2624
3260
|
|
|
2625
3261
|
// daemon/src/providers/codex/rpc.ts
|
|
2626
|
-
import { spawn as
|
|
3262
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2627
3263
|
var CodexRpcError, CodexProcessExitedError, CodexRpc;
|
|
2628
3264
|
var init_rpc = __esm({
|
|
2629
3265
|
"daemon/src/providers/codex/rpc.ts"() {
|
|
2630
3266
|
init_log();
|
|
2631
3267
|
init_bin();
|
|
3268
|
+
init_decode();
|
|
3269
|
+
init_agentShell();
|
|
2632
3270
|
CodexRpcError = class extends Error {
|
|
2633
3271
|
code;
|
|
2634
3272
|
constructor(message, code) {
|
|
@@ -2651,9 +3289,9 @@ var init_rpc = __esm({
|
|
|
2651
3289
|
onNotification;
|
|
2652
3290
|
onExit;
|
|
2653
3291
|
constructor(argv, opts = {}) {
|
|
2654
|
-
this.proc =
|
|
3292
|
+
this.proc = spawn3(argv.cmd, argv.args, {
|
|
2655
3293
|
cwd: opts.cwd,
|
|
2656
|
-
env: opts.env ?? process.env,
|
|
3294
|
+
env: withAgentShell(opts.env ?? process.env),
|
|
2657
3295
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2658
3296
|
windowsHide: true,
|
|
2659
3297
|
...argv.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
|
|
@@ -2663,9 +3301,9 @@ var init_rpc = __esm({
|
|
|
2663
3301
|
trackTree(this.proc);
|
|
2664
3302
|
this.proc.stdout.setEncoding("utf8");
|
|
2665
3303
|
this.proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
2666
|
-
|
|
3304
|
+
const errDec = new OutputDecoder();
|
|
2667
3305
|
this.proc.stderr.on("data", (chunk) => {
|
|
2668
|
-
this.stderrTail = (this.stderrTail + chunk).slice(-4096);
|
|
3306
|
+
this.stderrTail = (this.stderrTail + errDec.write(chunk)).slice(-4096);
|
|
2669
3307
|
});
|
|
2670
3308
|
this.proc.on("error", () => this.failAll());
|
|
2671
3309
|
this.proc.on("close", (code) => {
|
|
@@ -2987,10 +3625,10 @@ function mapNotification(method, params, st) {
|
|
|
2987
3625
|
const steps = Array.isArray(params.plan) ? params.plan : [];
|
|
2988
3626
|
const items = steps.map((s) => {
|
|
2989
3627
|
const step = rec4(s);
|
|
2990
|
-
const
|
|
3628
|
+
const status2 = str4(step.status) ?? "pending";
|
|
2991
3629
|
return {
|
|
2992
3630
|
text: str4(step.step) ?? "",
|
|
2993
|
-
status:
|
|
3631
|
+
status: status2 === "completed" ? "done" : status2 === "inProgress" || status2 === "in_progress" ? "in_progress" : "pending"
|
|
2994
3632
|
};
|
|
2995
3633
|
});
|
|
2996
3634
|
if (items.length) out.push({ type: "todo", items });
|
|
@@ -3018,12 +3656,12 @@ function mapNotification(method, params, st) {
|
|
|
3018
3656
|
}
|
|
3019
3657
|
case "turn/completed": {
|
|
3020
3658
|
const turn = rec4(params.turn);
|
|
3021
|
-
const
|
|
3659
|
+
const status2 = str4(turn.status) ?? "completed";
|
|
3022
3660
|
const error = rec4(turn.error);
|
|
3023
3661
|
out.push({
|
|
3024
3662
|
type: "turn_ended",
|
|
3025
3663
|
turnId: str4(turn.id) ?? turnId ?? st.currentTurnId ?? "",
|
|
3026
|
-
status:
|
|
3664
|
+
status: status2 === "completed" ? "completed" : status2 === "interrupted" ? "interrupted" : "failed",
|
|
3027
3665
|
durationMs: typeof turn.durationMs === "number" ? turn.durationMs : void 0,
|
|
3028
3666
|
error: str4(error.message),
|
|
3029
3667
|
filesChanged: st.filesChanged || void 0
|
|
@@ -3053,7 +3691,7 @@ var init_map2 = __esm({
|
|
|
3053
3691
|
|
|
3054
3692
|
// daemon/src/providers/codex/commands.ts
|
|
3055
3693
|
import { readdir as readdir3, readFile as readFile7, stat as stat6 } from "node:fs/promises";
|
|
3056
|
-
import { homedir as
|
|
3694
|
+
import { homedir as homedir6 } from "node:os";
|
|
3057
3695
|
import { basename as basename3, join as join8 } from "node:path";
|
|
3058
3696
|
function parseFrontMatter(raw) {
|
|
3059
3697
|
if (!raw.startsWith("---\n")) return { meta: {}, body: raw };
|
|
@@ -3084,11 +3722,11 @@ async function readPromptFiles(dir) {
|
|
|
3084
3722
|
}
|
|
3085
3723
|
for (const f of names) {
|
|
3086
3724
|
if (!f.endsWith(".md")) continue;
|
|
3087
|
-
const
|
|
3725
|
+
const path4 = join8(dir, f);
|
|
3088
3726
|
try {
|
|
3089
|
-
const st = await stat6(
|
|
3727
|
+
const st = await stat6(path4);
|
|
3090
3728
|
if (!st.isFile()) continue;
|
|
3091
|
-
const raw = await readFile7(
|
|
3729
|
+
const raw = await readFile7(path4, "utf8");
|
|
3092
3730
|
const { meta, body } = parseFrontMatter(raw);
|
|
3093
3731
|
const name = basename3(f, ".md");
|
|
3094
3732
|
out.push({
|
|
@@ -3132,7 +3770,7 @@ async function readSkillDirs(root) {
|
|
|
3132
3770
|
return out;
|
|
3133
3771
|
}
|
|
3134
3772
|
async function loadCodexSlashEntries(cwd, opts) {
|
|
3135
|
-
const home =
|
|
3773
|
+
const home = homedir6();
|
|
3136
3774
|
const parts = await Promise.all([
|
|
3137
3775
|
opts?.skipHome ? Promise.resolve([]) : readPromptFiles(join8(home, ".codex", "prompts")),
|
|
3138
3776
|
readPromptFiles(join8(cwd, ".codex", "prompts")),
|
|
@@ -3423,8 +4061,8 @@ var init_session2 = __esm({
|
|
|
3423
4061
|
}
|
|
3424
4062
|
}
|
|
3425
4063
|
waitFor(requestId) {
|
|
3426
|
-
return new Promise((
|
|
3427
|
-
this.pending.set(requestId, { kind: "permission", resolve:
|
|
4064
|
+
return new Promise((resolve4) => {
|
|
4065
|
+
this.pending.set(requestId, { kind: "permission", resolve: resolve4 });
|
|
3428
4066
|
});
|
|
3429
4067
|
}
|
|
3430
4068
|
respond(requestId, answer) {
|
|
@@ -3472,7 +4110,7 @@ var init_types = __esm({
|
|
|
3472
4110
|
});
|
|
3473
4111
|
|
|
3474
4112
|
// daemon/src/providers/codex/provider.ts
|
|
3475
|
-
import { homedir as
|
|
4113
|
+
import { homedir as homedir7 } from "node:os";
|
|
3476
4114
|
import { join as join9 } from "node:path";
|
|
3477
4115
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
3478
4116
|
function runVersion2(r, args, timeoutMs = 5e3) {
|
|
@@ -3486,8 +4124,9 @@ function runVersion2(r, args, timeoutMs = 5e3) {
|
|
|
3486
4124
|
}
|
|
3487
4125
|
reject(new Error("timeout"));
|
|
3488
4126
|
}, timeoutMs);
|
|
3489
|
-
|
|
3490
|
-
p.
|
|
4127
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
4128
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
4129
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
3491
4130
|
p.on("error", reject);
|
|
3492
4131
|
p.on("close", (code) => {
|
|
3493
4132
|
clearTimeout(t);
|
|
@@ -3573,7 +4212,7 @@ function parseCodexConfig(text) {
|
|
|
3573
4212
|
return { modelProvider: provider, model: typeof model === "string" ? model : null, providerModels: [...models2] };
|
|
3574
4213
|
}
|
|
3575
4214
|
async function readCodexConfig() {
|
|
3576
|
-
const home = process.env.CODEX_HOME?.trim() || join9(
|
|
4215
|
+
const home = process.env.CODEX_HOME?.trim() || join9(homedir7(), ".codex");
|
|
3577
4216
|
const file = home.toLowerCase().endsWith(".toml") ? home : join9(home, "config.toml");
|
|
3578
4217
|
try {
|
|
3579
4218
|
return parseCodexConfig(await readFile8(file, "utf8"));
|
|
@@ -3658,6 +4297,7 @@ var init_provider2 = __esm({
|
|
|
3658
4297
|
init_i18n();
|
|
3659
4298
|
init_launch();
|
|
3660
4299
|
init_types();
|
|
4300
|
+
init_decode();
|
|
3661
4301
|
INSTALL_HINT2 = "npm i -g @openai/codex";
|
|
3662
4302
|
codexCapabilities = {
|
|
3663
4303
|
resume: true,
|
|
@@ -3683,8 +4323,8 @@ var init_provider2 = __esm({
|
|
|
3683
4323
|
function catalogById(id2) {
|
|
3684
4324
|
return PROVIDER_CATALOG.find((p) => p.id === id2);
|
|
3685
4325
|
}
|
|
3686
|
-
function providerCreatable(
|
|
3687
|
-
return
|
|
4326
|
+
function providerCreatable(status2) {
|
|
4327
|
+
return status2 !== "planned";
|
|
3688
4328
|
}
|
|
3689
4329
|
var PROVIDER_CATALOG;
|
|
3690
4330
|
var init_catalog = __esm({
|
|
@@ -3943,7 +4583,7 @@ var init_sessionCaps = __esm({
|
|
|
3943
4583
|
import { readFile as readFile10, stat as stat7 } from "node:fs/promises";
|
|
3944
4584
|
import { basename as basename4 } from "node:path";
|
|
3945
4585
|
import { pathToFileURL } from "node:url";
|
|
3946
|
-
async function buildAcpPrompt(text, attachments, caps) {
|
|
4586
|
+
async function buildAcpPrompt(text, attachments, caps, mentions = []) {
|
|
3947
4587
|
const media = [];
|
|
3948
4588
|
const links = [];
|
|
3949
4589
|
const pathLines = [];
|
|
@@ -4006,6 +4646,9 @@ async function buildAcpPrompt(text, attachments, caps) {
|
|
|
4006
4646
|
const blocks = [];
|
|
4007
4647
|
if (fullText) blocks.push({ type: "text", text: fullText });
|
|
4008
4648
|
blocks.push(...media, ...links);
|
|
4649
|
+
for (const m of mentions) {
|
|
4650
|
+
blocks.push({ type: "resource_link", uri: pathToFileURL(m.path).href, name: basename4(m.path) || m.path });
|
|
4651
|
+
}
|
|
4009
4652
|
return { blocks, imagesByPath, warnings };
|
|
4010
4653
|
}
|
|
4011
4654
|
var MAX_INLINE_MEDIA_BYTES, MAX_INLINE_TOTAL_BYTES, MAX_EMBED_TEXT_BYTES, isText;
|
|
@@ -4037,6 +4680,7 @@ var init_session3 = __esm({
|
|
|
4037
4680
|
init_prompt();
|
|
4038
4681
|
init_i18n();
|
|
4039
4682
|
init_log();
|
|
4683
|
+
init_decode();
|
|
4040
4684
|
AcpSession = class {
|
|
4041
4685
|
constructor(o) {
|
|
4042
4686
|
this.o = o;
|
|
@@ -4082,8 +4726,9 @@ var init_session3 = __esm({
|
|
|
4082
4726
|
if (!bin.ok) throw new Error(bin.reason);
|
|
4083
4727
|
try {
|
|
4084
4728
|
this.child = spawnResolved(bin, launchArgv(this.o.acpArgs, this.o.launch), { cwd: this.o.cwd });
|
|
4729
|
+
const errDec = new OutputDecoder();
|
|
4085
4730
|
this.child.stderr?.on("data", (d) => {
|
|
4086
|
-
const t =
|
|
4731
|
+
const t = errDec.write(d).trim();
|
|
4087
4732
|
if (t) log("info", `[${this.o.binName} acp] ${t.slice(0, 500)}`);
|
|
4088
4733
|
});
|
|
4089
4734
|
this.child.on("error", (e) => this.failInit(e));
|
|
@@ -4230,7 +4875,7 @@ var init_session3 = __esm({
|
|
|
4230
4875
|
return hit?.id ?? want;
|
|
4231
4876
|
}
|
|
4232
4877
|
if (this.o.flavor === "cursor") {
|
|
4233
|
-
const map = { default: "agent", plan: "plan", ask: "ask" };
|
|
4878
|
+
const map = { default: "agent", bypassPermissions: "agent", plan: "plan", ask: "ask" };
|
|
4234
4879
|
const want = map[mode2] ?? "agent";
|
|
4235
4880
|
const avail = this.active?.modes?.availableModes ?? [];
|
|
4236
4881
|
const hit = avail.find((m) => m.id === want || m.name.toLowerCase() === want);
|
|
@@ -4309,8 +4954,8 @@ var init_session3 = __esm({
|
|
|
4309
4954
|
await this.applyCursorAutoReviewConfig(false);
|
|
4310
4955
|
}
|
|
4311
4956
|
waitPerm(requestId) {
|
|
4312
|
-
return new Promise((
|
|
4313
|
-
this.permPending.set(requestId,
|
|
4957
|
+
return new Promise((resolve4) => {
|
|
4958
|
+
this.permPending.set(requestId, resolve4);
|
|
4314
4959
|
});
|
|
4315
4960
|
}
|
|
4316
4961
|
respond(requestId, answer) {
|
|
@@ -4347,7 +4992,7 @@ var init_session3 = __esm({
|
|
|
4347
4992
|
this.q.push({ type: "turn_ended", turnId: this.turnId, status: "completed" });
|
|
4348
4993
|
return;
|
|
4349
4994
|
}
|
|
4350
|
-
const built = await buildAcpPrompt(text, attachments, this.promptCaps);
|
|
4995
|
+
const built = await buildAcpPrompt(text, attachments, this.promptCaps, input.mentions ?? []);
|
|
4351
4996
|
const agent = this.o.providerName ?? this.o.binName;
|
|
4352
4997
|
if (built.imagesByPath.length && !this.promptCaps.image) {
|
|
4353
4998
|
const switched = this.o.imagesFollowStartModel && this.initCaps.image && this.o.model !== this.startModel;
|
|
@@ -4480,8 +5125,9 @@ function runVersion3(r, args, timeoutMs = 8e3) {
|
|
|
4480
5125
|
}
|
|
4481
5126
|
reject(new Error("timeout"));
|
|
4482
5127
|
}, timeoutMs);
|
|
4483
|
-
|
|
4484
|
-
p.
|
|
5128
|
+
const od = new OutputDecoder(), ed = new OutputDecoder();
|
|
5129
|
+
p.stdout?.on("data", (c) => out += od.write(c));
|
|
5130
|
+
p.stderr?.on("data", (c) => out += ed.write(c));
|
|
4485
5131
|
p.on("error", reject);
|
|
4486
5132
|
p.on("close", (code) => {
|
|
4487
5133
|
clearTimeout(t);
|
|
@@ -4496,7 +5142,7 @@ async function resolveProviderBin(cfg2) {
|
|
|
4496
5142
|
}
|
|
4497
5143
|
function createAcpProvider(cfg2) {
|
|
4498
5144
|
const base = cfg2.capabilities ?? acpCapabilities;
|
|
4499
|
-
const ORDER = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
5145
|
+
const ORDER = ["plan", "default", "acceptEdits", "bypassPermissions", "ask"];
|
|
4500
5146
|
const caps = cfg2.modeMap ? { ...base, modes: ORDER.filter((m) => cfg2.modeMap[m] !== void 0), modeLabels: cfg2.modeLabels } : base;
|
|
4501
5147
|
return {
|
|
4502
5148
|
id: cfg2.id,
|
|
@@ -4561,6 +5207,7 @@ var init_provider3 = __esm({
|
|
|
4561
5207
|
init_types();
|
|
4562
5208
|
init_session3();
|
|
4563
5209
|
init_promptCaps();
|
|
5210
|
+
init_decode();
|
|
4564
5211
|
acpCapabilities = {
|
|
4565
5212
|
resume: true,
|
|
4566
5213
|
interrupt: true,
|
|
@@ -4576,7 +5223,9 @@ var init_provider3 = __esm({
|
|
|
4576
5223
|
cursorAcpCapabilities = {
|
|
4577
5224
|
...acpCapabilities,
|
|
4578
5225
|
questions: true,
|
|
4579
|
-
|
|
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"]
|
|
4580
5229
|
};
|
|
4581
5230
|
}
|
|
4582
5231
|
});
|
|
@@ -4685,8 +5334,9 @@ function probeAcpRaw(r, acpArgs, timeoutMs = 3e4, extMethod) {
|
|
|
4685
5334
|
child.on("close", () => finish(new Error("ACP \u8FDB\u7A0B\u63D0\u524D\u9000\u51FA")));
|
|
4686
5335
|
child.stderr?.on("data", () => {
|
|
4687
5336
|
});
|
|
5337
|
+
child.stdout?.setEncoding("utf8");
|
|
4688
5338
|
child.stdout?.on("data", (d) => {
|
|
4689
|
-
buf += d
|
|
5339
|
+
buf += d;
|
|
4690
5340
|
let n;
|
|
4691
5341
|
while ((n = buf.indexOf("\n")) >= 0) {
|
|
4692
5342
|
const line = buf.slice(0, n).trim();
|
|
@@ -4843,8 +5493,9 @@ function acpConfigForEntry(entry, appVersion) {
|
|
|
4843
5493
|
...base,
|
|
4844
5494
|
acpArgs: ["acp"],
|
|
4845
5495
|
fetchModels: acpProbeModels(["acp"], entry.id),
|
|
4846
|
-
|
|
4847
|
-
|
|
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" }
|
|
4848
5499
|
};
|
|
4849
5500
|
case "copilot":
|
|
4850
5501
|
return {
|
|
@@ -4966,13 +5617,13 @@ var init_pickFolderWin = __esm({
|
|
|
4966
5617
|
|
|
4967
5618
|
// daemon/src/util/pickFolder.ts
|
|
4968
5619
|
import { execFile as execFile2 } from "node:child_process";
|
|
4969
|
-
import { existsSync as
|
|
5620
|
+
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
4970
5621
|
function appleScriptString(s) {
|
|
4971
5622
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
4972
5623
|
}
|
|
4973
5624
|
function isDir(p) {
|
|
4974
5625
|
try {
|
|
4975
|
-
return !!p &&
|
|
5626
|
+
return !!p && existsSync8(p) && statSync4(p).isDirectory();
|
|
4976
5627
|
} catch {
|
|
4977
5628
|
return false;
|
|
4978
5629
|
}
|
|
@@ -5021,20 +5672,20 @@ var init_pickFolder = __esm({
|
|
|
5021
5672
|
|
|
5022
5673
|
// daemon/src/providers/magpieGateway.ts
|
|
5023
5674
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
5024
|
-
import { homedir as
|
|
5675
|
+
import { homedir as homedir8 } from "node:os";
|
|
5025
5676
|
import { join as join11 } from "node:path";
|
|
5026
|
-
async function readJson(
|
|
5677
|
+
async function readJson(path4) {
|
|
5027
5678
|
try {
|
|
5028
|
-
return JSON.parse(await readFile11(
|
|
5679
|
+
return JSON.parse(await readFile11(path4, "utf8"));
|
|
5029
5680
|
} catch {
|
|
5030
5681
|
return null;
|
|
5031
5682
|
}
|
|
5032
5683
|
}
|
|
5033
5684
|
async function readApplied() {
|
|
5034
|
-
return await readJson(join11(
|
|
5685
|
+
return await readJson(join11(homedir8(), ".config", "magpie", "applied.json")) ?? {};
|
|
5035
5686
|
}
|
|
5036
5687
|
async function readClaudeEnv() {
|
|
5037
|
-
const s = await readJson(join11(
|
|
5688
|
+
const s = await readJson(join11(homedir8(), ".claude", "settings.json"));
|
|
5038
5689
|
return s?.env ?? {};
|
|
5039
5690
|
}
|
|
5040
5691
|
async function gatewayBase() {
|
|
@@ -5135,7 +5786,7 @@ var init_magpieGateway = __esm({
|
|
|
5135
5786
|
});
|
|
5136
5787
|
|
|
5137
5788
|
// daemon/src/providers/infoCache.ts
|
|
5138
|
-
function
|
|
5789
|
+
function withTimeout2(p, ms, fb) {
|
|
5139
5790
|
return Promise.race([p, new Promise((r) => setTimeout(() => r(fb), ms))]);
|
|
5140
5791
|
}
|
|
5141
5792
|
function fresh(e, ttl) {
|
|
@@ -5151,7 +5802,7 @@ async function cachedDetect(p, launch) {
|
|
|
5151
5802
|
let job = inflightDetect.get(key);
|
|
5152
5803
|
if (!job) {
|
|
5153
5804
|
const timedOut = { available: false, reason: tr("provider.timeout") };
|
|
5154
|
-
job =
|
|
5805
|
+
job = withTimeout2(
|
|
5155
5806
|
p.detect(launch).catch((e) => ({ available: false, reason: String(e?.message ?? e) })),
|
|
5156
5807
|
STEP_TIMEOUT_MS,
|
|
5157
5808
|
timedOut
|
|
@@ -5169,7 +5820,7 @@ async function cachedModels(p, launch) {
|
|
|
5169
5820
|
if (fresh(hit, MODELS_MS)) return hit.value;
|
|
5170
5821
|
let job = inflightModels.get(key);
|
|
5171
5822
|
if (!job) {
|
|
5172
|
-
job =
|
|
5823
|
+
job = withTimeout2(p.listModels(launch).catch(() => []), MODELS_TIMEOUT_MS, null).then(async (v) => {
|
|
5173
5824
|
if (v === null) return models.get(key)?.value ?? [];
|
|
5174
5825
|
const list = await withMagpie(p.id, dropPlaceholder(v)).catch(() => dropPlaceholder(v));
|
|
5175
5826
|
models.set(key, { at: Date.now(), value: list });
|
|
@@ -5222,40 +5873,1039 @@ var init_modes = __esm({
|
|
|
5222
5873
|
}
|
|
5223
5874
|
});
|
|
5224
5875
|
|
|
5225
|
-
// daemon/src/
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
return
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
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
|
+
|
|
5976
|
+
// daemon/src/util/mentions.ts
|
|
5977
|
+
import { realpath, stat as stat8 } from "node:fs/promises";
|
|
5978
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep2, win32 as win322, posix as posix2 } from "node:path";
|
|
5979
|
+
function insideRoot(root, child, platform = process.platform) {
|
|
5980
|
+
const p = platform === "win32" ? win322 : posix2;
|
|
5981
|
+
const norm2 = (s) => platform === "win32" ? p.resolve(s).toLowerCase() : p.resolve(s);
|
|
5982
|
+
const r = p.relative(norm2(root), norm2(child));
|
|
5983
|
+
return r === "" || !r.startsWith("..") && !p.isAbsolute(r);
|
|
5984
|
+
}
|
|
5985
|
+
async function resolveMentions(projectPath, refs) {
|
|
5986
|
+
const mentions = [];
|
|
5987
|
+
const warnings = [];
|
|
5988
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5989
|
+
const shown = resolve3(projectPath);
|
|
5990
|
+
let realRoot;
|
|
5991
|
+
try {
|
|
5992
|
+
realRoot = await realpath(shown);
|
|
5993
|
+
} catch {
|
|
5994
|
+
return { mentions, warnings: refs.map((r) => r.rel) };
|
|
5995
|
+
}
|
|
5996
|
+
for (const ref of refs.slice(0, MAX_MENTIONS)) {
|
|
5997
|
+
const rel = String(ref.rel ?? "").replace(/\\/g, "/");
|
|
5998
|
+
if (!rel || rel.includes("\0") || isAbsolute2(rel) || /^[a-zA-Z]:/.test(rel)) {
|
|
5999
|
+
warnings.push(rel || "(empty)");
|
|
6000
|
+
continue;
|
|
6001
|
+
}
|
|
6002
|
+
const abs = resolve3(shown, rel === "." ? "" : rel);
|
|
6003
|
+
if (!insideRoot(shown, abs)) {
|
|
6004
|
+
warnings.push(rel);
|
|
6005
|
+
continue;
|
|
6006
|
+
}
|
|
6007
|
+
let real;
|
|
6008
|
+
let isDir2;
|
|
6009
|
+
try {
|
|
6010
|
+
real = await realpath(abs);
|
|
6011
|
+
isDir2 = (await stat8(real)).isDirectory();
|
|
6012
|
+
} catch {
|
|
6013
|
+
warnings.push(rel);
|
|
6014
|
+
continue;
|
|
6015
|
+
}
|
|
6016
|
+
if (!insideRoot(realRoot, real)) {
|
|
6017
|
+
warnings.push(rel);
|
|
6018
|
+
continue;
|
|
6019
|
+
}
|
|
6020
|
+
if (seen.has(abs)) continue;
|
|
6021
|
+
seen.add(abs);
|
|
6022
|
+
mentions.push({ path: abs, isDir: isDir2 });
|
|
6023
|
+
}
|
|
6024
|
+
return { mentions, warnings };
|
|
6025
|
+
}
|
|
6026
|
+
function mentionSection(mentions) {
|
|
6027
|
+
if (!mentions.length) return "";
|
|
6028
|
+
const lines = mentions.map((m) => {
|
|
6029
|
+
const shown = m.isDir && !m.path.endsWith(sep2) && !m.path.endsWith("/") ? m.path + (m.path.includes("\\") && !m.path.includes("/") ? "\\" : "/") : m.path;
|
|
6030
|
+
const quoted = /\s|`/.test(shown) ? "`" + shown.replace(/`/g, "'") + "`" : shown;
|
|
6031
|
+
return `- ${quoted}${m.isDir ? " (folder)" : ""}`;
|
|
6032
|
+
});
|
|
6033
|
+
return `Referenced files (read them as needed):
|
|
6034
|
+
${lines.join("\n")}`;
|
|
6035
|
+
}
|
|
6036
|
+
function withMentions(text, mentions) {
|
|
6037
|
+
const sec = mentionSection(mentions);
|
|
6038
|
+
if (!sec) return text;
|
|
6039
|
+
return text.trim() ? `${text}
|
|
6040
|
+
|
|
6041
|
+
${sec}` : sec;
|
|
6042
|
+
}
|
|
6043
|
+
var MAX_MENTIONS;
|
|
6044
|
+
var init_mentions = __esm({
|
|
6045
|
+
"daemon/src/util/mentions.ts"() {
|
|
6046
|
+
MAX_MENTIONS = 50;
|
|
6047
|
+
}
|
|
6048
|
+
});
|
|
6049
|
+
|
|
6050
|
+
// daemon/src/preview/url.ts
|
|
6051
|
+
function isLoopbackHost(host) {
|
|
6052
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6053
|
+
if (h === "localhost" || h.endsWith(".localhost")) return /^[a-z0-9.-]+$/.test(h) && !h.includes("..");
|
|
6054
|
+
if (h === "0.0.0.0" || h === "::1" || h === "::") return true;
|
|
6055
|
+
const m = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
|
|
6056
|
+
return !!m && m.slice(1).every((x) => Number(x) <= 255);
|
|
6057
|
+
}
|
|
6058
|
+
function connectHosts(h) {
|
|
6059
|
+
if (h === "localhost" || h.endsWith(".localhost")) return ["127.0.0.1", "::1"];
|
|
6060
|
+
if (h === "0.0.0.0") return ["127.0.0.1"];
|
|
6061
|
+
if (h === "::1") return ["::1"];
|
|
6062
|
+
if (h === "::") return ["::1", "127.0.0.1"];
|
|
6063
|
+
return [h];
|
|
6064
|
+
}
|
|
6065
|
+
function withScheme(raw) {
|
|
6066
|
+
const s = raw.trim();
|
|
6067
|
+
if (/^\d{1,5}$/.test(s)) return `http://localhost:${s}/`;
|
|
6068
|
+
if (/^:\d{1,5}(?:[/?#].*)?$/.test(s)) return `http://localhost${s}`;
|
|
6069
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return s;
|
|
6070
|
+
if (/^[a-z][a-z0-9+.-]*:(?!\d)/i.test(s)) return s;
|
|
6071
|
+
return `http://${s}`;
|
|
6072
|
+
}
|
|
6073
|
+
function checkPreviewUrl(raw) {
|
|
6074
|
+
if (typeof raw !== "string" || !raw.trim()) return { ok: false, reason: "empty" };
|
|
6075
|
+
if (raw.length > MAX_PREVIEW_URL) return { ok: false, reason: "too_long" };
|
|
6076
|
+
let u;
|
|
6077
|
+
try {
|
|
6078
|
+
u = new URL(withScheme(raw));
|
|
6079
|
+
} catch {
|
|
6080
|
+
return { ok: false, reason: "bad_url" };
|
|
6081
|
+
}
|
|
6082
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return { ok: false, reason: "scheme" };
|
|
6083
|
+
if (u.username || u.password) return { ok: false, reason: "userinfo" };
|
|
6084
|
+
const hostname = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6085
|
+
if (!isLoopbackHost(hostname)) return { ok: false, reason: "not_local" };
|
|
6086
|
+
const secure = u.protocol === "https:";
|
|
6087
|
+
const port = u.port ? Number(u.port) : secure ? 443 : 80;
|
|
6088
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return { ok: false, reason: "port" };
|
|
6089
|
+
const href = u.href;
|
|
6090
|
+
if (href.length > MAX_PREVIEW_URL) return { ok: false, reason: "too_long" };
|
|
6091
|
+
const hostForUrl = hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
6092
|
+
return {
|
|
6093
|
+
ok: true,
|
|
6094
|
+
target: {
|
|
6095
|
+
href,
|
|
6096
|
+
origin: `${u.protocol}//${hostForUrl}:${port}`,
|
|
6097
|
+
path: `${u.pathname}${u.search}${u.hash}`,
|
|
6098
|
+
secure,
|
|
6099
|
+
hostname,
|
|
6100
|
+
port,
|
|
6101
|
+
connect: connectHosts(hostname),
|
|
6102
|
+
hostHeader: `${hostForUrl}:${port}`
|
|
5237
6103
|
}
|
|
5238
|
-
return fn(r.data, ctx);
|
|
5239
6104
|
};
|
|
5240
6105
|
}
|
|
5241
|
-
function
|
|
5242
|
-
const
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
6106
|
+
function deniedPort(port, denied) {
|
|
6107
|
+
for (const d of denied) if (d === port) return true;
|
|
6108
|
+
return false;
|
|
6109
|
+
}
|
|
6110
|
+
function displayOrigin(origin) {
|
|
6111
|
+
return origin.replace(/^http:\/\/(.+):80$/, "http://$1").replace(/^https:\/\/(.+):443$/, "https://$1");
|
|
6112
|
+
}
|
|
6113
|
+
function externalHref(raw) {
|
|
6114
|
+
if (typeof raw !== "string" || !raw || raw.length > MAX_PREVIEW_URL) return null;
|
|
6115
|
+
let u;
|
|
6116
|
+
try {
|
|
6117
|
+
u = new URL(raw);
|
|
6118
|
+
} catch {
|
|
6119
|
+
return null;
|
|
6120
|
+
}
|
|
6121
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return null;
|
|
6122
|
+
if (!u.hostname) return null;
|
|
6123
|
+
const href = u.href;
|
|
6124
|
+
if (/[\s"'<>`\\^{|}]/.test(href) || href.length > MAX_PREVIEW_URL) return null;
|
|
6125
|
+
return href;
|
|
6126
|
+
}
|
|
6127
|
+
var MAX_PREVIEW_URL;
|
|
6128
|
+
var init_url = __esm({
|
|
6129
|
+
"daemon/src/preview/url.ts"() {
|
|
6130
|
+
MAX_PREVIEW_URL = 2048;
|
|
6131
|
+
}
|
|
6132
|
+
});
|
|
6133
|
+
|
|
6134
|
+
// daemon/src/util/openExternal.ts
|
|
6135
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
6136
|
+
function openerFor(platform, href) {
|
|
6137
|
+
if (platform === "win32") return { cmd: "rundll32.exe", args: ["url.dll,FileProtocolHandler", href] };
|
|
6138
|
+
if (platform === "darwin") return { cmd: "open", args: [href] };
|
|
6139
|
+
return { cmd: "xdg-open", args: [href] };
|
|
6140
|
+
}
|
|
6141
|
+
async function openExternal(raw, opts = {}) {
|
|
6142
|
+
const href = externalHref(raw);
|
|
6143
|
+
if (!href) return { ok: false, reason: "bad_url" };
|
|
6144
|
+
const { cmd, args } = openerFor(opts.platform ?? process.platform, href);
|
|
6145
|
+
const run = opts.exec ?? execFile3;
|
|
6146
|
+
return await new Promise((resolve4) => {
|
|
6147
|
+
let settled = false;
|
|
6148
|
+
const done = (v) => {
|
|
6149
|
+
if (!settled) {
|
|
6150
|
+
settled = true;
|
|
6151
|
+
resolve4(v);
|
|
6152
|
+
}
|
|
6153
|
+
};
|
|
6154
|
+
try {
|
|
6155
|
+
const child = run(cmd, args, { windowsHide: true, timeout: 15e3 }, (err) => {
|
|
6156
|
+
done(err ? { ok: false, href, reason: "opener_failed" } : { ok: true, href });
|
|
6157
|
+
});
|
|
6158
|
+
child.on("error", () => done({ ok: false, href, reason: "no_opener" }));
|
|
6159
|
+
} catch {
|
|
6160
|
+
done({ ok: false, href, reason: "no_opener" });
|
|
6161
|
+
}
|
|
6162
|
+
setTimeout(() => done({ ok: true, href }), opts.waitMs ?? 4e3).unref?.();
|
|
6163
|
+
});
|
|
6164
|
+
}
|
|
6165
|
+
var init_openExternal = __esm({
|
|
6166
|
+
"daemon/src/util/openExternal.ts"() {
|
|
6167
|
+
init_url();
|
|
6168
|
+
}
|
|
6169
|
+
});
|
|
6170
|
+
|
|
6171
|
+
// daemon/src/preview/inject.ts
|
|
6172
|
+
function injectScriptTag(html) {
|
|
6173
|
+
const tag = `<script src="${PREVIEW_SCRIPT_PATH}"></script>`;
|
|
6174
|
+
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
6175
|
+
if (head) return html.slice(0, head.index + head[0].length) + tag + html.slice(head.index + head[0].length);
|
|
6176
|
+
const root = /<html(\s[^>]*)?>/i.exec(html);
|
|
6177
|
+
if (root) return html.slice(0, root.index + root[0].length) + tag + html.slice(root.index + root[0].length);
|
|
6178
|
+
const doctype = /^\s*<!doctype[^>]*>/i.exec(html);
|
|
6179
|
+
if (doctype) return html.slice(0, doctype[0].length) + tag + html.slice(doctype[0].length);
|
|
6180
|
+
return tag + html;
|
|
6181
|
+
}
|
|
6182
|
+
var PREVIEW_SCRIPT_PATH, PREVIEW_SCRIPT;
|
|
6183
|
+
var init_inject = __esm({
|
|
6184
|
+
"daemon/src/preview/inject.ts"() {
|
|
6185
|
+
PREVIEW_SCRIPT_PATH = "/__agent_console__/preview.js";
|
|
6186
|
+
PREVIEW_SCRIPT = `(function(){
|
|
6187
|
+
"use strict";
|
|
6188
|
+
if (window.__acPreview) return; window.__acPreview = 1;
|
|
6189
|
+
function mem(){ var m = new Map(); var s = {
|
|
6190
|
+
getItem: function(k){ k = String(k); return m.has(k) ? m.get(k) : null; },
|
|
6191
|
+
setItem: function(k, v){ m.set(String(k), String(v)); },
|
|
6192
|
+
removeItem: function(k){ m.delete(String(k)); },
|
|
6193
|
+
clear: function(){ m.clear(); },
|
|
6194
|
+
key: function(i){ var a = Array.from(m.keys()); return i >= 0 && i < a.length ? a[i] : null; }
|
|
6195
|
+
}; Object.defineProperty(s, "length", { get: function(){ return m.size; } }); return s; }
|
|
6196
|
+
["localStorage", "sessionStorage"].forEach(function(n){
|
|
6197
|
+
var ok = true; try { var st = window[n]; st.getItem("__ac"); } catch (e) { ok = false; }
|
|
6198
|
+
if (!ok) { try { Object.defineProperty(window, n, { value: mem(), configurable: true }); } catch (e) {} }
|
|
6199
|
+
});
|
|
6200
|
+
try { void document.cookie; } catch (e) {
|
|
6201
|
+
var jar = {};
|
|
6202
|
+
try { Object.defineProperty(document, "cookie", { configurable: true,
|
|
6203
|
+
get: function(){ return Object.keys(jar).map(function(k){ return k + "=" + jar[k]; }).join("; "); },
|
|
6204
|
+
set: function(v){ var p = String(v).split(";")[0]; var i = p.indexOf("="); if (i > 0) jar[p.slice(0, i).trim()] = p.slice(i + 1).trim(); }
|
|
6205
|
+
}); } catch (e2) {}
|
|
6206
|
+
}
|
|
6207
|
+
var parentWin = window.parent;
|
|
6208
|
+
if (!parentWin || parentWin === window) return;
|
|
6209
|
+
var last = "";
|
|
6210
|
+
function report(){ try {
|
|
6211
|
+
var msg = { ac_preview: 1, href: String(location.href), title: String(document.title || "") };
|
|
6212
|
+
var key = msg.href + "\\n" + msg.title; if (key === last) return; last = key;
|
|
6213
|
+
parentWin.postMessage(msg, "*");
|
|
6214
|
+
} catch (e) {} }
|
|
6215
|
+
["pushState", "replaceState"].forEach(function(k){
|
|
6216
|
+
var orig = history[k]; if (typeof orig !== "function") return;
|
|
6217
|
+
history[k] = function(){ var r = orig.apply(this, arguments); setTimeout(report, 0); return r; };
|
|
6218
|
+
});
|
|
6219
|
+
window.addEventListener("popstate", report);
|
|
6220
|
+
window.addEventListener("hashchange", report);
|
|
6221
|
+
window.addEventListener("load", report);
|
|
6222
|
+
document.addEventListener("DOMContentLoaded", report);
|
|
6223
|
+
setInterval(report, 1000);
|
|
6224
|
+
window.addEventListener("message", function(e){
|
|
6225
|
+
if (e.source !== parentWin) return;
|
|
6226
|
+
var d = e.data; if (!d || typeof d !== "object") return;
|
|
6227
|
+
var c = d.ac_preview_cmd;
|
|
6228
|
+
if (c === "back") history.back();
|
|
6229
|
+
else if (c === "forward") history.forward();
|
|
6230
|
+
else if (c === "reload") location.reload();
|
|
6231
|
+
else if (c === "ping") { last = ""; report(); }
|
|
6232
|
+
});
|
|
6233
|
+
report();
|
|
6234
|
+
})();
|
|
6235
|
+
`;
|
|
6236
|
+
}
|
|
6237
|
+
});
|
|
6238
|
+
|
|
6239
|
+
// daemon/src/preview/proxy.ts
|
|
6240
|
+
import { createServer as createServer2, request as httpRequest } from "node:http";
|
|
6241
|
+
import { request as httpsRequest } from "node:https";
|
|
6242
|
+
import { connect as netConnect, isIP } from "node:net";
|
|
6243
|
+
import { connect as tlsConnect } from "node:tls";
|
|
6244
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
6245
|
+
function esc(s) {
|
|
6246
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
6247
|
+
}
|
|
6248
|
+
function rewriteRequestHeaders(inH, t, proxyOrigin, upgrade) {
|
|
6249
|
+
const out = {};
|
|
6250
|
+
const targetOrigin = displayOrigin(t.origin);
|
|
6251
|
+
for (const [k, v] of Object.entries(inH)) {
|
|
6252
|
+
if (v === void 0) continue;
|
|
6253
|
+
const key = k.toLowerCase();
|
|
6254
|
+
if (!upgrade && HOP.has(key)) continue;
|
|
6255
|
+
if (upgrade && (key === "proxy-connection" || key === "proxy-authorization" || key === "keep-alive" || key === "te" || key === "trailer" || key === "transfer-encoding")) continue;
|
|
6256
|
+
if (key === "cookie" || key === "host" || key === "origin" || key === "referer" || key === "forwarded" || key.startsWith("x-forwarded-")) continue;
|
|
6257
|
+
out[key] = v;
|
|
6258
|
+
}
|
|
6259
|
+
out.host = t.hostHeader.replace(/:(80|443)$/, (m) => m === ":80" && !t.secure || m === ":443" && t.secure ? "" : m);
|
|
6260
|
+
if (inH.origin !== void 0) out.origin = targetOrigin;
|
|
6261
|
+
const ref = inH.referer;
|
|
6262
|
+
if (typeof ref === "string" && (ref === proxyOrigin || ref.startsWith(proxyOrigin + "/"))) out.referer = targetOrigin + ref.slice(proxyOrigin.length);
|
|
6263
|
+
if (inH["sec-fetch-site"] !== void 0) out["sec-fetch-site"] = "same-origin";
|
|
6264
|
+
if (!upgrade) out["accept-encoding"] = "identity";
|
|
6265
|
+
return out;
|
|
6266
|
+
}
|
|
6267
|
+
function rewriteResponseHeaders(inH, t, proxyOrigin) {
|
|
6268
|
+
const out = {};
|
|
6269
|
+
for (const [k, v] of Object.entries(inH)) {
|
|
6270
|
+
if (v === void 0) continue;
|
|
6271
|
+
const key = k.toLowerCase();
|
|
6272
|
+
if (HOP.has(key)) continue;
|
|
6273
|
+
if (key === "x-frame-options" || key === "set-cookie" || key === "referrer-policy" || key.startsWith("access-control-allow-")) continue;
|
|
6274
|
+
if (key === "content-security-policy" || key === "content-security-policy-report-only") {
|
|
6275
|
+
const kept = (Array.isArray(v) ? v : [v]).map(stripFrameAncestors).filter(Boolean);
|
|
6276
|
+
if (kept.length) out[key] = kept.join(", ");
|
|
6277
|
+
continue;
|
|
6278
|
+
}
|
|
6279
|
+
if (key === "location" && typeof v === "string") {
|
|
6280
|
+
out[key] = rewriteLocation(v, t, proxyOrigin);
|
|
6281
|
+
continue;
|
|
6282
|
+
}
|
|
6283
|
+
out[key] = v;
|
|
6284
|
+
}
|
|
6285
|
+
out["access-control-allow-origin"] = "null";
|
|
6286
|
+
out["referrer-policy"] = "no-referrer";
|
|
6287
|
+
const vary = typeof out.vary === "string" ? out.vary : "";
|
|
6288
|
+
out.vary = vary ? /\borigin\b/i.test(vary) ? vary : `${vary}, Origin` : "Origin";
|
|
6289
|
+
return out;
|
|
6290
|
+
}
|
|
6291
|
+
function stripFrameAncestors(csp) {
|
|
6292
|
+
return csp.split(";").map((d) => d.trim()).filter((d) => d && !/^frame-ancestors(\s|$)/i.test(d)).join("; ");
|
|
6293
|
+
}
|
|
6294
|
+
function rewriteLocation(loc, t, proxyOrigin) {
|
|
6295
|
+
let u;
|
|
6296
|
+
try {
|
|
6297
|
+
u = new URL(loc, t.href);
|
|
6298
|
+
} catch {
|
|
6299
|
+
return loc;
|
|
6300
|
+
}
|
|
6301
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(loc) && !loc.startsWith("//")) return loc;
|
|
6302
|
+
const port = u.port ? Number(u.port) : u.protocol === "https:" ? 443 : 80;
|
|
6303
|
+
const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6304
|
+
if (u.protocol === (t.secure ? "https:" : "http:") && host === t.hostname && port === t.port) {
|
|
6305
|
+
return proxyOrigin + u.pathname + u.search + u.hash;
|
|
6306
|
+
}
|
|
6307
|
+
return loc;
|
|
6308
|
+
}
|
|
6309
|
+
function looksLikeMagpie(h) {
|
|
6310
|
+
return Object.keys(h).some((k) => k.toLowerCase().startsWith("x-magpie-"));
|
|
6311
|
+
}
|
|
6312
|
+
function magpieIndex(body) {
|
|
6313
|
+
try {
|
|
6314
|
+
const j = JSON.parse(body);
|
|
6315
|
+
return !!j && typeof j === "object" && j.name === "magpie" && (Array.isArray(j.apis) || typeof j.version === "string");
|
|
6316
|
+
} catch {
|
|
6317
|
+
return false;
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6320
|
+
async function identify(t, addrs, timeoutMs) {
|
|
6321
|
+
for (const addr of addrs) {
|
|
6322
|
+
const r = await new Promise((resolve4) => {
|
|
6323
|
+
let settled = false;
|
|
6324
|
+
const done = (v) => {
|
|
6325
|
+
if (!settled) {
|
|
6326
|
+
settled = true;
|
|
6327
|
+
clearTimeout(timer);
|
|
6328
|
+
resolve4(v);
|
|
6329
|
+
}
|
|
6330
|
+
};
|
|
6331
|
+
const reqFn = t.secure ? httpsRequest : httpRequest;
|
|
6332
|
+
const q = reqFn({
|
|
6333
|
+
host: addr,
|
|
6334
|
+
port: t.port,
|
|
6335
|
+
method: "GET",
|
|
6336
|
+
path: "/",
|
|
6337
|
+
headers: { host: t.hostHeader, accept: "application/json", "accept-encoding": "identity", "user-agent": "agent-console-preview-check" },
|
|
6338
|
+
agent: false,
|
|
6339
|
+
...t.secure ? { rejectUnauthorized: false, servername: isIP(t.hostname) ? void 0 : t.hostname } : {}
|
|
6340
|
+
});
|
|
6341
|
+
const timer = setTimeout(() => {
|
|
6342
|
+
q.destroy();
|
|
6343
|
+
done("none");
|
|
6344
|
+
}, timeoutMs);
|
|
6345
|
+
q.on("error", () => done("none"));
|
|
6346
|
+
q.on("response", (res) => {
|
|
6347
|
+
if (looksLikeMagpie(res.headers)) {
|
|
6348
|
+
res.destroy();
|
|
6349
|
+
return done("magpie");
|
|
6350
|
+
}
|
|
6351
|
+
const ct = String(res.headers["content-type"] ?? "").toLowerCase();
|
|
6352
|
+
if (!ct.includes("json")) {
|
|
6353
|
+
res.destroy();
|
|
6354
|
+
return done("other");
|
|
6355
|
+
}
|
|
6356
|
+
const chunks = [];
|
|
6357
|
+
let size = 0;
|
|
6358
|
+
res.on("data", (c) => {
|
|
6359
|
+
size += c.length;
|
|
6360
|
+
if (size > 256 * 1024) {
|
|
6361
|
+
res.destroy();
|
|
6362
|
+
done("other");
|
|
6363
|
+
return;
|
|
6364
|
+
}
|
|
6365
|
+
chunks.push(c);
|
|
6366
|
+
});
|
|
6367
|
+
res.on("end", () => done(magpieIndex(Buffer.concat(chunks).toString("utf8")) ? "magpie" : "other"));
|
|
6368
|
+
res.on("error", () => done("none"));
|
|
6369
|
+
});
|
|
6370
|
+
q.end();
|
|
6371
|
+
});
|
|
6372
|
+
if (r !== "none") return r;
|
|
6373
|
+
}
|
|
6374
|
+
return "none";
|
|
6375
|
+
}
|
|
6376
|
+
var PreviewError, HOP, RETRY_CODES, IDENTITY_TTL, PREVIEW_PING_PATH, PreviewProxy;
|
|
6377
|
+
var init_proxy = __esm({
|
|
6378
|
+
"daemon/src/preview/proxy.ts"() {
|
|
6379
|
+
init_security();
|
|
6380
|
+
init_url();
|
|
6381
|
+
init_inject();
|
|
6382
|
+
PreviewError = class extends Error {
|
|
6383
|
+
code;
|
|
6384
|
+
constructor(code, message) {
|
|
6385
|
+
super(message);
|
|
6386
|
+
this.code = code;
|
|
6387
|
+
}
|
|
6388
|
+
};
|
|
6389
|
+
HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "te", "trailer", "transfer-encoding", "upgrade"]);
|
|
6390
|
+
RETRY_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "EADDRNOTAVAIL", "EAFNOSUPPORT", "ENETUNREACH", "EHOSTUNREACH", "ECONNRESET", "ETIMEDOUT"]);
|
|
6391
|
+
IDENTITY_TTL = 60 * 1e3;
|
|
6392
|
+
PREVIEW_PING_PATH = "/__agent_console__/ping";
|
|
6393
|
+
PreviewProxy = class {
|
|
6394
|
+
constructor(opts) {
|
|
6395
|
+
this.opts = opts;
|
|
6396
|
+
this.idleMs = opts.idleMs ?? 10 * 60 * 1e3;
|
|
6397
|
+
this.maxTargets = opts.maxTargets ?? 8;
|
|
6398
|
+
this.maxHtml = opts.maxHtmlBytes ?? 8 * 1024 * 1024;
|
|
6399
|
+
this.now = opts.now ?? Date.now;
|
|
6400
|
+
this.identityTimeout = opts.identityTimeoutMs ?? 30 * 1e3;
|
|
6401
|
+
}
|
|
6402
|
+
opts;
|
|
6403
|
+
byOrigin = /* @__PURE__ */ new Map();
|
|
6404
|
+
opening = /* @__PURE__ */ new Map();
|
|
6405
|
+
sweepTimer = null;
|
|
6406
|
+
idleMs;
|
|
6407
|
+
maxTargets;
|
|
6408
|
+
maxHtml;
|
|
6409
|
+
now;
|
|
6410
|
+
identityTimeout;
|
|
6411
|
+
/** Ports of the listeners themselves (a preview of a preview is refused) */
|
|
6412
|
+
ports() {
|
|
6413
|
+
return [...this.byOrigin.values()].map((l) => l.port);
|
|
6414
|
+
}
|
|
6415
|
+
get size() {
|
|
6416
|
+
return this.byOrigin.size;
|
|
6417
|
+
}
|
|
6418
|
+
async open(raw) {
|
|
6419
|
+
const c = checkPreviewUrl(raw);
|
|
6420
|
+
if (!c.ok) throw new PreviewError(c.reason, `preview url refused: ${c.reason}`);
|
|
6421
|
+
const t = c.target;
|
|
6422
|
+
if (deniedPort(t.port, [...this.opts.deniedPorts(), ...this.ports()])) {
|
|
6423
|
+
throw new PreviewError("denied_port", `port ${t.port} can't be previewed`);
|
|
6424
|
+
}
|
|
6425
|
+
let l = this.byOrigin.get(t.origin);
|
|
6426
|
+
if (!l) {
|
|
6427
|
+
const who = await identify(t, t.connect, Math.min(3e3, this.identityTimeout));
|
|
6428
|
+
if (who === "magpie") {
|
|
6429
|
+
this.opts.log?.("warn", `preview refused: ${t.origin} answers like Magpie's gateway`);
|
|
6430
|
+
throw new PreviewError("denied_port", `${t.origin} is Magpie's gateway`);
|
|
6431
|
+
}
|
|
6432
|
+
let p = this.opening.get(t.origin);
|
|
6433
|
+
if (!p) {
|
|
6434
|
+
p = this.listen(t).finally(() => this.opening.delete(t.origin));
|
|
6435
|
+
this.opening.set(t.origin, p);
|
|
6436
|
+
}
|
|
6437
|
+
l = await p;
|
|
6438
|
+
}
|
|
6439
|
+
l.lastUsed = this.now();
|
|
6440
|
+
l.nonce = randomBytes3(16).toString("hex");
|
|
6441
|
+
l.canaryAuthority = `acprobe${randomBytes3(16).toString("hex")}.localhost:${l.port}`;
|
|
6442
|
+
l.canaryNonce = randomBytes3(16).toString("hex");
|
|
6443
|
+
this.ensureSweep();
|
|
6444
|
+
return {
|
|
6445
|
+
proxyOrigin: l.proxyOrigin,
|
|
6446
|
+
target: t.origin,
|
|
6447
|
+
url: t.href,
|
|
6448
|
+
path: t.path,
|
|
6449
|
+
proxyHref: l.proxyOrigin + t.path,
|
|
6450
|
+
nonce: l.nonce,
|
|
6451
|
+
canaryOrigin: `http://${l.canaryAuthority}`,
|
|
6452
|
+
canaryNonce: l.canaryNonce
|
|
6453
|
+
};
|
|
6454
|
+
}
|
|
6455
|
+
/** Close the listener for this target (any URL on it, or its origin) */
|
|
6456
|
+
release(raw) {
|
|
6457
|
+
const c = checkPreviewUrl(raw);
|
|
6458
|
+
if (!c.ok) return false;
|
|
6459
|
+
const l = this.byOrigin.get(c.target.origin);
|
|
6460
|
+
if (!l) return false;
|
|
6461
|
+
this.close(l);
|
|
6462
|
+
return true;
|
|
6463
|
+
}
|
|
6464
|
+
closeAll() {
|
|
6465
|
+
for (const l of [...this.byOrigin.values()]) this.close(l);
|
|
6466
|
+
if (this.sweepTimer) clearInterval(this.sweepTimer);
|
|
6467
|
+
this.sweepTimer = null;
|
|
6468
|
+
}
|
|
6469
|
+
/** Close listeners idle for idleMs (no request and no open WebSocket); exposed for tests with a fake clock */
|
|
6470
|
+
sweep() {
|
|
6471
|
+
const now = this.now();
|
|
6472
|
+
for (const l of [...this.byOrigin.values()]) {
|
|
6473
|
+
if (l.tunnels === 0 && now - l.lastUsed > this.idleMs) {
|
|
6474
|
+
this.opts.log?.("info", `preview proxy for ${l.target.origin} idle, closing`);
|
|
6475
|
+
this.close(l);
|
|
6476
|
+
}
|
|
6477
|
+
}
|
|
6478
|
+
if (this.byOrigin.size === 0 && this.sweepTimer) {
|
|
6479
|
+
clearInterval(this.sweepTimer);
|
|
6480
|
+
this.sweepTimer = null;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
ensureSweep() {
|
|
6484
|
+
if (this.sweepTimer) return;
|
|
6485
|
+
this.sweepTimer = setInterval(() => this.sweep(), 60 * 1e3);
|
|
6486
|
+
this.sweepTimer.unref();
|
|
6487
|
+
}
|
|
6488
|
+
close(l) {
|
|
6489
|
+
if (this.byOrigin.get(l.target.origin) === l) this.byOrigin.delete(l.target.origin);
|
|
6490
|
+
try {
|
|
6491
|
+
l.server.close();
|
|
6492
|
+
} catch {
|
|
6493
|
+
}
|
|
6494
|
+
for (const s of l.sockets) s.destroy();
|
|
6495
|
+
l.sockets.clear();
|
|
6496
|
+
}
|
|
6497
|
+
async listen(t) {
|
|
6498
|
+
while (this.byOrigin.size >= this.maxTargets) {
|
|
6499
|
+
const oldest = [...this.byOrigin.values()].sort((a2, b) => a2.lastUsed - b.lastUsed)[0];
|
|
6500
|
+
if (!oldest) break;
|
|
6501
|
+
this.close(oldest);
|
|
6502
|
+
}
|
|
6503
|
+
const server = createServer2();
|
|
6504
|
+
const l = {
|
|
6505
|
+
target: t,
|
|
6506
|
+
server,
|
|
6507
|
+
port: 0,
|
|
6508
|
+
authority: "",
|
|
6509
|
+
proxyOrigin: "",
|
|
6510
|
+
nonce: "",
|
|
6511
|
+
canaryAuthority: "",
|
|
6512
|
+
canaryNonce: "",
|
|
6513
|
+
magpie: false,
|
|
6514
|
+
checkedAt: 0,
|
|
6515
|
+
resolving: null,
|
|
6516
|
+
lastUsed: this.now(),
|
|
6517
|
+
sockets: /* @__PURE__ */ new Set(),
|
|
6518
|
+
tunnels: 0,
|
|
6519
|
+
addr: null
|
|
6520
|
+
};
|
|
6521
|
+
server.on("connection", (s) => {
|
|
6522
|
+
l.sockets.add(s);
|
|
6523
|
+
s.on("close", () => l.sockets.delete(s));
|
|
6524
|
+
});
|
|
6525
|
+
server.on("request", (req, res) => this.onRequest(l, req, res));
|
|
6526
|
+
server.on("upgrade", (req, socket, head) => this.onUpgrade(l, req, socket, head));
|
|
6527
|
+
server.on("clientError", (_e, socket) => {
|
|
6528
|
+
try {
|
|
6529
|
+
socket.destroy();
|
|
6530
|
+
} catch {
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
await new Promise((resolve4, reject) => {
|
|
6534
|
+
server.once("error", reject);
|
|
6535
|
+
server.listen(0, "127.0.0.1", () => resolve4());
|
|
6536
|
+
});
|
|
6537
|
+
const a = server.address();
|
|
6538
|
+
l.port = a && typeof a === "object" ? a.port : 0;
|
|
6539
|
+
l.authority = `ac${randomBytes3(16).toString("hex")}.localhost:${l.port}`;
|
|
6540
|
+
l.proxyOrigin = `http://${l.authority}`;
|
|
6541
|
+
this.byOrigin.set(t.origin, l);
|
|
6542
|
+
this.opts.log?.("info", `preview proxy 127.0.0.1:${l.port} \u2192 ${t.origin}`);
|
|
6543
|
+
return l;
|
|
6544
|
+
}
|
|
6545
|
+
// ---- request gate shared by HTTP and WS ----
|
|
6546
|
+
/** null = refuse; otherwise which name matched (canary is ping-only) */
|
|
6547
|
+
gate(l, req) {
|
|
6548
|
+
const host = String(req.headers.host ?? "").toLowerCase();
|
|
6549
|
+
let role = null;
|
|
6550
|
+
if (l.authority && tokenEqual(host, l.authority)) role = "key";
|
|
6551
|
+
else if (l.canaryAuthority && tokenEqual(host, l.canaryAuthority)) role = "canary";
|
|
6552
|
+
else {
|
|
6553
|
+
return checkHost(req.headers.host, l.port).ok ? { code: 403, why: "preview_key" } : { code: 421, why: "bad_host" };
|
|
6554
|
+
}
|
|
6555
|
+
const o = req.headers.origin;
|
|
6556
|
+
if (o !== void 0 && o !== "" && o !== "null") {
|
|
6557
|
+
if (role === "key" && o.toLowerCase() !== l.proxyOrigin) return { code: 403, why: "bad_origin" };
|
|
6558
|
+
if (role === "canary") return { code: 403, why: "bad_origin" };
|
|
6559
|
+
}
|
|
6560
|
+
return { role };
|
|
6561
|
+
}
|
|
6562
|
+
/** A refusal: plain text, no CORS headers (the page that sent it can't read it), nothing forwarded */
|
|
6563
|
+
refuse(res, req, code, why) {
|
|
6564
|
+
res.writeHead(code, {
|
|
6565
|
+
"content-type": "text/plain; charset=utf-8",
|
|
6566
|
+
"cache-control": "no-store",
|
|
6567
|
+
"x-content-type-options": "nosniff",
|
|
6568
|
+
"content-security-policy": "sandbox; default-src 'none'",
|
|
6569
|
+
"referrer-policy": "no-referrer"
|
|
6570
|
+
});
|
|
6571
|
+
res.end(req.method === "HEAD" ? void 0 : why);
|
|
6572
|
+
req.resume();
|
|
6573
|
+
}
|
|
6574
|
+
onRequest(l, req, res) {
|
|
6575
|
+
const g = this.gate(l, req);
|
|
6576
|
+
if ("code" in g) {
|
|
6577
|
+
this.refuse(res, req, g.code, g.why);
|
|
6578
|
+
return;
|
|
6579
|
+
}
|
|
6580
|
+
l.lastUsed = this.now();
|
|
6581
|
+
const path4 = (req.url ?? "/").split("?")[0];
|
|
6582
|
+
if (path4 === PREVIEW_PING_PATH) {
|
|
6583
|
+
const body = g.role === "canary" ? l.canaryNonce : l.nonce;
|
|
6584
|
+
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "referrer-policy": "no-referrer" });
|
|
6585
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
6586
|
+
req.resume();
|
|
6587
|
+
return;
|
|
6588
|
+
}
|
|
6589
|
+
if (g.role === "canary") {
|
|
6590
|
+
this.refuse(res, req, 403, "preview_canary");
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
if (l.magpie) {
|
|
6594
|
+
this.refuse(res, req, 403, "magpie");
|
|
6595
|
+
return;
|
|
6596
|
+
}
|
|
6597
|
+
if (path4 === PREVIEW_SCRIPT_PATH) {
|
|
6598
|
+
res.writeHead(200, {
|
|
6599
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
6600
|
+
"cache-control": "no-store",
|
|
6601
|
+
"access-control-allow-origin": "null",
|
|
6602
|
+
"x-content-type-options": "nosniff",
|
|
6603
|
+
"referrer-policy": "no-referrer"
|
|
6604
|
+
});
|
|
6605
|
+
res.end(req.method === "HEAD" ? void 0 : PREVIEW_SCRIPT);
|
|
6606
|
+
return;
|
|
6607
|
+
}
|
|
6608
|
+
if (req.method === "OPTIONS" && req.headers["access-control-request-method"]) {
|
|
6609
|
+
const h = {
|
|
6610
|
+
"access-control-allow-origin": "null",
|
|
6611
|
+
"access-control-allow-methods": String(req.headers["access-control-request-method"]).slice(0, 200) + ", GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD",
|
|
6612
|
+
"access-control-max-age": "600",
|
|
6613
|
+
vary: "origin"
|
|
6614
|
+
};
|
|
6615
|
+
const rh = req.headers["access-control-request-headers"];
|
|
6616
|
+
if (typeof rh === "string") h["access-control-allow-headers"] = rh.slice(0, 2e3);
|
|
6617
|
+
res.writeHead(204, h);
|
|
6618
|
+
res.end();
|
|
6619
|
+
req.resume();
|
|
6620
|
+
return;
|
|
6621
|
+
}
|
|
6622
|
+
void this.forward(l, req, res);
|
|
6623
|
+
}
|
|
6624
|
+
/**
|
|
6625
|
+
* The address to forward to, once the target is known not to be Magpie's gateway (asked again after IDENTITY_TTL).
|
|
6626
|
+
* Fails closed: no answer to the identity check → null (error page), Magpie → l.magpie (refused from then on).
|
|
6627
|
+
*/
|
|
6628
|
+
resolveAddr(l) {
|
|
6629
|
+
if (l.magpie) return Promise.resolve(null);
|
|
6630
|
+
if (l.addr && this.now() - l.checkedAt < IDENTITY_TTL) return Promise.resolve(l.addr);
|
|
6631
|
+
if (!l.resolving) {
|
|
6632
|
+
l.resolving = (async () => {
|
|
6633
|
+
const addr = l.addr ?? await this.probeAddr(l);
|
|
6634
|
+
if (!addr) return null;
|
|
6635
|
+
const who = await identify(l.target, [addr], this.identityTimeout);
|
|
6636
|
+
if (who === "magpie") {
|
|
6637
|
+
l.magpie = true;
|
|
6638
|
+
l.addr = null;
|
|
6639
|
+
this.opts.log?.("warn", `preview proxy for ${l.target.origin}: the target answers like Magpie's gateway, refusing`);
|
|
6640
|
+
return null;
|
|
6641
|
+
}
|
|
6642
|
+
if (who === "none") {
|
|
6643
|
+
l.addr = null;
|
|
6644
|
+
return null;
|
|
6645
|
+
}
|
|
6646
|
+
l.addr = addr;
|
|
6647
|
+
l.checkedAt = this.now();
|
|
6648
|
+
return addr;
|
|
6649
|
+
})().finally(() => {
|
|
6650
|
+
l.resolving = null;
|
|
6651
|
+
});
|
|
6652
|
+
}
|
|
6653
|
+
return l.resolving;
|
|
6654
|
+
}
|
|
6655
|
+
async probeAddr(l) {
|
|
6656
|
+
for (const host of l.target.connect) {
|
|
6657
|
+
const ok = await new Promise((resolve4) => {
|
|
6658
|
+
const s = netConnect({ host, port: l.target.port });
|
|
6659
|
+
const done = (v) => {
|
|
6660
|
+
s.removeAllListeners();
|
|
6661
|
+
s.destroy();
|
|
6662
|
+
resolve4(v);
|
|
6663
|
+
};
|
|
6664
|
+
s.setTimeout(3e3, () => done(false));
|
|
6665
|
+
s.once("connect", () => done(true));
|
|
6666
|
+
s.once("error", () => done(false));
|
|
6667
|
+
});
|
|
6668
|
+
if (ok) return host;
|
|
6669
|
+
}
|
|
6670
|
+
return null;
|
|
6671
|
+
}
|
|
6672
|
+
servername(l) {
|
|
6673
|
+
return isIP(l.target.hostname) ? void 0 : l.target.hostname;
|
|
6674
|
+
}
|
|
6675
|
+
async forward(l, req, res) {
|
|
6676
|
+
const addr = await this.resolveAddr(l);
|
|
6677
|
+
if (l.magpie) {
|
|
6678
|
+
this.refuse(res, req, 403, "magpie");
|
|
6679
|
+
return;
|
|
6680
|
+
}
|
|
6681
|
+
if (!addr) {
|
|
6682
|
+
this.errorPage(l, req, res);
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
const headers = rewriteRequestHeaders(req.headers, l.target, l.proxyOrigin, false);
|
|
6686
|
+
const reqFn = l.target.secure ? httpsRequest : httpRequest;
|
|
6687
|
+
const up = reqFn({
|
|
6688
|
+
host: addr,
|
|
6689
|
+
port: l.target.port,
|
|
6690
|
+
method: req.method,
|
|
6691
|
+
path: req.url ?? "/",
|
|
6692
|
+
headers,
|
|
6693
|
+
...l.target.secure ? { rejectUnauthorized: false, servername: this.servername(l) } : {}
|
|
6694
|
+
});
|
|
6695
|
+
up.on("response", (ur) => this.respond(l, req, res, ur));
|
|
6696
|
+
up.on("error", (e) => {
|
|
6697
|
+
if (RETRY_CODES.has(e.code ?? "")) l.addr = null;
|
|
6698
|
+
if (!res.headersSent) this.errorPage(l, req, res);
|
|
6699
|
+
else res.destroy();
|
|
6700
|
+
});
|
|
6701
|
+
req.on("aborted", () => up.destroy());
|
|
6702
|
+
res.on("close", () => {
|
|
6703
|
+
if (!res.writableFinished) up.destroy();
|
|
6704
|
+
});
|
|
6705
|
+
req.pipe(up);
|
|
6706
|
+
}
|
|
6707
|
+
respond(l, req, res, ur) {
|
|
6708
|
+
l.lastUsed = this.now();
|
|
6709
|
+
if (looksLikeMagpie(ur.headers)) {
|
|
6710
|
+
l.magpie = true;
|
|
6711
|
+
l.addr = null;
|
|
6712
|
+
ur.resume();
|
|
6713
|
+
this.refuse(res, req, 403, "magpie");
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
const h = rewriteResponseHeaders(ur.headers, l.target, l.proxyOrigin);
|
|
6717
|
+
const status2 = ur.statusCode ?? 502;
|
|
6718
|
+
const ctype = String(ur.headers["content-type"] ?? "").toLowerCase();
|
|
6719
|
+
const encoded = !!ur.headers["content-encoding"] && ur.headers["content-encoding"] !== "identity";
|
|
6720
|
+
const html = ctype.includes("text/html") && !encoded && req.method !== "HEAD" && status2 !== 204 && status2 !== 304;
|
|
6721
|
+
if (!html) {
|
|
6722
|
+
res.writeHead(status2, h);
|
|
6723
|
+
ur.pipe(res);
|
|
6724
|
+
return;
|
|
6725
|
+
}
|
|
6726
|
+
const chunks = [];
|
|
6727
|
+
let size = 0;
|
|
6728
|
+
let passthrough = false;
|
|
6729
|
+
ur.on("data", (c) => {
|
|
6730
|
+
if (passthrough) return;
|
|
6731
|
+
size += c.length;
|
|
6732
|
+
chunks.push(c);
|
|
6733
|
+
if (size > this.maxHtml) {
|
|
6734
|
+
passthrough = true;
|
|
6735
|
+
res.writeHead(status2, h);
|
|
6736
|
+
for (const x of chunks) res.write(x);
|
|
6737
|
+
ur.removeAllListeners("data");
|
|
6738
|
+
ur.removeAllListeners("end");
|
|
6739
|
+
ur.pipe(res);
|
|
6740
|
+
}
|
|
6741
|
+
});
|
|
6742
|
+
ur.on("end", () => {
|
|
6743
|
+
if (passthrough) return;
|
|
6744
|
+
const out = Buffer.from(injectScriptTag(Buffer.concat(chunks).toString("latin1")), "latin1");
|
|
6745
|
+
h["content-length"] = String(out.length);
|
|
6746
|
+
res.writeHead(status2, h);
|
|
6747
|
+
res.end(out);
|
|
6748
|
+
});
|
|
6749
|
+
ur.on("error", () => res.destroy());
|
|
6750
|
+
}
|
|
6751
|
+
errorPage(l, req, res) {
|
|
6752
|
+
const where = displayOrigin(l.target.origin).replace(/^https?:\/\//, "");
|
|
6753
|
+
const body = `<!doctype html><html><head><meta charset="utf-8"><title>${esc(where)}</title>
|
|
6754
|
+
<style>body{font:13px/1.6 system-ui,sans-serif;color:#525252;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#fff}
|
|
6755
|
+
@media (prefers-color-scheme:dark){body{background:#1a1a1a;color:#a3a3a3}button{background:#262626;color:#e5e5e5;border-color:#3a3a3a}}
|
|
6756
|
+
.b{text-align:center;max-width:420px;padding:20px}code{font-family:ui-monospace,monospace}button{margin-top:12px;padding:5px 14px;border:1px solid #d4d4d4;border-radius:6px;background:#fafafa;cursor:pointer;font:inherit}</style></head>
|
|
6757
|
+
<body><div class="b"><div><code>${esc(where)}</code> \u6CA1\u6709\u54CD\u5E94\uFF0Cdev server \u542F\u52A8\u4E86\u5417\uFF1F</div><div><code>${esc(where)}</code> is not responding. Is the dev server running?</div>
|
|
6758
|
+
<button onclick="location.reload()">\u91CD\u8BD5 / Retry</button></div></body></html>`;
|
|
6759
|
+
res.writeHead(502, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null" });
|
|
6760
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
6761
|
+
req.resume();
|
|
6762
|
+
}
|
|
6763
|
+
// ---- WebSocket (HMR) ----
|
|
6764
|
+
onUpgrade(l, req, socket, head) {
|
|
6765
|
+
const g = this.gate(l, req);
|
|
6766
|
+
if ("code" in g || g.role === "canary" || l.magpie) {
|
|
6767
|
+
const code = "code" in g ? g.code : 403;
|
|
6768
|
+
socket.end(`HTTP/1.1 ${code} ${code === 421 ? "Misdirected Request" : "Forbidden"}\r
|
|
6769
|
+
connection: close\r
|
|
6770
|
+
\r
|
|
6771
|
+
`);
|
|
6772
|
+
return;
|
|
6773
|
+
}
|
|
6774
|
+
l.lastUsed = this.now();
|
|
6775
|
+
socket.on("error", () => {
|
|
6776
|
+
});
|
|
6777
|
+
void this.resolveAddr(l).then((addr) => {
|
|
6778
|
+
if (!addr || socket.destroyed) {
|
|
6779
|
+
socket.end("HTTP/1.1 502 Bad Gateway\r\nconnection: close\r\n\r\n");
|
|
6780
|
+
return;
|
|
6781
|
+
}
|
|
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 });
|
|
6783
|
+
const ready2 = l.target.secure ? "secureConnect" : "connect";
|
|
6784
|
+
l.tunnels++;
|
|
6785
|
+
let counted = true;
|
|
6786
|
+
const done = () => {
|
|
6787
|
+
if (counted) {
|
|
6788
|
+
counted = false;
|
|
6789
|
+
l.tunnels--;
|
|
6790
|
+
l.lastUsed = this.now();
|
|
6791
|
+
}
|
|
6792
|
+
up.destroy();
|
|
6793
|
+
socket.destroy();
|
|
6794
|
+
};
|
|
6795
|
+
up.once(ready2, () => {
|
|
6796
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/1.1`];
|
|
6797
|
+
const hs = rewriteRequestHeaders(req.headers, l.target, l.proxyOrigin, true);
|
|
6798
|
+
for (const [k, v] of Object.entries(hs)) {
|
|
6799
|
+
if (v === void 0) continue;
|
|
6800
|
+
for (const one of Array.isArray(v) ? v : [v]) lines.push(`${k}: ${String(one).replace(/[\r\n]/g, "")}`);
|
|
6801
|
+
}
|
|
6802
|
+
up.write(lines.join("\r\n") + "\r\n\r\n");
|
|
6803
|
+
if (head?.length) up.write(head);
|
|
6804
|
+
up.pipe(socket);
|
|
6805
|
+
socket.pipe(up);
|
|
6806
|
+
});
|
|
6807
|
+
up.on("data", () => {
|
|
6808
|
+
l.lastUsed = this.now();
|
|
6809
|
+
});
|
|
6810
|
+
up.on("error", done);
|
|
6811
|
+
up.on("close", done);
|
|
6812
|
+
socket.on("close", done);
|
|
6813
|
+
});
|
|
6814
|
+
}
|
|
6815
|
+
};
|
|
6816
|
+
}
|
|
6817
|
+
});
|
|
6818
|
+
|
|
6819
|
+
// daemon/src/preview/host.ts
|
|
6820
|
+
import os from "node:os";
|
|
6821
|
+
function previewHost(platform = os.platform(), release2 = os.release()) {
|
|
6822
|
+
if (platform !== "darwin") return { platform, darwinMajor: null };
|
|
6823
|
+
const m = /^(\d+)\./.exec(String(release2 ?? "").trim()) ?? /^(\d+)$/.exec(String(release2 ?? "").trim());
|
|
6824
|
+
const major = m ? Number(m[1]) : NaN;
|
|
6825
|
+
return { platform, darwinMajor: Number.isInteger(major) && major > 0 ? major : null };
|
|
6826
|
+
}
|
|
6827
|
+
var init_host = __esm({
|
|
6828
|
+
"daemon/src/preview/host.ts"() {
|
|
6829
|
+
}
|
|
6830
|
+
});
|
|
6831
|
+
|
|
6832
|
+
// daemon/src/ws/methods.ts
|
|
6833
|
+
import { z } from "zod";
|
|
6834
|
+
import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
|
|
6835
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
6836
|
+
import { homedir as homedir9 } from "node:os";
|
|
6837
|
+
import { dirname as dirname3, join as join12, win32 as win32Path } from "node:path";
|
|
6838
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
6839
|
+
function wrap(schema, fn) {
|
|
6840
|
+
return (params, ctx) => {
|
|
6841
|
+
const r = schema.safeParse(params ?? {});
|
|
6842
|
+
if (!r.success) {
|
|
6843
|
+
throw new ApiError("bad_params", tr("err.badParams", { detail: r.error.issues.map((i) => i.message).join("; ") }));
|
|
6844
|
+
}
|
|
6845
|
+
return fn(r.data, ctx);
|
|
6846
|
+
};
|
|
6847
|
+
}
|
|
6848
|
+
function registerMethods(gw, svc) {
|
|
6849
|
+
const zEmpty = z.object({}).passthrough().optional();
|
|
6850
|
+
gw.method("app.setLocale", wrap(z.object({ lang: z.string().max(32) }), (p) => {
|
|
6851
|
+
setAppLocale(p.lang);
|
|
6852
|
+
return true;
|
|
6853
|
+
}));
|
|
6854
|
+
gw.method("app.info", wrap(zEmpty, () => ({
|
|
6855
|
+
version: svc.cfg.version,
|
|
6856
|
+
dataDir: svc.cfg.dataDir,
|
|
6857
|
+
pid: process.pid,
|
|
6858
|
+
port: svc.cfg.port
|
|
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
|
+
}));
|
|
6868
|
+
gw.method("app.shutdown", wrap(zEmpty, () => {
|
|
6869
|
+
setTimeout(() => process.exit(0), 100);
|
|
6870
|
+
return { ok: true };
|
|
6871
|
+
}));
|
|
6872
|
+
gw.method("app.openExternal", wrap(z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL) }), async (p) => openExternal(p.url)));
|
|
6873
|
+
gw.method("preview.open", wrap(
|
|
6874
|
+
z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL), threadId: z.string().max(200).optional() }),
|
|
6875
|
+
async (p) => {
|
|
6876
|
+
if (!svc.preview) throw new ApiError("unavailable", tr("err.previewUnavailable"));
|
|
6877
|
+
let r;
|
|
6878
|
+
try {
|
|
6879
|
+
r = await svc.preview.open(p.url);
|
|
6880
|
+
} catch (e) {
|
|
6881
|
+
if (e instanceof PreviewError) {
|
|
6882
|
+
throw new ApiError(e.code === "denied_port" ? "preview_denied" : "preview_url", tr(e.code === "denied_port" ? "err.previewDenied" : "err.previewUrl"));
|
|
6883
|
+
}
|
|
6884
|
+
throw e;
|
|
6885
|
+
}
|
|
6886
|
+
if (p.threadId) {
|
|
6887
|
+
const meta = await svc.threads.get(p.threadId);
|
|
6888
|
+
if (meta && meta.previewUrl !== r.url) {
|
|
6889
|
+
const next = await svc.threads.patch(p.threadId, { previewUrl: r.url });
|
|
6890
|
+
gw.publish("sidebar", { type: "thread.upsert", thread: next });
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
return { ...r, host: previewHost() };
|
|
6894
|
+
}
|
|
6895
|
+
));
|
|
6896
|
+
gw.method("preview.release", wrap(z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL) }), (p) => ({
|
|
6897
|
+
ok: svc.preview?.release(p.url) ?? false
|
|
6898
|
+
})));
|
|
6899
|
+
gw.method("settings.get", wrap(zEmpty, () => svc.settings.get()));
|
|
6900
|
+
gw.method("settings.set", wrap(z.object({ patch: z.record(z.string(), z.unknown()) }), async (p) => {
|
|
6901
|
+
const checked = parseSettingsPatch(p.patch);
|
|
5256
6902
|
if (!checked.ok) throw new ApiError("bad_params", `Invalid setting: ${checked.error}`);
|
|
5257
6903
|
const before = await svc.settings.get();
|
|
5258
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
|
+
}
|
|
5259
6909
|
for (const id2 of Object.keys(checked.patch.providerCommand ?? {})) {
|
|
5260
6910
|
if (JSON.stringify(before.providerCommand?.[id2] ?? null) !== JSON.stringify(next.providerCommand?.[id2] ?? null)) {
|
|
5261
6911
|
clearProviderCache(id2);
|
|
@@ -5352,10 +7002,10 @@ function registerMethods(gw, svc) {
|
|
|
5352
7002
|
gw.method("projects.open", wrap(z.object({ id: z.string() }), async (p) => {
|
|
5353
7003
|
const proj = await svc.projects.get(p.id);
|
|
5354
7004
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5355
|
-
if (!
|
|
7005
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.dirMissing"));
|
|
5356
7006
|
const cmd = process.platform === "win32" ? "explorer.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
5357
7007
|
const target = process.platform === "win32" ? win32Path.normalize(proj.path) : proj.path;
|
|
5358
|
-
|
|
7008
|
+
execFile4(cmd, [target], { windowsHide: true }, () => {
|
|
5359
7009
|
}).on("error", () => {
|
|
5360
7010
|
});
|
|
5361
7011
|
return { ok: true };
|
|
@@ -5364,8 +7014,8 @@ function registerMethods(gw, svc) {
|
|
|
5364
7014
|
let exists = false;
|
|
5365
7015
|
let isDir2 = false;
|
|
5366
7016
|
try {
|
|
5367
|
-
exists =
|
|
5368
|
-
isDir2 = exists &&
|
|
7017
|
+
exists = existsSync9(p.path);
|
|
7018
|
+
isDir2 = exists && statSync5(p.path).isDirectory();
|
|
5369
7019
|
} catch {
|
|
5370
7020
|
}
|
|
5371
7021
|
return { exists, isDir: isDir2 };
|
|
@@ -5393,7 +7043,7 @@ function registerMethods(gw, svc) {
|
|
|
5393
7043
|
async (p) => {
|
|
5394
7044
|
const proj = await svc.projects.get(p.projectId);
|
|
5395
7045
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5396
|
-
if (!
|
|
7046
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
5397
7047
|
if (!p.providerSessionId && !isProviderEnabled(await svc.settings.get(), p.provider)) {
|
|
5398
7048
|
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: catalogById(p.provider)?.name ?? p.provider }));
|
|
5399
7049
|
}
|
|
@@ -5402,8 +7052,10 @@ function registerMethods(gw, svc) {
|
|
|
5402
7052
|
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: cat.name }));
|
|
5403
7053
|
}
|
|
5404
7054
|
const provider = getProvider(p.provider);
|
|
5405
|
-
const
|
|
7055
|
+
const guard = await guardLegacyMode(svc.settings, p.provider, clampMode(provider?.capabilities.modes ?? [], p.mode));
|
|
7056
|
+
const mode2 = guard.mode;
|
|
5406
7057
|
const meta = await svc.threads.create({ ...p, mode: mode2 });
|
|
7058
|
+
if (guard.notice) await svc.threads.appendEvent(meta.id, upgradeNotice());
|
|
5407
7059
|
gw.publish("sidebar", { type: "thread.upsert", thread: meta });
|
|
5408
7060
|
return meta;
|
|
5409
7061
|
}
|
|
@@ -5429,7 +7081,8 @@ function registerMethods(gw, svc) {
|
|
|
5429
7081
|
id: z.string(),
|
|
5430
7082
|
title: z.string().optional(),
|
|
5431
7083
|
pinned: z.boolean().optional(),
|
|
5432
|
-
archived: z.boolean().optional()
|
|
7084
|
+
archived: z.boolean().optional(),
|
|
7085
|
+
previewUrl: z.string().max(MAX_PREVIEW_URL).nullable().optional()
|
|
5433
7086
|
}),
|
|
5434
7087
|
async (p) => {
|
|
5435
7088
|
const meta = await svc.threads.get(p.id);
|
|
@@ -5441,6 +7094,14 @@ function registerMethods(gw, svc) {
|
|
|
5441
7094
|
}
|
|
5442
7095
|
if (p.pinned !== void 0) patch.pinned = p.pinned;
|
|
5443
7096
|
if (p.archived !== void 0) patch.archived = p.archived;
|
|
7097
|
+
if (p.previewUrl !== void 0) {
|
|
7098
|
+
if (p.previewUrl === null) patch.previewUrl = null;
|
|
7099
|
+
else {
|
|
7100
|
+
const c = checkPreviewUrl(p.previewUrl);
|
|
7101
|
+
if (!c.ok) throw new ApiError("bad_params", tr("err.previewUrl"));
|
|
7102
|
+
patch.previewUrl = c.target.href;
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
5444
7105
|
const next = await svc.threads.patch(p.id, patch);
|
|
5445
7106
|
gw.publish("sidebar", { type: "thread.upsert", thread: next });
|
|
5446
7107
|
return next;
|
|
@@ -5457,7 +7118,7 @@ function registerMethods(gw, svc) {
|
|
|
5457
7118
|
const projectPath = async (projectId) => {
|
|
5458
7119
|
const proj = await svc.projects.get(projectId);
|
|
5459
7120
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5460
|
-
if (!
|
|
7121
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
5461
7122
|
return proj.path;
|
|
5462
7123
|
};
|
|
5463
7124
|
gw.method("git.status", wrap(
|
|
@@ -5538,8 +7199,8 @@ function registerMethods(gw, svc) {
|
|
|
5538
7199
|
gw.method("terminal.create", wrap(
|
|
5539
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) }),
|
|
5540
7201
|
async (p) => {
|
|
5541
|
-
const
|
|
5542
|
-
const r = await svc.terminals.create(p.projectId,
|
|
7202
|
+
const path4 = await projectPath(p.projectId);
|
|
7203
|
+
const r = await svc.terminals.create(p.projectId, path4, p.cols, p.rows);
|
|
5543
7204
|
if ("error" in r) throw new ApiError("bad_params", r.error);
|
|
5544
7205
|
return r;
|
|
5545
7206
|
}
|
|
@@ -5564,10 +7225,14 @@ function registerMethods(gw, svc) {
|
|
|
5564
7225
|
id: z.string(),
|
|
5565
7226
|
text: z.string(),
|
|
5566
7227
|
attachments: z.array(z.object({ path: z.string(), mime: z.string(), name: z.string().optional() })).optional(),
|
|
5567
|
-
|
|
7228
|
+
// 1.2: paths dragged from the Files panel, relative to the project (the daemon resolves and checks them)
|
|
7229
|
+
mentions: z.array(z.object({ rel: z.string().min(1).max(4096), isDir: z.boolean().optional() })).max(MAX_MENTIONS).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()
|
|
5568
7233
|
}),
|
|
5569
7234
|
async (p) => {
|
|
5570
|
-
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments }, 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");
|
|
5571
7236
|
return { ok: true, delivered: delivered ?? "started" };
|
|
5572
7237
|
}
|
|
5573
7238
|
));
|
|
@@ -5638,11 +7303,13 @@ function registerMethods(gw, svc) {
|
|
|
5638
7303
|
L.push(`> ${meta.provider} \xB7 ${meta.model ?? tr("export.defaultModel")} \xB7 ${meta.mode} \xB7 ${new Date(meta.createdAt).toLocaleString(intlLocale())}`, "");
|
|
5639
7304
|
for (const e of events) {
|
|
5640
7305
|
switch (e.type) {
|
|
5641
|
-
case "user_message":
|
|
7306
|
+
case "user_message": {
|
|
7307
|
+
const refs = e.mentions?.length ? "\n\n" + e.mentions.map((m) => `- \`${m.path}${m.isDir ? "/" : ""}\``).join("\n") : "";
|
|
5642
7308
|
L.push(`**${tr("export.me")}**${e.steered ? tr("export.steered") : ""}:
|
|
5643
7309
|
|
|
5644
|
-
${e.text}`, "");
|
|
7310
|
+
${e.text}${refs}`, "");
|
|
5645
7311
|
break;
|
|
7312
|
+
}
|
|
5646
7313
|
case "assistant_message":
|
|
5647
7314
|
L.push(e.text, "");
|
|
5648
7315
|
break;
|
|
@@ -5683,7 +7350,7 @@ ${e.text}
|
|
|
5683
7350
|
const markdown = L.join("\n");
|
|
5684
7351
|
const safe = meta.title.replace(/[\\/:*?"<>|\s]+/g, "_").slice(0, 60) || "thread";
|
|
5685
7352
|
const filename = `${safe}.md`;
|
|
5686
|
-
let dir = join12(
|
|
7353
|
+
let dir = join12(homedir9(), "Downloads");
|
|
5687
7354
|
try {
|
|
5688
7355
|
await mkdir3(dir, { recursive: true });
|
|
5689
7356
|
} catch {
|
|
@@ -5691,7 +7358,7 @@ ${e.text}
|
|
|
5691
7358
|
await mkdir3(dir, { recursive: true });
|
|
5692
7359
|
}
|
|
5693
7360
|
let dest = join12(dir, filename);
|
|
5694
|
-
if (
|
|
7361
|
+
if (existsSync9(dest)) {
|
|
5695
7362
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5696
7363
|
dest = join12(dir, `${safe}-${stamp2}.md`);
|
|
5697
7364
|
}
|
|
@@ -5702,18 +7369,18 @@ ${e.text}
|
|
|
5702
7369
|
}
|
|
5703
7370
|
function revealInFileManager(filePath) {
|
|
5704
7371
|
if (process.platform === "win32") {
|
|
5705
|
-
|
|
7372
|
+
execFile4("explorer.exe", ["/select," + win32Path.normalize(filePath)], { windowsHide: true }, () => {
|
|
5706
7373
|
}).on("error", () => {
|
|
5707
7374
|
});
|
|
5708
7375
|
return;
|
|
5709
7376
|
}
|
|
5710
7377
|
if (process.platform === "darwin") {
|
|
5711
|
-
|
|
7378
|
+
execFile4("open", ["-R", filePath], () => {
|
|
5712
7379
|
}).on("error", () => {
|
|
5713
7380
|
});
|
|
5714
7381
|
return;
|
|
5715
7382
|
}
|
|
5716
|
-
|
|
7383
|
+
execFile4("xdg-open", [dirname3(filePath)], () => {
|
|
5717
7384
|
}).on("error", () => {
|
|
5718
7385
|
});
|
|
5719
7386
|
}
|
|
@@ -5729,7 +7396,15 @@ var init_methods = __esm({
|
|
|
5729
7396
|
init_infoCache();
|
|
5730
7397
|
init_launch();
|
|
5731
7398
|
init_modes();
|
|
7399
|
+
init_autoApprove13();
|
|
5732
7400
|
init_i18n();
|
|
7401
|
+
init_mentions();
|
|
7402
|
+
init_openExternal();
|
|
7403
|
+
init_agentShell();
|
|
7404
|
+
init_gitBash();
|
|
7405
|
+
init_proxy();
|
|
7406
|
+
init_url();
|
|
7407
|
+
init_host();
|
|
5733
7408
|
threadModeSchema = z.enum(["plan", "default", "acceptEdits", "bypassPermissions", "ask"]);
|
|
5734
7409
|
ApiError = class extends Error {
|
|
5735
7410
|
code;
|
|
@@ -5790,10 +7465,14 @@ function sanitizeStored(raw) {
|
|
|
5790
7465
|
}
|
|
5791
7466
|
return out;
|
|
5792
7467
|
}
|
|
5793
|
-
var DEFAULT_SETTINGS, IDLE_CLOSE_MIN, IDLE_CLOSE_MAX, mode, id, settingsPatchSchema, SettingsStore;
|
|
7468
|
+
var CONTENT_WIDTHS, CHAT_FONT_MIN, CHAT_FONT_MAX, CHAT_FONT_DEFAULT, DEFAULT_SETTINGS, IDLE_CLOSE_MIN, IDLE_CLOSE_MAX, mode, id, settingsPatchSchema, SettingsStore;
|
|
5794
7469
|
var init_settings = __esm({
|
|
5795
7470
|
"daemon/src/store/settings.ts"() {
|
|
5796
7471
|
init_atomic();
|
|
7472
|
+
CONTENT_WIDTHS = ["narrow", "default", "wide", "full"];
|
|
7473
|
+
CHAT_FONT_MIN = 12;
|
|
7474
|
+
CHAT_FONT_MAX = 18;
|
|
7475
|
+
CHAT_FONT_DEFAULT = 13;
|
|
5797
7476
|
DEFAULT_SETTINGS = {
|
|
5798
7477
|
defaultProvider: "claude",
|
|
5799
7478
|
defaultMode: "default",
|
|
@@ -5801,6 +7480,8 @@ var init_settings = __esm({
|
|
|
5801
7480
|
runningSend: "queue",
|
|
5802
7481
|
showThinking: true,
|
|
5803
7482
|
theme: "auto",
|
|
7483
|
+
contentWidth: "default",
|
|
7484
|
+
chatFontSize: CHAT_FONT_DEFAULT,
|
|
5804
7485
|
idleCloseMinutes: 30,
|
|
5805
7486
|
collapsedProjects: [],
|
|
5806
7487
|
panel: { open: false, tab: "changes", width: 440 },
|
|
@@ -5821,10 +7502,13 @@ var init_settings = __esm({
|
|
|
5821
7502
|
showThinking: z2.boolean(),
|
|
5822
7503
|
// auto = follow the host: Magpie's light/dark inside Magpie, the OS outside it
|
|
5823
7504
|
theme: z2.enum(["auto", "light", "dark"]),
|
|
7505
|
+
contentWidth: z2.enum(CONTENT_WIDTHS),
|
|
7506
|
+
chatFontSize: z2.number().int().min(CHAT_FONT_MIN).max(CHAT_FONT_MAX),
|
|
5824
7507
|
idleCloseMinutes: z2.number().int().min(IDLE_CLOSE_MIN).max(IDLE_CLOSE_MAX),
|
|
5825
7508
|
collapsedProjects: z2.array(id).max(1e3),
|
|
5826
7509
|
panel: z2.object({ open: z2.boolean(), tab: z2.string().max(40), width: z2.number().min(0).max(5e3) }).partial(),
|
|
5827
7510
|
sidebarWidth: z2.number().min(0).max(5e3),
|
|
7511
|
+
sidebarHidden: z2.boolean(),
|
|
5828
7512
|
lastProjectId: id.nullable(),
|
|
5829
7513
|
lastThreadId: id.nullable(),
|
|
5830
7514
|
recentDirs: z2.array(z2.string().max(4096)).max(50),
|
|
@@ -5833,7 +7517,8 @@ var init_settings = __esm({
|
|
|
5833
7517
|
z2.string().max(64),
|
|
5834
7518
|
z2.object({ executable: z2.string().max(4096).optional(), args: z2.array(z2.string().max(4096)).max(64).optional() }).strict()
|
|
5835
7519
|
),
|
|
5836
|
-
v1Migrated: z2.boolean()
|
|
7520
|
+
v1Migrated: z2.boolean(),
|
|
7521
|
+
bashPath: z2.string().max(1024)
|
|
5837
7522
|
}).partial().strict();
|
|
5838
7523
|
SettingsStore = class {
|
|
5839
7524
|
file;
|
|
@@ -5919,8 +7604,8 @@ __export(legacy_exports, {
|
|
|
5919
7604
|
migrateLegacy: () => migrateLegacy
|
|
5920
7605
|
});
|
|
5921
7606
|
import { cp as cp2, mkdir as mkdir8, readdir as readdir6, readFile as readFile18 } from "node:fs/promises";
|
|
5922
|
-
import { existsSync as
|
|
5923
|
-
import { join as
|
|
7607
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
7608
|
+
import { join as join20 } from "node:path";
|
|
5924
7609
|
function stamp() {
|
|
5925
7610
|
const d = /* @__PURE__ */ new Date();
|
|
5926
7611
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -5929,19 +7614,19 @@ function stamp() {
|
|
|
5929
7614
|
async function migrateLegacy(dataDir, stores) {
|
|
5930
7615
|
const settings = await stores.settings.get();
|
|
5931
7616
|
if (settings.v1Migrated) return { migrated: 0, skipped: true };
|
|
5932
|
-
const oldDir =
|
|
5933
|
-
const oldSettingsPath =
|
|
5934
|
-
if (!
|
|
7617
|
+
const oldDir = join20(dataDir, "sessions");
|
|
7618
|
+
const oldSettingsPath = join20(dataDir, "settings.json");
|
|
7619
|
+
if (!existsSync13(oldDir) && !existsSync13(oldSettingsPath)) {
|
|
5935
7620
|
await stores.settings.set({ v1Migrated: true });
|
|
5936
7621
|
return { migrated: 0, skipped: true };
|
|
5937
7622
|
}
|
|
5938
|
-
const backup =
|
|
7623
|
+
const backup = join20(dataDir, "trash", `legacy-${stamp()}`);
|
|
5939
7624
|
await mkdir8(backup, { recursive: true });
|
|
5940
|
-
if (
|
|
7625
|
+
if (existsSync13(oldDir)) await cp2(oldDir, join20(backup, "sessions"), { recursive: true }).catch(() => {
|
|
5941
7626
|
});
|
|
5942
|
-
if (
|
|
7627
|
+
if (existsSync13(oldSettingsPath)) await cp2(oldSettingsPath, join20(backup, "settings.json")).catch(() => {
|
|
5943
7628
|
});
|
|
5944
|
-
const oldSettings =
|
|
7629
|
+
const oldSettings = existsSync13(oldSettingsPath) ? JSON.parse(await readFile18(oldSettingsPath, "utf8").catch(() => "{}")) : {};
|
|
5945
7630
|
const projectIdByPath = /* @__PURE__ */ new Map();
|
|
5946
7631
|
const lc = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
5947
7632
|
for (const p of oldSettings.projects ?? []) {
|
|
@@ -5955,13 +7640,13 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5955
7640
|
}
|
|
5956
7641
|
let sessionIds = [];
|
|
5957
7642
|
try {
|
|
5958
|
-
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) &&
|
|
7643
|
+
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) && existsSync13(join20(oldDir, d, "meta.json")));
|
|
5959
7644
|
} catch {
|
|
5960
7645
|
}
|
|
5961
7646
|
const metas = [];
|
|
5962
7647
|
for (const id2 of sessionIds) {
|
|
5963
7648
|
try {
|
|
5964
|
-
const m = JSON.parse(await readFile18(
|
|
7649
|
+
const m = JSON.parse(await readFile18(join20(oldDir, id2, "meta.json"), "utf8"));
|
|
5965
7650
|
if (m?.cwd) metas.push(m);
|
|
5966
7651
|
} catch {
|
|
5967
7652
|
}
|
|
@@ -5975,7 +7660,7 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5975
7660
|
extraDirs.push(m.cwd);
|
|
5976
7661
|
}
|
|
5977
7662
|
for (const d of extraDirs) {
|
|
5978
|
-
if (!
|
|
7663
|
+
if (!existsSync13(d)) continue;
|
|
5979
7664
|
try {
|
|
5980
7665
|
const name = d.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? d;
|
|
5981
7666
|
const proj = await stores.projects.create(d, name);
|
|
@@ -5994,7 +7679,7 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5994
7679
|
return best ? projectIdByPath.get(best) : null;
|
|
5995
7680
|
};
|
|
5996
7681
|
let migrated = 0;
|
|
5997
|
-
const threadsDir =
|
|
7682
|
+
const threadsDir = join20(dataDir, "threads");
|
|
5998
7683
|
for (const m of metas) {
|
|
5999
7684
|
try {
|
|
6000
7685
|
const projectId = projectOf(m.cwd);
|
|
@@ -6031,10 +7716,10 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
6031
7716
|
importedFrom: m.cliSessionId ? { provider, sessionId: m.cliSessionId } : void 0,
|
|
6032
7717
|
seq: 0
|
|
6033
7718
|
};
|
|
6034
|
-
const tdir =
|
|
6035
|
-
if (
|
|
6036
|
-
await mkdir8(
|
|
6037
|
-
const oldEvts = (await readFile18(
|
|
7719
|
+
const tdir = join20(threadsDir, meta.id);
|
|
7720
|
+
if (existsSync13(tdir)) meta.id = `t_${m.id}_v1`.slice(0, 80);
|
|
7721
|
+
await mkdir8(join20(threadsDir, meta.id), { recursive: true });
|
|
7722
|
+
const oldEvts = (await readFile18(join20(oldDir, m.id, "events.jsonl"), "utf8").catch(() => "")).split("\n").filter((l) => l.trim()).map((l) => {
|
|
6038
7723
|
try {
|
|
6039
7724
|
return JSON.parse(l);
|
|
6040
7725
|
} catch {
|
|
@@ -6113,9 +7798,9 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
6113
7798
|
seq++;
|
|
6114
7799
|
lines.push({ type: "tool_call", itemId: key, kind: "other", title: o.title, status: "failed", seq, ts: Date.now(), threadId: meta.id });
|
|
6115
7800
|
}
|
|
6116
|
-
for (const ev of lines) await appendJsonl(
|
|
7801
|
+
for (const ev of lines) await appendJsonl(join20(threadsDir, meta.id, "timeline.jsonl"), ev);
|
|
6117
7802
|
meta.seq = seq;
|
|
6118
|
-
await writeJsonAtomic(
|
|
7803
|
+
await writeJsonAtomic(join20(threadsDir, meta.id, "meta.json"), meta);
|
|
6119
7804
|
migrated++;
|
|
6120
7805
|
} catch (e) {
|
|
6121
7806
|
log("warn", "\u8FC1\u79FB\uFF1A\u4F1A\u8BDD\u5931\u8D25", { id: m.id, err: String(e) });
|
|
@@ -6180,8 +7865,8 @@ var init_warm = __esm({
|
|
|
6180
7865
|
|
|
6181
7866
|
// daemon/src/main.ts
|
|
6182
7867
|
import { mkdirSync as mkdirSync2, rmSync as rmSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
6183
|
-
import { join as
|
|
6184
|
-
import { fileURLToPath as
|
|
7868
|
+
import { join as join21, dirname as dirname8 } from "node:path";
|
|
7869
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6185
7870
|
|
|
6186
7871
|
// daemon/src/config.ts
|
|
6187
7872
|
import { randomBytes } from "node:crypto";
|
|
@@ -6196,8 +7881,8 @@ import { dirname, join } from "node:path";
|
|
|
6196
7881
|
import { fileURLToPath } from "node:url";
|
|
6197
7882
|
function resolveDaemonMainJs() {
|
|
6198
7883
|
const self = fileURLToPath(import.meta.url);
|
|
6199
|
-
const
|
|
6200
|
-
if (
|
|
7884
|
+
const norm2 = self.replace(/\\/g, "/");
|
|
7885
|
+
if (norm2.endsWith("/daemon/dist/main.js") || norm2.endsWith("/dist/main.js")) return self;
|
|
6201
7886
|
const here = dirname(self);
|
|
6202
7887
|
const candidates = [
|
|
6203
7888
|
join(here, "..", "..", "dist", "main.js"),
|
|
@@ -6236,6 +7921,7 @@ function parseConfig(argv) {
|
|
|
6236
7921
|
let data = null;
|
|
6237
7922
|
let token = null;
|
|
6238
7923
|
let parentPid;
|
|
7924
|
+
let gatePort;
|
|
6239
7925
|
for (let i = 0; i < argv.length; i++) {
|
|
6240
7926
|
const a = argv[i];
|
|
6241
7927
|
if (a === "--port" && argv[i + 1]) port = Number(argv[++i]);
|
|
@@ -6246,6 +7932,8 @@ function parseConfig(argv) {
|
|
|
6246
7932
|
else if (a.startsWith("--token=")) token = a.slice(8);
|
|
6247
7933
|
else if (a === "--parent-pid" && argv[i + 1]) parentPid = Number(argv[++i]);
|
|
6248
7934
|
else if (a.startsWith("--parent-pid=")) parentPid = Number(a.slice(13));
|
|
7935
|
+
else if (a === "--gate-port" && argv[i + 1]) gatePort = Number(argv[++i]);
|
|
7936
|
+
else if (a.startsWith("--gate-port=")) gatePort = Number(a.slice(12));
|
|
6249
7937
|
}
|
|
6250
7938
|
if (port == null || !Number.isInteger(port) || port < 0 || port > 65535) {
|
|
6251
7939
|
const envPort = Number(process.env.AGENT_CONSOLE_PORT || "");
|
|
@@ -6261,7 +7949,8 @@ function parseConfig(argv) {
|
|
|
6261
7949
|
version: pkgVersion(),
|
|
6262
7950
|
idleExitMinutes: Number.isFinite(idleEnv) && idleEnv > 0 ? idleEnv : 60,
|
|
6263
7951
|
buildStamp: daemonBuildStamp(),
|
|
6264
|
-
parentPid: Number.isInteger(parentPid) ? parentPid : void 0
|
|
7952
|
+
parentPid: Number.isInteger(parentPid) ? parentPid : void 0,
|
|
7953
|
+
gatePort: Number.isInteger(gatePort) && gatePort > 0 && gatePort <= 65535 ? gatePort : void 0
|
|
6265
7954
|
};
|
|
6266
7955
|
}
|
|
6267
7956
|
|
|
@@ -6269,35 +7958,13 @@ function parseConfig(argv) {
|
|
|
6269
7958
|
init_log();
|
|
6270
7959
|
|
|
6271
7960
|
// daemon/src/http.ts
|
|
7961
|
+
init_security();
|
|
7962
|
+
init_log();
|
|
7963
|
+
init_i18n();
|
|
6272
7964
|
import { createServer } from "node:http";
|
|
6273
7965
|
import { readFile, stat } from "node:fs/promises";
|
|
6274
7966
|
import { existsSync as existsSync2 } from "node:fs";
|
|
6275
7967
|
import { join as join4, normalize, extname } from "node:path";
|
|
6276
|
-
|
|
6277
|
-
// daemon/src/security.ts
|
|
6278
|
-
import { timingSafeEqual, createHash } from "node:crypto";
|
|
6279
|
-
function checkHost(host, port) {
|
|
6280
|
-
if (!host) return { ok: false, code: 421, message: "Host \u4E0D\u5BF9" };
|
|
6281
|
-
const h = host.toLowerCase();
|
|
6282
|
-
if (h === `127.0.0.1:${port}` || h === `localhost:${port}`) return { ok: true };
|
|
6283
|
-
return { ok: false, code: 421, message: "Host \u4E0D\u5BF9" };
|
|
6284
|
-
}
|
|
6285
|
-
function classifyOrigin(origin, port) {
|
|
6286
|
-
if (origin === void 0 || origin === "") return "none";
|
|
6287
|
-
if (origin === "null") return "opaque";
|
|
6288
|
-
const o = origin.toLowerCase();
|
|
6289
|
-
if (o === `http://127.0.0.1:${port}` || o === `http://localhost:${port}`) return "self";
|
|
6290
|
-
return "foreign";
|
|
6291
|
-
}
|
|
6292
|
-
function tokenEqual(a, b) {
|
|
6293
|
-
const ha = createHash("sha256").update(a).digest();
|
|
6294
|
-
const hb = createHash("sha256").update(b).digest();
|
|
6295
|
-
return timingSafeEqual(ha, hb);
|
|
6296
|
-
}
|
|
6297
|
-
|
|
6298
|
-
// daemon/src/http.ts
|
|
6299
|
-
init_log();
|
|
6300
|
-
init_i18n();
|
|
6301
7968
|
var MIME = {
|
|
6302
7969
|
".html": "text/html; charset=utf-8",
|
|
6303
7970
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -6553,17 +8220,18 @@ var AttachmentStore = class {
|
|
|
6553
8220
|
|
|
6554
8221
|
// daemon/src/main.ts
|
|
6555
8222
|
init_settings();
|
|
8223
|
+
init_agentShell();
|
|
6556
8224
|
|
|
6557
8225
|
// daemon/src/store/projects.ts
|
|
6558
8226
|
init_atomic();
|
|
6559
8227
|
init_paths();
|
|
6560
8228
|
init_methods();
|
|
6561
8229
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
6562
|
-
import { existsSync as
|
|
8230
|
+
import { existsSync as existsSync10, statSync as statSync6 } from "node:fs";
|
|
6563
8231
|
import { join as join14 } from "node:path";
|
|
6564
|
-
import { randomBytes as
|
|
8232
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
6565
8233
|
function newId() {
|
|
6566
|
-
return "p_" + Date.now().toString(36) +
|
|
8234
|
+
return "p_" + Date.now().toString(36) + randomBytes4(3).toString("hex");
|
|
6567
8235
|
}
|
|
6568
8236
|
var ProjectsStore = class {
|
|
6569
8237
|
file;
|
|
@@ -6592,19 +8260,19 @@ var ProjectsStore = class {
|
|
|
6592
8260
|
this.writing = this.writing.then(() => writeJsonAtomic(this.file, snap));
|
|
6593
8261
|
await this.writing;
|
|
6594
8262
|
}
|
|
6595
|
-
async create(
|
|
6596
|
-
if (!
|
|
6597
|
-
if (!
|
|
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()) {
|
|
6598
8266
|
throw new ApiError("bad_path", "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939");
|
|
6599
8267
|
}
|
|
6600
|
-
const np = normPath(
|
|
8268
|
+
const np = normPath(path4);
|
|
6601
8269
|
const list = await this.list();
|
|
6602
8270
|
const dup = list.find((p) => normPath(p.path) === np);
|
|
6603
8271
|
if (dup) return dup;
|
|
6604
8272
|
const proj = {
|
|
6605
8273
|
id: newId(),
|
|
6606
|
-
name: name?.trim() ||
|
|
6607
|
-
path:
|
|
8274
|
+
name: name?.trim() || path4.replace(/[\\/]+$/, "").split(/[\\/]/).pop() || path4,
|
|
8275
|
+
path: path4,
|
|
6608
8276
|
createdAt: Date.now()
|
|
6609
8277
|
};
|
|
6610
8278
|
list.push(proj);
|
|
@@ -6636,9 +8304,9 @@ init_methods();
|
|
|
6636
8304
|
init_i18n();
|
|
6637
8305
|
import { mkdir as mkdir6, rename as rename3, readdir as readdir4, readFile as readFile15 } from "node:fs/promises";
|
|
6638
8306
|
import { join as join15 } from "node:path";
|
|
6639
|
-
import { randomBytes as
|
|
8307
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
6640
8308
|
function newId2() {
|
|
6641
|
-
return "t_" + Date.now().toString(36) +
|
|
8309
|
+
return "t_" + Date.now().toString(36) + randomBytes5(3).toString("hex");
|
|
6642
8310
|
}
|
|
6643
8311
|
var ThreadsStore = class {
|
|
6644
8312
|
dir;
|
|
@@ -6653,6 +8321,15 @@ var ThreadsStore = class {
|
|
|
6653
8321
|
});
|
|
6654
8322
|
return p;
|
|
6655
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
|
+
}
|
|
6656
8333
|
constructor(dataDir) {
|
|
6657
8334
|
this.dir = join15(dataDir, "threads");
|
|
6658
8335
|
this.trashDir = join15(dataDir, "trash", "threads");
|
|
@@ -6784,10 +8461,11 @@ var ThreadsStore = class {
|
|
|
6784
8461
|
};
|
|
6785
8462
|
|
|
6786
8463
|
// daemon/src/ws/gateway.ts
|
|
6787
|
-
|
|
6788
|
-
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
8464
|
+
init_security();
|
|
6789
8465
|
init_log();
|
|
6790
8466
|
init_i18n();
|
|
8467
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
8468
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
6791
8469
|
var Conn = class {
|
|
6792
8470
|
id;
|
|
6793
8471
|
ws;
|
|
@@ -6796,7 +8474,7 @@ var Conn = class {
|
|
|
6796
8474
|
alive = true;
|
|
6797
8475
|
lastMsg = Date.now();
|
|
6798
8476
|
constructor(ws) {
|
|
6799
|
-
this.id = "c_" +
|
|
8477
|
+
this.id = "c_" + randomBytes6(4).toString("hex");
|
|
6800
8478
|
this.ws = ws;
|
|
6801
8479
|
}
|
|
6802
8480
|
send(obj) {
|
|
@@ -6840,6 +8518,8 @@ var Gateway = class {
|
|
|
6840
8518
|
methods = /* @__PURE__ */ new Map();
|
|
6841
8519
|
helloTimers = /* @__PURE__ */ new Map();
|
|
6842
8520
|
onTerminalInput;
|
|
8521
|
+
/** Called after a connection closes, with how many are left (the preview proxy closes when none are) */
|
|
8522
|
+
onConnectionClosed;
|
|
6843
8523
|
method(name, fn) {
|
|
6844
8524
|
this.methods.set(name, fn);
|
|
6845
8525
|
}
|
|
@@ -6890,6 +8570,10 @@ var Gateway = class {
|
|
|
6890
8570
|
});
|
|
6891
8571
|
ws.on("close", () => {
|
|
6892
8572
|
this.conns.delete(conn.id);
|
|
8573
|
+
try {
|
|
8574
|
+
this.onConnectionClosed?.(this.conns.size);
|
|
8575
|
+
} catch {
|
|
8576
|
+
}
|
|
6893
8577
|
const t = this.helloTimers.get(conn.id);
|
|
6894
8578
|
if (t) clearTimeout(t);
|
|
6895
8579
|
this.helloTimers.delete(conn.id);
|
|
@@ -6988,6 +8672,8 @@ init_registry();
|
|
|
6988
8672
|
// daemon/src/agents/manager.ts
|
|
6989
8673
|
init_launch();
|
|
6990
8674
|
init_modes();
|
|
8675
|
+
init_autoApprove13();
|
|
8676
|
+
init_autoApprove();
|
|
6991
8677
|
init_attachmentRefs();
|
|
6992
8678
|
init_registry();
|
|
6993
8679
|
init_catalog();
|
|
@@ -7037,7 +8723,16 @@ var Coalescer = class {
|
|
|
7037
8723
|
init_methods();
|
|
7038
8724
|
init_log();
|
|
7039
8725
|
init_i18n();
|
|
8726
|
+
init_agentShell();
|
|
8727
|
+
init_mentions();
|
|
7040
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
|
+
}
|
|
7041
8736
|
var SLASH_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
7042
8737
|
var MAX_WARM_IDLE = 3;
|
|
7043
8738
|
var DELTA_TYPES = ["assistant_delta", "reasoning_delta", "tool_output_delta"];
|
|
@@ -7062,6 +8757,17 @@ var AgentManager = class {
|
|
|
7062
8757
|
resolveAttachments(refs) {
|
|
7063
8758
|
return resolveAttachmentRefs(this.attachments, refs);
|
|
7064
8759
|
}
|
|
8760
|
+
/** Mentions from the Files panel → absolute paths inside the project; a path that can't be used gets a notice */
|
|
8761
|
+
async resolveMentionRefs(threadId, projectId, refs) {
|
|
8762
|
+
if (!refs?.length) return [];
|
|
8763
|
+
const proj = await this.projects.get(projectId);
|
|
8764
|
+
if (!proj) return [];
|
|
8765
|
+
const { mentions, warnings } = await resolveMentions(proj.path, refs);
|
|
8766
|
+
if (warnings.length) {
|
|
8767
|
+
await this.appendRaw(threadId, { type: "notice", level: "warn", message: tr("notice.mentionSkipped", { paths: warnings.join(", ") }) });
|
|
8768
|
+
}
|
|
8769
|
+
return mentions;
|
|
8770
|
+
}
|
|
7065
8771
|
attachmentLabels(refs) {
|
|
7066
8772
|
return refs.map((a) => ({ id: a.path, name: (a.name ?? "").trim() || a.path, mime: a.mime }));
|
|
7067
8773
|
}
|
|
@@ -7133,6 +8839,8 @@ var AgentManager = class {
|
|
|
7133
8839
|
if (!rt) return null;
|
|
7134
8840
|
return {
|
|
7135
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,
|
|
7136
8844
|
queue: rt.queue,
|
|
7137
8845
|
pending: [...rt.pending.values()].map((p) => ({
|
|
7138
8846
|
requestId: p.requestId,
|
|
@@ -7177,8 +8885,11 @@ var AgentManager = class {
|
|
|
7177
8885
|
for (const t of DELTA_TYPES) rt.coalescer.flushItem(deltaKey(t, itemId));
|
|
7178
8886
|
}
|
|
7179
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 */
|
|
7180
8890
|
async send(threadId, input, behavior = "queue") {
|
|
7181
8891
|
const meta = await this.mustThread(threadId);
|
|
8892
|
+
const mentions = await this.resolveMentionRefs(threadId, meta.projectId, input.mentions);
|
|
7182
8893
|
if (meta.archived) {
|
|
7183
8894
|
await this.threads.patch(threadId, { archived: false });
|
|
7184
8895
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
@@ -7202,64 +8913,76 @@ var AgentManager = class {
|
|
|
7202
8913
|
if (behavior === "steer" && provider?.capabilities.steer && rt.session?.steer && !rt.starting) {
|
|
7203
8914
|
try {
|
|
7204
8915
|
const images = await this.resolveAttachments(attachmentRefs);
|
|
7205
|
-
await rt.session.steer({ text: input.text, images });
|
|
8916
|
+
await rt.session.steer({ text: withMentions(input.text, mentions), images, mentions });
|
|
7206
8917
|
await this.appendRaw(threadId, {
|
|
7207
8918
|
type: "user_message",
|
|
7208
8919
|
text: input.text,
|
|
7209
8920
|
steered: true,
|
|
7210
|
-
attachments: this.attachmentLabels(attachmentRefs)
|
|
8921
|
+
attachments: this.attachmentLabels(attachmentRefs),
|
|
8922
|
+
...mentions.length ? { mentions } : {},
|
|
8923
|
+
...input.clientId ? { clientId: input.clientId } : {}
|
|
7211
8924
|
});
|
|
7212
8925
|
return "steered";
|
|
7213
8926
|
} catch (e) {
|
|
7214
8927
|
log("info", `thread ${threadId} \u63D2\u8BDD\u5931\u8D25\uFF0C\u6539\u4E3A\u6392\u961F\uFF1A${String(e)}`);
|
|
7215
8928
|
}
|
|
7216
8929
|
}
|
|
7217
|
-
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs };
|
|
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 } : {} };
|
|
7218
8931
|
rt.queue.push(q);
|
|
7219
8932
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
7220
8933
|
return "queued";
|
|
7221
8934
|
}
|
|
7222
|
-
await this.startTurn(threadId, input.text, attachmentRefs);
|
|
8935
|
+
await this.startTurn(threadId, input.text, attachmentRefs, mentions, input.clientId);
|
|
7223
8936
|
return "started";
|
|
7224
8937
|
}
|
|
7225
|
-
async startTurn(threadId, text, attachmentRefs) {
|
|
8938
|
+
async startTurn(threadId, text, attachmentRefs, mentions = [], clientId) {
|
|
7226
8939
|
const rt = this.rt(threadId);
|
|
7227
8940
|
rt.starting = true;
|
|
7228
8941
|
try {
|
|
7229
|
-
await this.startTurnInner(threadId, text, attachmentRefs);
|
|
8942
|
+
await this.startTurnInner(threadId, text, attachmentRefs, mentions, clientId);
|
|
7230
8943
|
} finally {
|
|
7231
8944
|
rt.starting = false;
|
|
7232
8945
|
}
|
|
7233
8946
|
}
|
|
7234
|
-
async startTurnInner(threadId, text, attachmentRefs) {
|
|
8947
|
+
async startTurnInner(threadId, text, attachmentRefs, mentions, clientId) {
|
|
7235
8948
|
const meta = await this.mustThread(threadId);
|
|
7236
8949
|
const rt = this.rt(threadId);
|
|
7237
|
-
const
|
|
8950
|
+
const allowDisabled = !!meta.providerSessionId;
|
|
8951
|
+
this.assertProviderAllowed(meta.provider, await this.settings.get(), { allowDisabled });
|
|
8952
|
+
await this.appendRaw(threadId, {
|
|
8953
|
+
type: "user_message",
|
|
8954
|
+
text,
|
|
8955
|
+
attachments: this.attachmentLabels(attachmentRefs),
|
|
8956
|
+
...mentions.length ? { mentions } : {},
|
|
8957
|
+
...clientId ? { clientId } : {}
|
|
8958
|
+
});
|
|
8959
|
+
const trimmed = titleText(text);
|
|
8960
|
+
if (meta.titleAuto && trimmed) {
|
|
8961
|
+
await this.threads.patch(threadId, { title: trimmed.slice(0, 40), titleAuto: true });
|
|
8962
|
+
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
8963
|
+
}
|
|
7238
8964
|
if (!rt.session) {
|
|
7239
8965
|
await this.setStatus(threadId, "initializing");
|
|
7240
8966
|
try {
|
|
8967
|
+
await this.assertProviderUsable(meta.provider, { allowDisabled });
|
|
7241
8968
|
await this.createSessionIfNeeded(threadId, { updateStatus: true });
|
|
7242
8969
|
} catch (e) {
|
|
7243
8970
|
await this.appendRaw(threadId, { type: "notice", level: "error", message: tr("notice.sessionStartFailed", { detail: String(e?.message ?? e) }) });
|
|
7244
8971
|
await this.appendRaw(threadId, { type: "turn_ended", status: "failed", error: String(e?.message ?? e) });
|
|
8972
|
+
await this.threads.patch(threadId, { lastTurnStatus: "failed" });
|
|
7245
8973
|
await this.setStatus(threadId, "error");
|
|
7246
8974
|
throw e;
|
|
7247
8975
|
}
|
|
7248
8976
|
}
|
|
7249
|
-
|
|
7250
|
-
if (
|
|
7251
|
-
|
|
7252
|
-
this.
|
|
8977
|
+
await agentShellReady();
|
|
8978
|
+
if (!rt.bashHinted && bashMissingFor(meta.provider)) {
|
|
8979
|
+
rt.bashHinted = true;
|
|
8980
|
+
await this.appendRaw(threadId, { type: "notice", level: "warn", code: "bash_missing", message: tr("notice.bashMissing") });
|
|
7253
8981
|
}
|
|
7254
|
-
await this.appendRaw(threadId, {
|
|
7255
|
-
type: "user_message",
|
|
7256
|
-
text,
|
|
7257
|
-
attachments: this.attachmentLabels(attachmentRefs)
|
|
7258
|
-
});
|
|
7259
8982
|
await this.setStatus(threadId, "running");
|
|
7260
8983
|
try {
|
|
7261
8984
|
const images = await this.resolveAttachments(attachmentRefs);
|
|
7262
|
-
await rt.session.send({ text, images });
|
|
8985
|
+
await rt.session.send({ text: withMentions(text, mentions), images, mentions });
|
|
7263
8986
|
} catch (e) {
|
|
7264
8987
|
const msg = String(e?.message ?? e);
|
|
7265
8988
|
await this.appendRaw(threadId, { type: "notice", level: "error", message: tr("notice.sendFailed", { detail: msg }) });
|
|
@@ -7357,9 +9080,21 @@ var AgentManager = class {
|
|
|
7357
9080
|
},
|
|
7358
9081
|
event: ev
|
|
7359
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
|
+
}
|
|
7360
9094
|
const toolName = ev.detail?.toolName;
|
|
7361
9095
|
if (toolName && rt.allowAlways.has(toolName)) {
|
|
7362
|
-
|
|
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" });
|
|
7363
9098
|
return;
|
|
7364
9099
|
}
|
|
7365
9100
|
rt.pending.set(ev.requestId, pending);
|
|
@@ -7390,7 +9125,7 @@ var AgentManager = class {
|
|
|
7390
9125
|
});
|
|
7391
9126
|
if (next) {
|
|
7392
9127
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
7393
|
-
await this.startTurn(threadId, next.text, next.attachments).catch((e) => {
|
|
9128
|
+
await this.startTurn(threadId, next.text, next.attachments, next.mentions ?? [], next.clientId).catch((e) => {
|
|
7394
9129
|
log("warn", `thread ${threadId} \u6392\u961F\u6D88\u606F\u542F\u52A8\u5931\u8D25\uFF1A${String(e)}`);
|
|
7395
9130
|
});
|
|
7396
9131
|
}
|
|
@@ -7441,6 +9176,22 @@ var AgentManager = class {
|
|
|
7441
9176
|
}
|
|
7442
9177
|
rt.session?.respond(requestId, answer);
|
|
7443
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
|
+
}
|
|
7444
9195
|
// ---- interrupt ----
|
|
7445
9196
|
async interrupt(threadId) {
|
|
7446
9197
|
const rt = this.rt(threadId);
|
|
@@ -7485,8 +9236,9 @@ var AgentManager = class {
|
|
|
7485
9236
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
7486
9237
|
}
|
|
7487
9238
|
async setMode(threadId, mode2) {
|
|
7488
|
-
await this.threads.patch(threadId, { mode: mode2 });
|
|
9239
|
+
const meta = await this.threads.patch(threadId, { mode: mode2 });
|
|
7489
9240
|
const rt = this.rt(threadId);
|
|
9241
|
+
if (mode2 === FULL_AUTO) await this.autoApprovePending(threadId, meta.provider, mode2);
|
|
7490
9242
|
if (rt.session) await rt.session.setMode(mode2);
|
|
7491
9243
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
7492
9244
|
}
|
|
@@ -7525,7 +9277,8 @@ var AgentManager = class {
|
|
|
7525
9277
|
throw new ApiError("busy", tr("err.busySwitch"));
|
|
7526
9278
|
}
|
|
7527
9279
|
const events = await this.threads.readEvents(threadId, 0, 5e3);
|
|
7528
|
-
|
|
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) {
|
|
7529
9282
|
throw new ApiError("bad_params", tr("err.hasMessages"));
|
|
7530
9283
|
}
|
|
7531
9284
|
const p = await this.assertProviderUsable(providerId);
|
|
@@ -7538,14 +9291,16 @@ var AgentManager = class {
|
|
|
7538
9291
|
rt.commands = [];
|
|
7539
9292
|
}
|
|
7540
9293
|
rt.status = "idle";
|
|
9294
|
+
const guard = await guardLegacyMode(this.settings, providerId, clampMode(p.capabilities.modes, meta.mode));
|
|
7541
9295
|
await this.threads.patch(threadId, {
|
|
7542
9296
|
provider: providerId,
|
|
7543
9297
|
model: null,
|
|
7544
|
-
mode:
|
|
9298
|
+
mode: guard.mode,
|
|
7545
9299
|
effort: null,
|
|
7546
9300
|
providerSessionId: null,
|
|
7547
9301
|
status: "idle"
|
|
7548
9302
|
});
|
|
9303
|
+
if (guard.notice) await this.appendRaw(threadId, upgradeNotice());
|
|
7549
9304
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
7550
9305
|
}
|
|
7551
9306
|
async close(threadId) {
|
|
@@ -7662,6 +9417,7 @@ var AgentManager = class {
|
|
|
7662
9417
|
const settings = await this.settings.get();
|
|
7663
9418
|
const launch = launchFromSettings(settings, meta.provider);
|
|
7664
9419
|
rt.deltasOnly = !!provider.capabilities.deltasOnly;
|
|
9420
|
+
await agentShellReady();
|
|
7665
9421
|
rt.session = await provider.createSession({
|
|
7666
9422
|
threadId,
|
|
7667
9423
|
cwd: proj.path,
|
|
@@ -7690,10 +9446,10 @@ var AgentManager = class {
|
|
|
7690
9446
|
providerOf(meta) {
|
|
7691
9447
|
return getProvider(meta.provider);
|
|
7692
9448
|
}
|
|
7693
|
-
|
|
9449
|
+
/** The checks that start nothing: a known Agent, enabled (unless allowDisabled), not a planned one */
|
|
9450
|
+
assertProviderAllowed(providerId, s, opts = {}) {
|
|
7694
9451
|
const p = getProvider(providerId);
|
|
7695
9452
|
if (!p) throw new ApiError("bad_params", tr("provider.unknown", { id: providerId }));
|
|
7696
|
-
const s = await this.settings.get();
|
|
7697
9453
|
if (!opts.allowDisabled && !isProviderEnabled(s, providerId)) {
|
|
7698
9454
|
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: p.name }));
|
|
7699
9455
|
}
|
|
@@ -7701,16 +9457,21 @@ var AgentManager = class {
|
|
|
7701
9457
|
if (entry && !providerCreatable(entry.status)) {
|
|
7702
9458
|
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: p.name }));
|
|
7703
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);
|
|
7704
9465
|
const det = await cachedDetect(p, launchFromSettings(s, providerId));
|
|
7705
9466
|
if (!det.available) throw new ApiError("bad_params", det.reason ?? tr("provider.unavailable", { name: p.name }));
|
|
7706
9467
|
return p;
|
|
7707
9468
|
}
|
|
7708
|
-
async setStatus(threadId,
|
|
9469
|
+
async setStatus(threadId, status2) {
|
|
7709
9470
|
const rt = this.rt(threadId);
|
|
7710
|
-
rt.status =
|
|
7711
|
-
const meta = await this.threads.patch(threadId, { status });
|
|
9471
|
+
rt.status = status2;
|
|
9472
|
+
const meta = await this.threads.patch(threadId, { status: status2 });
|
|
7712
9473
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: meta });
|
|
7713
|
-
this.gw.publish(`thread:${threadId}`, { type: "status", status });
|
|
9474
|
+
this.gw.publish(`thread:${threadId}`, { type: "status", status: status2 });
|
|
7714
9475
|
}
|
|
7715
9476
|
// Append an event: seq is assigned in the store's serialized section, persisted, then pushed to thread:<id>
|
|
7716
9477
|
async appendRaw(threadId, ev) {
|
|
@@ -7755,18 +9516,20 @@ var AgentManager = class {
|
|
|
7755
9516
|
|
|
7756
9517
|
// daemon/src/git/service.ts
|
|
7757
9518
|
init_i18n();
|
|
7758
|
-
|
|
7759
|
-
import {
|
|
7760
|
-
import {
|
|
7761
|
-
import {
|
|
9519
|
+
init_decode();
|
|
9520
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
9521
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
9522
|
+
import { cp, mkdir as mkdir7, readFile as readFile16, rename as rename4, rm, stat as stat10 } from "node:fs/promises";
|
|
9523
|
+
import { join as join16, normalize as normalize2, sep as sep3 } from "node:path";
|
|
7762
9524
|
var TIMEOUT2 = 15e3;
|
|
7763
9525
|
var DIFF_LIMIT = 1024 * 1024;
|
|
7764
9526
|
function runGit(cwd, args) {
|
|
7765
9527
|
return new Promise((res) => {
|
|
7766
|
-
|
|
9528
|
+
execFile5("git", ["-c", "core.quotepath=false", ...args], { cwd, timeout: TIMEOUT2, maxBuffer: 8 * 1024 * 1024, windowsHide: true, encoding: "buffer" }, (e, stdout, stderr) => {
|
|
7767
9529
|
const code = e && typeof e.code === "number" ? e.code : e ? 1 : 0;
|
|
7768
9530
|
const missing = e?.code === "ENOENT";
|
|
7769
|
-
|
|
9531
|
+
const errText = stderr?.length ? decodeOutput(stderr) : "";
|
|
9532
|
+
res({ code, out: stdout ? stdout.toString("utf8") : "", err: missing ? tr("git.missing") : errText || (e?.message ?? "") });
|
|
7770
9533
|
});
|
|
7771
9534
|
});
|
|
7772
9535
|
}
|
|
@@ -7774,7 +9537,7 @@ function insidePath(projectPath, p) {
|
|
|
7774
9537
|
const rel = normalize2(p).replace(/\\/g, "/");
|
|
7775
9538
|
const abs = normalize2(join16(projectPath, rel));
|
|
7776
9539
|
const proj = normalize2(projectPath);
|
|
7777
|
-
if (abs !== proj && !abs.startsWith(proj +
|
|
9540
|
+
if (abs !== proj && !abs.startsWith(proj + sep3)) return null;
|
|
7778
9541
|
return rel.replace(/\\/g, "/");
|
|
7779
9542
|
}
|
|
7780
9543
|
async function moveAcrossDevices(from, to, ops = {}) {
|
|
@@ -7803,7 +9566,7 @@ var GitService = class {
|
|
|
7803
9566
|
if (!rec5 || rec5.length < 4) continue;
|
|
7804
9567
|
const index = rec5[0];
|
|
7805
9568
|
const worktree = rec5[1];
|
|
7806
|
-
let
|
|
9569
|
+
let path4 = rec5.slice(3);
|
|
7807
9570
|
let kind = "modified";
|
|
7808
9571
|
if (index === "?" && worktree === "?") kind = "untracked";
|
|
7809
9572
|
else if (index === "R" || worktree === "R") {
|
|
@@ -7811,7 +9574,7 @@ var GitService = class {
|
|
|
7811
9574
|
i++;
|
|
7812
9575
|
} else if (index === "A" || worktree === "A") kind = "added";
|
|
7813
9576
|
else if (index === "D" || worktree === "D") kind = "deleted";
|
|
7814
|
-
files.push({ path:
|
|
9577
|
+
files.push({ path: path4, index, worktree, kind });
|
|
7815
9578
|
}
|
|
7816
9579
|
return { isRepo: true, branch: br.out.trim() || void 0, files };
|
|
7817
9580
|
}
|
|
@@ -7822,8 +9585,8 @@ var GitService = class {
|
|
|
7822
9585
|
const ls = await runGit(projectPath, ["ls-files", "--error-unmatch", "--", rel]);
|
|
7823
9586
|
if (ls.code !== 0) {
|
|
7824
9587
|
const full = join16(projectPath, rel);
|
|
7825
|
-
if (!
|
|
7826
|
-
const s = await
|
|
9588
|
+
if (!existsSync11(full)) return { error: tr("fs.fileMissing") };
|
|
9589
|
+
const s = await stat10(full);
|
|
7827
9590
|
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
7828
9591
|
if (s.size > DIFF_LIMIT) return { tooLarge: true };
|
|
7829
9592
|
const text = await readFile16(full, "utf8").catch(() => null);
|
|
@@ -7856,7 +9619,7 @@ ${lines}` };
|
|
|
7856
9619
|
return { ok: true };
|
|
7857
9620
|
}
|
|
7858
9621
|
const full = join16(projectPath, rel);
|
|
7859
|
-
if (!
|
|
9622
|
+
if (!existsSync11(full)) return { ok: false, error: tr("fs.fileMissing") };
|
|
7860
9623
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
7861
9624
|
const dest = join16(dataDir, "trash", "discarded", stamp2, rel);
|
|
7862
9625
|
await mkdir7(join16(dest, ".."), { recursive: true });
|
|
@@ -7871,8 +9634,8 @@ ${lines}` };
|
|
|
7871
9634
|
|
|
7872
9635
|
// daemon/src/files/service.ts
|
|
7873
9636
|
init_i18n();
|
|
7874
|
-
import { readdir as readdir5, readFile as readFile17, stat as
|
|
7875
|
-
import { join as join17, normalize as normalize3, sep as
|
|
9637
|
+
import { readdir as readdir5, readFile as readFile17, stat as stat11 } from "node:fs/promises";
|
|
9638
|
+
import { join as join17, normalize as normalize3, sep as sep4, extname as extname3 } from "node:path";
|
|
7876
9639
|
import ignore from "ignore";
|
|
7877
9640
|
function decodeText(buf) {
|
|
7878
9641
|
try {
|
|
@@ -7937,7 +9700,7 @@ function insidePath2(projectPath, p) {
|
|
|
7937
9700
|
const rel = normalize3(p || ".").replace(/\\/g, "/");
|
|
7938
9701
|
const abs = normalize3(join17(projectPath, rel));
|
|
7939
9702
|
const proj = normalize3(projectPath);
|
|
7940
|
-
if (abs !== proj && !abs.startsWith(proj +
|
|
9703
|
+
if (abs !== proj && !abs.startsWith(proj + sep4)) return null;
|
|
7941
9704
|
return rel;
|
|
7942
9705
|
}
|
|
7943
9706
|
var FileService = class {
|
|
@@ -7946,7 +9709,7 @@ var FileService = class {
|
|
|
7946
9709
|
igCache = /* @__PURE__ */ new Map();
|
|
7947
9710
|
async igFor(projectPath) {
|
|
7948
9711
|
const gi = join17(projectPath, ".gitignore");
|
|
7949
|
-
const st = await
|
|
9712
|
+
const st = await stat11(gi).catch(() => null);
|
|
7950
9713
|
const mtime = st?.isFile() ? st.mtimeMs : -1;
|
|
7951
9714
|
const hit = this.igCache.get(projectPath);
|
|
7952
9715
|
if (hit && hit.mtime === mtime) return hit.ig;
|
|
@@ -7965,7 +9728,7 @@ var FileService = class {
|
|
|
7965
9728
|
const rel = insidePath2(projectPath, dir);
|
|
7966
9729
|
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7967
9730
|
const abs = join17(projectPath, rel);
|
|
7968
|
-
const s = await
|
|
9731
|
+
const s = await stat11(abs).catch(() => null);
|
|
7969
9732
|
if (!s?.isDirectory()) return { error: tr("fs.dirMissing") };
|
|
7970
9733
|
const ig = await this.igFor(projectPath);
|
|
7971
9734
|
const dirents = await readdir5(abs, { withFileTypes: true }).catch(() => null);
|
|
@@ -7978,7 +9741,7 @@ var FileService = class {
|
|
|
7978
9741
|
const childRel = rel === "." ? name : `${rel}/${name}`;
|
|
7979
9742
|
let isDir2 = d.isDirectory();
|
|
7980
9743
|
if (d.isSymbolicLink()) {
|
|
7981
|
-
const st = await
|
|
9744
|
+
const st = await stat11(join17(projectPath, childRel)).catch(() => null);
|
|
7982
9745
|
if (!st) continue;
|
|
7983
9746
|
isDir2 = st.isDirectory();
|
|
7984
9747
|
} else if (!d.isFile() && !isDir2) {
|
|
@@ -7996,7 +9759,7 @@ var FileService = class {
|
|
|
7996
9759
|
const rel = insidePath2(projectPath, p);
|
|
7997
9760
|
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7998
9761
|
const abs = join17(projectPath, rel);
|
|
7999
|
-
const s = await
|
|
9762
|
+
const s = await stat11(abs).catch(() => null);
|
|
8000
9763
|
if (!s) return { error: tr("fs.fileMissing") };
|
|
8001
9764
|
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
8002
9765
|
if (s.size > MAX_READ) return { tooLarge: true };
|
|
@@ -8013,19 +9776,19 @@ var FileService = class {
|
|
|
8013
9776
|
init_pathscan();
|
|
8014
9777
|
init_log();
|
|
8015
9778
|
init_i18n();
|
|
8016
|
-
|
|
8017
|
-
import {
|
|
9779
|
+
init_decode();
|
|
9780
|
+
import { spawn as spawn4, execFileSync as execFileSync2 } from "node:child_process";
|
|
9781
|
+
import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as existsSync12, statSync as statSync7 } from "node:fs";
|
|
8018
9782
|
import { createRequire as moduleRequire } from "node:module";
|
|
8019
9783
|
import { release } from "node:os";
|
|
8020
9784
|
import { dirname as dirname6, join as join18 } from "node:path";
|
|
8021
|
-
import { StringDecoder } from "node:string_decoder";
|
|
8022
9785
|
var MAX_TERMS_PER_PROJECT = 8;
|
|
8023
9786
|
var BUF_MAX = 2 * 1024 * 1024;
|
|
8024
9787
|
var SNAP_LINES = 5e3;
|
|
8025
9788
|
function isExecFile(file) {
|
|
8026
9789
|
try {
|
|
8027
9790
|
accessSync2(file, fsc.X_OK);
|
|
8028
|
-
return
|
|
9791
|
+
return statSync7(file).isFile();
|
|
8029
9792
|
} catch {
|
|
8030
9793
|
return false;
|
|
8031
9794
|
}
|
|
@@ -8064,7 +9827,7 @@ function fixSpawnHelper() {
|
|
|
8064
9827
|
const root = dirname6(req.resolve("node-pty/package.json"));
|
|
8065
9828
|
for (const sub of [`prebuilds/darwin-${process.arch}`, "build/Release"]) {
|
|
8066
9829
|
const f = join18(root, sub, "spawn-helper");
|
|
8067
|
-
if (
|
|
9830
|
+
if (existsSync12(f) && !isExecFile(f)) {
|
|
8068
9831
|
chmodSync(f, 493);
|
|
8069
9832
|
log("info", "\u5DF2\u4FEE\u590D node-pty spawn-helper \u7684\u53EF\u6267\u884C\u4F4D", { file: f });
|
|
8070
9833
|
}
|
|
@@ -8142,7 +9905,7 @@ var TerminalService = class {
|
|
|
8142
9905
|
if (!term.pty) {
|
|
8143
9906
|
term.simple = true;
|
|
8144
9907
|
const args = simpleShellArgs(shell);
|
|
8145
|
-
const proc =
|
|
9908
|
+
const proc = spawn4(shell, args, {
|
|
8146
9909
|
cwd,
|
|
8147
9910
|
stdio: "pipe",
|
|
8148
9911
|
windowsHide: true,
|
|
@@ -8150,8 +9913,8 @@ var TerminalService = class {
|
|
|
8150
9913
|
detached: process.platform !== "win32"
|
|
8151
9914
|
});
|
|
8152
9915
|
term.proc = proc;
|
|
8153
|
-
const outDec = new
|
|
8154
|
-
const errDec = new
|
|
9916
|
+
const outDec = new OutputDecoder();
|
|
9917
|
+
const errDec = new OutputDecoder();
|
|
8155
9918
|
proc.stdout.on("data", (d) => this.onData(term, outDec.write(d)));
|
|
8156
9919
|
proc.stderr.on("data", (d) => this.onData(term, errDec.write(d)));
|
|
8157
9920
|
proc.on("exit", () => this.onExit(term));
|
|
@@ -8210,7 +9973,7 @@ var TerminalService = class {
|
|
|
8210
9973
|
const args = ["/pid", String(pid), "/T", "/F"];
|
|
8211
9974
|
try {
|
|
8212
9975
|
if (sync) execFileSync2("taskkill", args, { windowsHide: true, stdio: "ignore", timeout: 5e3 });
|
|
8213
|
-
else
|
|
9976
|
+
else spawn4("taskkill", args, { windowsHide: true, stdio: "ignore" }).on("error", () => {
|
|
8214
9977
|
});
|
|
8215
9978
|
} catch {
|
|
8216
9979
|
}
|
|
@@ -8296,18 +10059,98 @@ function pidAlive(pid, kill = (p, s) => process.kill(p, s)) {
|
|
|
8296
10059
|
}
|
|
8297
10060
|
function augmentPath(env, nodeDir, platform = process.platform, home) {
|
|
8298
10061
|
const win = platform === "win32";
|
|
8299
|
-
const
|
|
10062
|
+
const sep5 = win ? ";" : platform === process.platform ? delimiter : ":";
|
|
8300
10063
|
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
8301
|
-
const cur = (env[key] ?? "").split(
|
|
8302
|
-
const
|
|
8303
|
-
const seen = new Set(cur.map(
|
|
10064
|
+
const cur = (env[key] ?? "").split(sep5).filter(Boolean);
|
|
10065
|
+
const norm2 = (d) => win ? d.replace(/[\\/]+$/, "").toLowerCase() : d.replace(/\/+$/, "");
|
|
10066
|
+
const seen = new Set(cur.map(norm2));
|
|
8304
10067
|
const out = [...cur];
|
|
8305
10068
|
for (const d of [nodeDir, ...extraBinDirs({ platform, env, home })]) {
|
|
8306
|
-
if (!d || seen.has(
|
|
8307
|
-
seen.add(
|
|
10069
|
+
if (!d || seen.has(norm2(d))) continue;
|
|
10070
|
+
seen.add(norm2(d));
|
|
8308
10071
|
out.push(d);
|
|
8309
10072
|
}
|
|
8310
|
-
return out.join(
|
|
10073
|
+
return out.join(sep5);
|
|
10074
|
+
}
|
|
10075
|
+
|
|
10076
|
+
// daemon/src/main.ts
|
|
10077
|
+
init_proxy();
|
|
10078
|
+
|
|
10079
|
+
// daemon/src/preview/ports.ts
|
|
10080
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
10081
|
+
import { homedir as homedir10 } from "node:os";
|
|
10082
|
+
import { basename as basename5, dirname as dirname7, isAbsolute as isAbsolute3, join as join19 } from "node:path";
|
|
10083
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10084
|
+
var MAGPIE_DEFAULT_PORTS = [3425, 3430];
|
|
10085
|
+
var GATE_DEFAULT_PORT = 3488;
|
|
10086
|
+
function portOf(addr) {
|
|
10087
|
+
if (!addr) return null;
|
|
10088
|
+
const m = /:(\d{1,5})\/?$/.exec(addr.trim());
|
|
10089
|
+
const n = m ? Number(m[1]) : NaN;
|
|
10090
|
+
return Number.isInteger(n) && n > 0 && n <= 65535 ? n : null;
|
|
10091
|
+
}
|
|
10092
|
+
function magpieDirFromInstall(p) {
|
|
10093
|
+
const norm2 = p.replace(/\\/g, "/");
|
|
10094
|
+
const i = norm2.lastIndexOf("/plugins/node_modules/");
|
|
10095
|
+
if (i <= 0) return null;
|
|
10096
|
+
return p.slice(0, i);
|
|
10097
|
+
}
|
|
10098
|
+
function magpieConfigDirs(env = process.env, where = {}) {
|
|
10099
|
+
const out = [];
|
|
10100
|
+
const add = (d) => {
|
|
10101
|
+
if (d && isAbsolute3(d) && !out.includes(d)) out.push(d);
|
|
10102
|
+
};
|
|
10103
|
+
let self = where.self;
|
|
10104
|
+
if (self === void 0) {
|
|
10105
|
+
try {
|
|
10106
|
+
self = fileURLToPath3(import.meta.url);
|
|
10107
|
+
} catch {
|
|
10108
|
+
self = "";
|
|
10109
|
+
}
|
|
10110
|
+
}
|
|
10111
|
+
add(magpieDirFromInstall(self));
|
|
10112
|
+
const cwd = where.cwd ?? (env === process.env ? process.cwd() : "");
|
|
10113
|
+
if (cwd && basename5(cwd) === "plugin-work") add(dirname7(cwd));
|
|
10114
|
+
const x = env.XDG_CONFIG_HOME;
|
|
10115
|
+
if (x && isAbsolute3(x)) add(join19(x, "magpie"));
|
|
10116
|
+
const platform = where.platform ?? process.platform;
|
|
10117
|
+
const home = where.home ?? (env === process.env ? homedir10() : platform === "win32" ? env.USERPROFILE : env.HOME);
|
|
10118
|
+
if (home) add(join19(home, ".config", "magpie"));
|
|
10119
|
+
return out;
|
|
10120
|
+
}
|
|
10121
|
+
function savedGatewayPort(file, read = (f) => readFileSync2(f, "utf8")) {
|
|
10122
|
+
let raw;
|
|
10123
|
+
try {
|
|
10124
|
+
raw = read(file);
|
|
10125
|
+
} catch {
|
|
10126
|
+
return null;
|
|
10127
|
+
}
|
|
10128
|
+
try {
|
|
10129
|
+
const s = JSON.parse(raw);
|
|
10130
|
+
const p = typeof s?.port === "number" ? s.port : 0;
|
|
10131
|
+
return Number.isInteger(p) && p >= 1024 && p <= 65535 ? p : MAGPIE_DEFAULT_PORTS[0];
|
|
10132
|
+
} catch {
|
|
10133
|
+
return null;
|
|
10134
|
+
}
|
|
10135
|
+
}
|
|
10136
|
+
function previewDeniedPorts(cfg2, env = process.env, opts = {}) {
|
|
10137
|
+
const out = /* @__PURE__ */ new Set([cfg2.port, GATE_DEFAULT_PORT, ...MAGPIE_DEFAULT_PORTS]);
|
|
10138
|
+
if (cfg2.gatePort) out.add(cfg2.gatePort);
|
|
10139
|
+
for (const k of ["MAGPIE_ADDR", "MAGPIE_WEB_ADDR", "MAGPIE_URL"]) {
|
|
10140
|
+
const p = portOf(env[k]);
|
|
10141
|
+
if (p) out.add(p);
|
|
10142
|
+
}
|
|
10143
|
+
for (const d of opts.configDirs ?? magpieConfigDirs(env)) {
|
|
10144
|
+
const p = savedGatewayPort(join19(d, "settings.json"), opts.read);
|
|
10145
|
+
if (p) out.add(p);
|
|
10146
|
+
}
|
|
10147
|
+
const extra = env.AGENT_CONSOLE_PREVIEW_DENY;
|
|
10148
|
+
if (extra) for (const s of extra.split(/[,\s]+/)) {
|
|
10149
|
+
const n = Number(s);
|
|
10150
|
+
if (Number.isInteger(n) && n > 0 && n <= 65535) out.add(n);
|
|
10151
|
+
}
|
|
10152
|
+
out.delete(0);
|
|
10153
|
+
return [...out];
|
|
8311
10154
|
}
|
|
8312
10155
|
|
|
8313
10156
|
// daemon/src/main.ts
|
|
@@ -8316,7 +10159,7 @@ if (nodeMajor < 20) {
|
|
|
8316
10159
|
console.error(`[agent-console] \u9700\u8981 Node.js 20 \u6216\u66F4\u9AD8\u7248\u672C\uFF0C\u5F53\u524D ${process.version}\uFF08${process.execPath}\uFF09`);
|
|
8317
10160
|
process.exit(3);
|
|
8318
10161
|
}
|
|
8319
|
-
process.env.PATH = augmentPath(process.env,
|
|
10162
|
+
process.env.PATH = augmentPath(process.env, dirname8(process.execPath));
|
|
8320
10163
|
var cfg = parseConfig(process.argv.slice(2));
|
|
8321
10164
|
process.on("uncaughtException", (e) => {
|
|
8322
10165
|
if (e?.code === "EPIPE") return;
|
|
@@ -8328,10 +10171,10 @@ process.on("unhandledRejection", (e) => {
|
|
|
8328
10171
|
async function main() {
|
|
8329
10172
|
initLog(cfg.dataDir);
|
|
8330
10173
|
for (const d of ["threads", "attachments", "trash", "logs"]) {
|
|
8331
|
-
mkdirSync2(
|
|
10174
|
+
mkdirSync2(join21(cfg.dataDir, d), { recursive: true });
|
|
8332
10175
|
}
|
|
8333
|
-
const appDir =
|
|
8334
|
-
const attachments = new AttachmentStore(
|
|
10176
|
+
const appDir = join21(dirname8(fileURLToPath4(import.meta.url)), "..", "..", "app");
|
|
10177
|
+
const attachments = new AttachmentStore(join21(cfg.dataDir, "attachments"));
|
|
8335
10178
|
const { bindAttachmentStore: bindAttachmentStore2 } = await Promise.resolve().then(() => (init_attachmentRefs(), attachmentRefs_exports));
|
|
8336
10179
|
bindAttachmentStore2(attachments);
|
|
8337
10180
|
const settings = new SettingsStore(cfg.dataDir);
|
|
@@ -8345,9 +10188,22 @@ async function main() {
|
|
|
8345
10188
|
} catch (e) {
|
|
8346
10189
|
log("error", "\u65E7\u6570\u636E\u8FC1\u79FB\u5931\u8D25", { err: String(e) });
|
|
8347
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
|
+
}
|
|
8348
10197
|
const manager = new AgentManager(gateway, threads, projects, settings, attachments);
|
|
8349
10198
|
await manager.recoverOnStart();
|
|
8350
10199
|
const terminals = new TerminalService(gateway);
|
|
10200
|
+
const preview = new PreviewProxy({
|
|
10201
|
+
deniedPorts: () => previewDeniedPorts(cfg),
|
|
10202
|
+
log: (level, msg) => log(level, msg)
|
|
10203
|
+
});
|
|
10204
|
+
gateway.onConnectionClosed = (left) => {
|
|
10205
|
+
if (left === 0) preview.closeAll();
|
|
10206
|
+
};
|
|
8351
10207
|
registerMethods(gateway, {
|
|
8352
10208
|
cfg,
|
|
8353
10209
|
settings,
|
|
@@ -8358,6 +10214,7 @@ async function main() {
|
|
|
8358
10214
|
git: new GitService(),
|
|
8359
10215
|
files: new FileService(),
|
|
8360
10216
|
terminals,
|
|
10217
|
+
preview,
|
|
8361
10218
|
runtime: (id2) => manager.runtimeInfo(id2)
|
|
8362
10219
|
});
|
|
8363
10220
|
const server = createHttpServer(cfg, appDir, attachments, {
|
|
@@ -8386,11 +10243,12 @@ async function main() {
|
|
|
8386
10243
|
const bound = server.address();
|
|
8387
10244
|
if (bound && typeof bound === "object") cfg.port = bound.port;
|
|
8388
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 : ""));
|
|
8389
10247
|
void (async () => {
|
|
8390
10248
|
const { warmProviderCache: warmProviderCache2 } = await Promise.resolve().then(() => (init_warm(), warm_exports));
|
|
8391
10249
|
await warmProviderCache2(await settings.get());
|
|
8392
10250
|
})().catch((e) => log("warn", "Provider \u9884\u70ED\u5931\u8D25", { err: String(e) }));
|
|
8393
|
-
const daemonJsonPath =
|
|
10251
|
+
const daemonJsonPath = join21(cfg.dataDir, "daemon.json");
|
|
8394
10252
|
await writeJsonAtomic(daemonJsonPath, {
|
|
8395
10253
|
pid: process.pid,
|
|
8396
10254
|
port: cfg.port,
|
|
@@ -8419,6 +10277,7 @@ async function main() {
|
|
|
8419
10277
|
shuttingDown = true;
|
|
8420
10278
|
log("info", "daemon \u5173\u95ED\u4E2D");
|
|
8421
10279
|
terminals.killAll();
|
|
10280
|
+
preview.closeAll();
|
|
8422
10281
|
gateway.closeAll();
|
|
8423
10282
|
try {
|
|
8424
10283
|
rmSync2(daemonJsonPath, { force: true });
|