magpie-agent-console 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/app/app.css +1 -1
- package/app/app.js +90 -89
- package/daemon/dist/main.js +1224 -150
- package/daemon/dist/main.js.map +1 -1
- package/index.mjs +4 -0
- package/package.json +3 -2
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();
|
|
@@ -129,6 +154,7 @@ var init_i18n = __esm({
|
|
|
129
154
|
"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
155
|
"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
156
|
"notice.attachmentIssue": "\u9644\u4EF6\u95EE\u9898\uFF1A{detail}",
|
|
157
|
+
"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
158
|
"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
159
|
"claude.startFailed": "\u65E0\u6CD5\u542F\u52A8 claude\uFF1A{detail}",
|
|
134
160
|
"claude.exited": "claude \u8FDB\u7A0B\u610F\u5916\u9000\u51FA\uFF08{code}\uFF09",
|
|
@@ -146,6 +172,9 @@ var init_i18n = __esm({
|
|
|
146
172
|
"provider.unavailable": "{name} \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u7528",
|
|
147
173
|
"provider.unknown": "\u672A\u77E5 Agent\uFF1A{id}",
|
|
148
174
|
"provider.timeout": "\u68C0\u6D4B\u8D85\u65F6",
|
|
175
|
+
"err.previewUrl": "\u9884\u89C8\u53EA\u652F\u6301\u672C\u673A\u5730\u5740\uFF08localhost\u3001127.0.0.1 \u7B49\uFF09",
|
|
176
|
+
"err.previewDenied": "\u8FD9\u4E2A\u7AEF\u53E3\u4E0D\u80FD\u9884\u89C8\uFF08\u662F Agent \u63A7\u5236\u53F0\u6216 Magpie \u81EA\u5DF1\u7684\u7AEF\u53E3\uFF09",
|
|
177
|
+
"err.previewUnavailable": "\u9884\u89C8\u4E0D\u53EF\u7528",
|
|
149
178
|
"err.projectMissing": "\u9879\u76EE\u4E0D\u5B58\u5728",
|
|
150
179
|
"err.threadMissing": "\u5BF9\u8BDD\u4E0D\u5B58\u5728",
|
|
151
180
|
"err.dirMissing": "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939",
|
|
@@ -235,6 +264,7 @@ var init_i18n = __esm({
|
|
|
235
264
|
"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
265
|
"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
266
|
"notice.attachmentIssue": "Attachment issue: {detail}",
|
|
267
|
+
"notice.mentionSkipped": "Not sent to the Agent (outside the project or gone): {paths}",
|
|
238
268
|
"claude.notFound": "claude was not found ({detail}). Claude Code is not bundled with this plugin; install it first: {hint}",
|
|
239
269
|
"claude.startFailed": "Could not start claude: {detail}",
|
|
240
270
|
"claude.exited": "claude exited unexpectedly ({code})",
|
|
@@ -252,6 +282,9 @@ var init_i18n = __esm({
|
|
|
252
282
|
"provider.unavailable": "{name} is not installed or not available",
|
|
253
283
|
"provider.unknown": "Unknown agent: {id}",
|
|
254
284
|
"provider.timeout": "Detection timed out",
|
|
285
|
+
"err.previewUrl": "Preview only supports local addresses (localhost, 127.0.0.1, \u2026)",
|
|
286
|
+
"err.previewDenied": "This port can't be previewed (it belongs to Agent Console or Magpie)",
|
|
287
|
+
"err.previewUnavailable": "Preview is unavailable",
|
|
255
288
|
"err.projectMissing": "Project not found",
|
|
256
289
|
"err.threadMissing": "Conversation not found",
|
|
257
290
|
"err.dirMissing": "The folder does not exist or is not a folder",
|
|
@@ -774,6 +807,7 @@ function pickScript(text) {
|
|
|
774
807
|
if (t.startsWith("/error")) return scriptError;
|
|
775
808
|
if (t.startsWith("/long")) return scriptLong;
|
|
776
809
|
if (t.startsWith("/exit")) return scriptExit;
|
|
810
|
+
if (t.startsWith("/serve")) return scriptServe;
|
|
777
811
|
return scriptDefault;
|
|
778
812
|
}
|
|
779
813
|
async function sleepAbortable(ms, ctx) {
|
|
@@ -789,6 +823,25 @@ function chunkText(s, n) {
|
|
|
789
823
|
for (let i = 0; i < s.length; i += n) out.push(s.slice(i, i + n));
|
|
790
824
|
return out;
|
|
791
825
|
}
|
|
826
|
+
async function scriptServe(ctx) {
|
|
827
|
+
const emit = (e) => ctx.emit(e);
|
|
828
|
+
const reply = [
|
|
829
|
+
"\u5F00\u53D1\u670D\u52A1\u5668\u5DF2\u7ECF\u542F\u52A8\uFF1Ahttp://localhost:5173/ \uFF0C\u63A5\u53E3\u5728 127.0.0.1:5173/api.json\u3002",
|
|
830
|
+
"",
|
|
831
|
+
"- \u5728\u9884\u89C8\u91CC\u6253\u5F00 [\u9996\u9875](http://localhost:5173/) \u6216 [about](http://localhost:5173/about.html)",
|
|
832
|
+
"- \u6587\u6863\uFF1A[Vite](https://vite.dev/guide/)",
|
|
833
|
+
"",
|
|
834
|
+
"```",
|
|
835
|
+
" VITE v5.4.21 ready in 139 ms",
|
|
836
|
+
" \u279C Local: http://localhost:5173/",
|
|
837
|
+
"```"
|
|
838
|
+
].join("\n");
|
|
839
|
+
for (const ch of chunkText(reply, 40)) {
|
|
840
|
+
if (await sleepAbortable(20, ctx)) return;
|
|
841
|
+
emit({ type: "assistant_delta", itemId: "sv", text: ch });
|
|
842
|
+
}
|
|
843
|
+
emit({ type: "assistant_message", itemId: "sv", text: reply });
|
|
844
|
+
}
|
|
792
845
|
function mockReply(ctx) {
|
|
793
846
|
const base = [
|
|
794
847
|
"\u597D\u7684\uFF0C\u6211\u5DF2\u7ECF\u6309\u4F60\u7684\u8981\u6C42\u6539\u597D\u4E86\u3002",
|
|
@@ -931,21 +984,21 @@ var init_mock = __esm({
|
|
|
931
984
|
}
|
|
932
985
|
}
|
|
933
986
|
respond(requestId, answer) {
|
|
934
|
-
const
|
|
935
|
-
if (
|
|
987
|
+
const resolve4 = this.pendingReq.get(requestId);
|
|
988
|
+
if (resolve4) {
|
|
936
989
|
this.pendingReq.delete(requestId);
|
|
937
|
-
|
|
990
|
+
resolve4(answer);
|
|
938
991
|
}
|
|
939
992
|
}
|
|
940
993
|
waitFor(requestId) {
|
|
941
|
-
return new Promise((
|
|
942
|
-
this.pendingReq.set(requestId,
|
|
994
|
+
return new Promise((resolve4) => {
|
|
995
|
+
this.pendingReq.set(requestId, resolve4);
|
|
943
996
|
});
|
|
944
997
|
}
|
|
945
998
|
async interrupt() {
|
|
946
999
|
this.interruptFlag = true;
|
|
947
|
-
for (const [id2,
|
|
948
|
-
|
|
1000
|
+
for (const [id2, resolve4] of this.pendingReq) {
|
|
1001
|
+
resolve4({ cancelled: true });
|
|
949
1002
|
this.pendingReq.delete(id2);
|
|
950
1003
|
}
|
|
951
1004
|
await sleep(120);
|
|
@@ -973,7 +1026,7 @@ var init_mock = __esm({
|
|
|
973
1026
|
}
|
|
974
1027
|
async close() {
|
|
975
1028
|
this.closed = true;
|
|
976
|
-
for (const [,
|
|
1029
|
+
for (const [, resolve4] of this.pendingReq) resolve4({ cancelled: true });
|
|
977
1030
|
this.pendingReq.clear();
|
|
978
1031
|
this.q.close();
|
|
979
1032
|
}
|
|
@@ -1037,8 +1090,8 @@ function extraBinDirs({ platform = process.platform, env = process.env, home = h
|
|
|
1037
1090
|
return dirs;
|
|
1038
1091
|
}
|
|
1039
1092
|
function searchDirs({ platform = process.platform, env = process.env, home = homedir2(), extra = true } = {}) {
|
|
1040
|
-
const
|
|
1041
|
-
const raw = String(envGet(env, "PATH") ?? "").split(
|
|
1093
|
+
const sep5 = platform === "win32" ? ";" : ":";
|
|
1094
|
+
const raw = String(envGet(env, "PATH") ?? "").split(sep5).map((d) => d.trim().replace(/^"(.*)"$/, "$1")).filter(Boolean);
|
|
1042
1095
|
const all = extra ? [...raw, ...extraBinDirs({ platform, env, home })] : raw;
|
|
1043
1096
|
const seen = /* @__PURE__ */ new Set();
|
|
1044
1097
|
const out = [];
|
|
@@ -1306,6 +1359,8 @@ var init_bin = __esm({
|
|
|
1306
1359
|
});
|
|
1307
1360
|
|
|
1308
1361
|
// daemon/src/agents/kinds.ts
|
|
1362
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1363
|
+
import { isAbsolute, resolve as resolve2 } from "node:path";
|
|
1309
1364
|
function relPath(p, cwd) {
|
|
1310
1365
|
if (!p) return "";
|
|
1311
1366
|
const s = String(p);
|
|
@@ -1317,6 +1372,15 @@ function relPath(p, cwd) {
|
|
|
1317
1372
|
if (a.toLowerCase() === b.toLowerCase()) return ".";
|
|
1318
1373
|
return s;
|
|
1319
1374
|
}
|
|
1375
|
+
function targetExists(input, cwd = "") {
|
|
1376
|
+
const p = String(input.file_path ?? input.path ?? "");
|
|
1377
|
+
if (!p) return false;
|
|
1378
|
+
try {
|
|
1379
|
+
return existsSync5(isAbsolute(p) || !cwd ? p : resolve2(cwd, p));
|
|
1380
|
+
} catch {
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1320
1384
|
function callInfo(name, input = {}, cwd = "") {
|
|
1321
1385
|
const t = String(name || "");
|
|
1322
1386
|
const f = (kind, title, detail) => ({ kind, title, detail });
|
|
@@ -1521,7 +1585,7 @@ function mapToolUse(blk, cwd) {
|
|
|
1521
1585
|
const input = rec(blk.input);
|
|
1522
1586
|
const name = str(blk.name) ?? "tool";
|
|
1523
1587
|
const id2 = str(blk.id) ?? "";
|
|
1524
|
-
const info = callInfo(name, { ...input, _exists: true }, cwd);
|
|
1588
|
+
const info = callInfo(name, { ...input, _exists: name === "Write" ? targetExists(input, cwd) : true }, cwd);
|
|
1525
1589
|
let detail = info.detail;
|
|
1526
1590
|
if (info.kind === "edit" && (input.old_string !== void 0 || Array.isArray(input.edits))) {
|
|
1527
1591
|
detail = editDetail({ path: info.detail.path, old: input.old_string, new: input.new_string, edits: input.edits });
|
|
@@ -1573,7 +1637,7 @@ __export(attachmentRefs_exports, {
|
|
|
1573
1637
|
resolveAttachmentRefs: () => resolveAttachmentRefs,
|
|
1574
1638
|
resolveImagePath: () => resolveImagePath
|
|
1575
1639
|
});
|
|
1576
|
-
import { existsSync as
|
|
1640
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
1577
1641
|
function bindAttachmentStore(store) {
|
|
1578
1642
|
globalAttachmentStore = store;
|
|
1579
1643
|
}
|
|
@@ -1596,7 +1660,7 @@ async function resolveAttachmentRefs(store, refs) {
|
|
|
1596
1660
|
out.push({ path: viaGlobal, mime: a.mime });
|
|
1597
1661
|
continue;
|
|
1598
1662
|
}
|
|
1599
|
-
if (
|
|
1663
|
+
if (existsSync6(a.path)) {
|
|
1600
1664
|
out.push(a);
|
|
1601
1665
|
continue;
|
|
1602
1666
|
}
|
|
@@ -1605,7 +1669,7 @@ async function resolveAttachmentRefs(store, refs) {
|
|
|
1605
1669
|
return out;
|
|
1606
1670
|
}
|
|
1607
1671
|
async function resolveImagePath(ref) {
|
|
1608
|
-
if (
|
|
1672
|
+
if (existsSync6(ref)) return ref;
|
|
1609
1673
|
const p = await lookupAttachmentPath(ref);
|
|
1610
1674
|
if (p) return p;
|
|
1611
1675
|
throw new Error(tr("attachment.missing", { ref }));
|
|
@@ -1768,15 +1832,15 @@ var init_cli = __esm({
|
|
|
1768
1832
|
});
|
|
1769
1833
|
this.child.stdin?.on("error", () => {
|
|
1770
1834
|
});
|
|
1771
|
-
this.exited = new Promise((
|
|
1835
|
+
this.exited = new Promise((resolve4) => {
|
|
1772
1836
|
this.child.on("error", (e) => {
|
|
1773
1837
|
this.spawnError = e;
|
|
1774
1838
|
this.finish();
|
|
1775
|
-
|
|
1839
|
+
resolve4({ code: null, signal: null });
|
|
1776
1840
|
});
|
|
1777
1841
|
this.child.on("close", (code, signal) => {
|
|
1778
1842
|
this.finish();
|
|
1779
|
-
|
|
1843
|
+
resolve4({ code, signal });
|
|
1780
1844
|
});
|
|
1781
1845
|
});
|
|
1782
1846
|
this.initialized = this.request({ subtype: "initialize" }, 6e4).catch(() => null);
|
|
@@ -1893,8 +1957,8 @@ var init_cli = __esm({
|
|
|
1893
1957
|
request(request, timeoutMs = 1e4) {
|
|
1894
1958
|
if (this.dead) return Promise.reject(new Error("claude is not running"));
|
|
1895
1959
|
const id2 = `ac_${++this.seq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1896
|
-
return new Promise((
|
|
1897
|
-
const p = { resolve:
|
|
1960
|
+
return new Promise((resolve4, reject) => {
|
|
1961
|
+
const p = { resolve: resolve4, reject };
|
|
1898
1962
|
p.timer = setTimeout(() => {
|
|
1899
1963
|
if (this.pending.delete(id2)) reject(new Error(`claude did not answer ${String(request.subtype)} in ${Math.round(timeoutMs / 1e3)} s`));
|
|
1900
1964
|
}, timeoutMs);
|
|
@@ -2066,7 +2130,7 @@ var init_session = __esm({
|
|
|
2066
2130
|
if (toolName === "AskUserQuestion" && Array.isArray(input.questions)) {
|
|
2067
2131
|
return this.handleQuestion(requestId, input, signal);
|
|
2068
2132
|
}
|
|
2069
|
-
const info = callInfo(toolName, { ...input, _exists: true }, this.o.cwd);
|
|
2133
|
+
const info = callInfo(toolName, { ...input, _exists: toolName === "Write" ? targetExists(input, this.o.cwd) : true }, this.o.cwd);
|
|
2070
2134
|
let detail = { ...info.detail, toolName };
|
|
2071
2135
|
if (info.kind === "edit" && (input.old_string !== void 0 || Array.isArray(input.edits))) {
|
|
2072
2136
|
detail = { ...editDetail({ path: info.detail.path, old: input.old_string, new: input.new_string, edits: input.edits }), toolName };
|
|
@@ -2083,12 +2147,12 @@ var init_session = __esm({
|
|
|
2083
2147
|
{ id: "deny", label: tr("perm.deny"), kind: "reject_once" }
|
|
2084
2148
|
]
|
|
2085
2149
|
});
|
|
2086
|
-
return new Promise((
|
|
2087
|
-
this.pending.set(requestId, { kind: "permission", resolve: (a) =>
|
|
2150
|
+
return new Promise((resolve4) => {
|
|
2151
|
+
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve4(this.toPermissionDecision(a, input)) });
|
|
2088
2152
|
signal.addEventListener("abort", () => {
|
|
2089
2153
|
if (this.pending.delete(requestId)) {
|
|
2090
2154
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2091
|
-
|
|
2155
|
+
resolve4({ behavior: "deny", message: "Cancelled by the user", interrupt: true });
|
|
2092
2156
|
}
|
|
2093
2157
|
}, { once: true });
|
|
2094
2158
|
});
|
|
@@ -2115,12 +2179,12 @@ var init_session = __esm({
|
|
|
2115
2179
|
{ id: "reject", label: tr("perm.deny"), kind: "reject_once" }
|
|
2116
2180
|
]
|
|
2117
2181
|
});
|
|
2118
|
-
const answer = await new Promise((
|
|
2119
|
-
this.pending.set(requestId, { kind: "permission", resolve: (a) =>
|
|
2182
|
+
const answer = await new Promise((resolve4) => {
|
|
2183
|
+
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve4(a) });
|
|
2120
2184
|
signal.addEventListener("abort", () => {
|
|
2121
2185
|
if (this.pending.delete(requestId)) {
|
|
2122
2186
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2123
|
-
|
|
2187
|
+
resolve4({ cancelled: true });
|
|
2124
2188
|
}
|
|
2125
2189
|
}, { once: true });
|
|
2126
2190
|
});
|
|
@@ -2138,12 +2202,12 @@ var init_session = __esm({
|
|
|
2138
2202
|
}
|
|
2139
2203
|
async handleQuestion(requestId, input, signal) {
|
|
2140
2204
|
this.emit({ type: "question", requestId, questions: input.questions });
|
|
2141
|
-
const answer = await new Promise((
|
|
2142
|
-
this.pending.set(requestId, { kind: "question", resolve: (a) =>
|
|
2205
|
+
const answer = await new Promise((resolve4) => {
|
|
2206
|
+
this.pending.set(requestId, { kind: "question", resolve: (a) => resolve4(a) });
|
|
2143
2207
|
signal.addEventListener("abort", () => {
|
|
2144
2208
|
if (this.pending.delete(requestId)) {
|
|
2145
2209
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
2146
|
-
|
|
2210
|
+
resolve4({ cancelled: true });
|
|
2147
2211
|
}
|
|
2148
2212
|
}, { once: true });
|
|
2149
2213
|
});
|
|
@@ -2265,7 +2329,7 @@ var init_models = __esm({
|
|
|
2265
2329
|
|
|
2266
2330
|
// daemon/src/history/import.ts
|
|
2267
2331
|
import { readdir, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
2268
|
-
import { existsSync as
|
|
2332
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2269
2333
|
import { join as join6 } from "node:path";
|
|
2270
2334
|
import { homedir as homedir3 } from "node:os";
|
|
2271
2335
|
function parseLine(line) {
|
|
@@ -2332,7 +2396,7 @@ async function readClaudeHistory(sessionId, cwd) {
|
|
|
2332
2396
|
const dirs = await readdir(CLAUDE_ROOT).catch(() => []);
|
|
2333
2397
|
for (const d of dirs) {
|
|
2334
2398
|
const path3 = join6(CLAUDE_ROOT, d, `${sessionId}.jsonl`);
|
|
2335
|
-
if (!
|
|
2399
|
+
if (!existsSync7(path3)) continue;
|
|
2336
2400
|
const msgs = [];
|
|
2337
2401
|
let matched = false;
|
|
2338
2402
|
for await (const line of linesOf(path3)) {
|
|
@@ -2387,14 +2451,14 @@ function runVersion(r, args, timeoutMs = 5e3) {
|
|
|
2387
2451
|
});
|
|
2388
2452
|
}
|
|
2389
2453
|
function createClaudeProvider(opts = {}) {
|
|
2390
|
-
const
|
|
2454
|
+
const resolve4 = opts.resolve ?? resolveBin;
|
|
2391
2455
|
const resolveCustom = opts.resolve ? (l) => opts.resolve(l.cmd) : resolveLaunch;
|
|
2392
2456
|
return {
|
|
2393
2457
|
id: "claude",
|
|
2394
2458
|
name: "Claude Code",
|
|
2395
2459
|
capabilities: claudeCapabilities,
|
|
2396
2460
|
async detect(launch) {
|
|
2397
|
-
const bin = launch ? await resolveCustom(launch) : await
|
|
2461
|
+
const bin = launch ? await resolveCustom(launch) : await resolve4("claude");
|
|
2398
2462
|
if (!bin.ok) return { available: false, reason: bin.reason, installHint: INSTALL_HINT };
|
|
2399
2463
|
try {
|
|
2400
2464
|
const version = await runVersion(bin, [...launch?.prefixArgs ?? [], "--version"]);
|
|
@@ -2407,7 +2471,7 @@ function createClaudeProvider(opts = {}) {
|
|
|
2407
2471
|
return listClaudeModels();
|
|
2408
2472
|
},
|
|
2409
2473
|
async createSession(o) {
|
|
2410
|
-
const bin = o.launch ? await resolveCustom(o.launch) : await
|
|
2474
|
+
const bin = o.launch ? await resolveCustom(o.launch) : await resolve4("claude");
|
|
2411
2475
|
if (!bin.ok) throw new Error(tr("claude.notFound", { detail: bin.reason, hint: INSTALL_HINT }));
|
|
2412
2476
|
return new ClaudeSession({
|
|
2413
2477
|
threadId: o.threadId,
|
|
@@ -3423,8 +3487,8 @@ var init_session2 = __esm({
|
|
|
3423
3487
|
}
|
|
3424
3488
|
}
|
|
3425
3489
|
waitFor(requestId) {
|
|
3426
|
-
return new Promise((
|
|
3427
|
-
this.pending.set(requestId, { kind: "permission", resolve:
|
|
3490
|
+
return new Promise((resolve4) => {
|
|
3491
|
+
this.pending.set(requestId, { kind: "permission", resolve: resolve4 });
|
|
3428
3492
|
});
|
|
3429
3493
|
}
|
|
3430
3494
|
respond(requestId, answer) {
|
|
@@ -3943,7 +4007,7 @@ var init_sessionCaps = __esm({
|
|
|
3943
4007
|
import { readFile as readFile10, stat as stat7 } from "node:fs/promises";
|
|
3944
4008
|
import { basename as basename4 } from "node:path";
|
|
3945
4009
|
import { pathToFileURL } from "node:url";
|
|
3946
|
-
async function buildAcpPrompt(text, attachments, caps) {
|
|
4010
|
+
async function buildAcpPrompt(text, attachments, caps, mentions = []) {
|
|
3947
4011
|
const media = [];
|
|
3948
4012
|
const links = [];
|
|
3949
4013
|
const pathLines = [];
|
|
@@ -4006,6 +4070,9 @@ async function buildAcpPrompt(text, attachments, caps) {
|
|
|
4006
4070
|
const blocks = [];
|
|
4007
4071
|
if (fullText) blocks.push({ type: "text", text: fullText });
|
|
4008
4072
|
blocks.push(...media, ...links);
|
|
4073
|
+
for (const m of mentions) {
|
|
4074
|
+
blocks.push({ type: "resource_link", uri: pathToFileURL(m.path).href, name: basename4(m.path) || m.path });
|
|
4075
|
+
}
|
|
4009
4076
|
return { blocks, imagesByPath, warnings };
|
|
4010
4077
|
}
|
|
4011
4078
|
var MAX_INLINE_MEDIA_BYTES, MAX_INLINE_TOTAL_BYTES, MAX_EMBED_TEXT_BYTES, isText;
|
|
@@ -4309,8 +4376,8 @@ var init_session3 = __esm({
|
|
|
4309
4376
|
await this.applyCursorAutoReviewConfig(false);
|
|
4310
4377
|
}
|
|
4311
4378
|
waitPerm(requestId) {
|
|
4312
|
-
return new Promise((
|
|
4313
|
-
this.permPending.set(requestId,
|
|
4379
|
+
return new Promise((resolve4) => {
|
|
4380
|
+
this.permPending.set(requestId, resolve4);
|
|
4314
4381
|
});
|
|
4315
4382
|
}
|
|
4316
4383
|
respond(requestId, answer) {
|
|
@@ -4347,7 +4414,7 @@ var init_session3 = __esm({
|
|
|
4347
4414
|
this.q.push({ type: "turn_ended", turnId: this.turnId, status: "completed" });
|
|
4348
4415
|
return;
|
|
4349
4416
|
}
|
|
4350
|
-
const built = await buildAcpPrompt(text, attachments, this.promptCaps);
|
|
4417
|
+
const built = await buildAcpPrompt(text, attachments, this.promptCaps, input.mentions ?? []);
|
|
4351
4418
|
const agent = this.o.providerName ?? this.o.binName;
|
|
4352
4419
|
if (built.imagesByPath.length && !this.promptCaps.image) {
|
|
4353
4420
|
const switched = this.o.imagesFollowStartModel && this.initCaps.image && this.o.model !== this.startModel;
|
|
@@ -4966,13 +5033,13 @@ var init_pickFolderWin = __esm({
|
|
|
4966
5033
|
|
|
4967
5034
|
// daemon/src/util/pickFolder.ts
|
|
4968
5035
|
import { execFile as execFile2 } from "node:child_process";
|
|
4969
|
-
import { existsSync as
|
|
5036
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "node:fs";
|
|
4970
5037
|
function appleScriptString(s) {
|
|
4971
5038
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
4972
5039
|
}
|
|
4973
5040
|
function isDir(p) {
|
|
4974
5041
|
try {
|
|
4975
|
-
return !!p &&
|
|
5042
|
+
return !!p && existsSync8(p) && statSync3(p).isDirectory();
|
|
4976
5043
|
} catch {
|
|
4977
5044
|
return false;
|
|
4978
5045
|
}
|
|
@@ -5222,13 +5289,869 @@ var init_modes = __esm({
|
|
|
5222
5289
|
}
|
|
5223
5290
|
});
|
|
5224
5291
|
|
|
5292
|
+
// daemon/src/util/mentions.ts
|
|
5293
|
+
import { realpath, stat as stat8 } from "node:fs/promises";
|
|
5294
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep2, win32 as win322, posix as posix2 } from "node:path";
|
|
5295
|
+
function insideRoot(root, child, platform = process.platform) {
|
|
5296
|
+
const p = platform === "win32" ? win322 : posix2;
|
|
5297
|
+
const norm = (s) => platform === "win32" ? p.resolve(s).toLowerCase() : p.resolve(s);
|
|
5298
|
+
const r = p.relative(norm(root), norm(child));
|
|
5299
|
+
return r === "" || !r.startsWith("..") && !p.isAbsolute(r);
|
|
5300
|
+
}
|
|
5301
|
+
async function resolveMentions(projectPath, refs) {
|
|
5302
|
+
const mentions = [];
|
|
5303
|
+
const warnings = [];
|
|
5304
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5305
|
+
const shown = resolve3(projectPath);
|
|
5306
|
+
let realRoot;
|
|
5307
|
+
try {
|
|
5308
|
+
realRoot = await realpath(shown);
|
|
5309
|
+
} catch {
|
|
5310
|
+
return { mentions, warnings: refs.map((r) => r.rel) };
|
|
5311
|
+
}
|
|
5312
|
+
for (const ref of refs.slice(0, MAX_MENTIONS)) {
|
|
5313
|
+
const rel = String(ref.rel ?? "").replace(/\\/g, "/");
|
|
5314
|
+
if (!rel || rel.includes("\0") || isAbsolute2(rel) || /^[a-zA-Z]:/.test(rel)) {
|
|
5315
|
+
warnings.push(rel || "(empty)");
|
|
5316
|
+
continue;
|
|
5317
|
+
}
|
|
5318
|
+
const abs = resolve3(shown, rel === "." ? "" : rel);
|
|
5319
|
+
if (!insideRoot(shown, abs)) {
|
|
5320
|
+
warnings.push(rel);
|
|
5321
|
+
continue;
|
|
5322
|
+
}
|
|
5323
|
+
let real;
|
|
5324
|
+
let isDir2;
|
|
5325
|
+
try {
|
|
5326
|
+
real = await realpath(abs);
|
|
5327
|
+
isDir2 = (await stat8(real)).isDirectory();
|
|
5328
|
+
} catch {
|
|
5329
|
+
warnings.push(rel);
|
|
5330
|
+
continue;
|
|
5331
|
+
}
|
|
5332
|
+
if (!insideRoot(realRoot, real)) {
|
|
5333
|
+
warnings.push(rel);
|
|
5334
|
+
continue;
|
|
5335
|
+
}
|
|
5336
|
+
if (seen.has(abs)) continue;
|
|
5337
|
+
seen.add(abs);
|
|
5338
|
+
mentions.push({ path: abs, isDir: isDir2 });
|
|
5339
|
+
}
|
|
5340
|
+
return { mentions, warnings };
|
|
5341
|
+
}
|
|
5342
|
+
function mentionSection(mentions) {
|
|
5343
|
+
if (!mentions.length) return "";
|
|
5344
|
+
const lines = mentions.map((m) => {
|
|
5345
|
+
const shown = m.isDir && !m.path.endsWith(sep2) && !m.path.endsWith("/") ? m.path + (m.path.includes("\\") && !m.path.includes("/") ? "\\" : "/") : m.path;
|
|
5346
|
+
const quoted = /\s|`/.test(shown) ? "`" + shown.replace(/`/g, "'") + "`" : shown;
|
|
5347
|
+
return `- ${quoted}${m.isDir ? " (folder)" : ""}`;
|
|
5348
|
+
});
|
|
5349
|
+
return `Referenced files (read them as needed):
|
|
5350
|
+
${lines.join("\n")}`;
|
|
5351
|
+
}
|
|
5352
|
+
function withMentions(text, mentions) {
|
|
5353
|
+
const sec = mentionSection(mentions);
|
|
5354
|
+
if (!sec) return text;
|
|
5355
|
+
return text.trim() ? `${text}
|
|
5356
|
+
|
|
5357
|
+
${sec}` : sec;
|
|
5358
|
+
}
|
|
5359
|
+
var MAX_MENTIONS;
|
|
5360
|
+
var init_mentions = __esm({
|
|
5361
|
+
"daemon/src/util/mentions.ts"() {
|
|
5362
|
+
MAX_MENTIONS = 50;
|
|
5363
|
+
}
|
|
5364
|
+
});
|
|
5365
|
+
|
|
5366
|
+
// daemon/src/preview/url.ts
|
|
5367
|
+
function isLoopbackHost(host) {
|
|
5368
|
+
const h = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5369
|
+
if (h === "localhost" || h.endsWith(".localhost")) return /^[a-z0-9.-]+$/.test(h) && !h.includes("..");
|
|
5370
|
+
if (h === "0.0.0.0" || h === "::1" || h === "::") return true;
|
|
5371
|
+
const m = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
|
|
5372
|
+
return !!m && m.slice(1).every((x) => Number(x) <= 255);
|
|
5373
|
+
}
|
|
5374
|
+
function connectHosts(h) {
|
|
5375
|
+
if (h === "localhost" || h.endsWith(".localhost")) return ["127.0.0.1", "::1"];
|
|
5376
|
+
if (h === "0.0.0.0") return ["127.0.0.1"];
|
|
5377
|
+
if (h === "::1") return ["::1"];
|
|
5378
|
+
if (h === "::") return ["::1", "127.0.0.1"];
|
|
5379
|
+
return [h];
|
|
5380
|
+
}
|
|
5381
|
+
function withScheme(raw) {
|
|
5382
|
+
const s = raw.trim();
|
|
5383
|
+
if (/^\d{1,5}$/.test(s)) return `http://localhost:${s}/`;
|
|
5384
|
+
if (/^:\d{1,5}(?:[/?#].*)?$/.test(s)) return `http://localhost${s}`;
|
|
5385
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return s;
|
|
5386
|
+
if (/^[a-z][a-z0-9+.-]*:(?!\d)/i.test(s)) return s;
|
|
5387
|
+
return `http://${s}`;
|
|
5388
|
+
}
|
|
5389
|
+
function checkPreviewUrl(raw) {
|
|
5390
|
+
if (typeof raw !== "string" || !raw.trim()) return { ok: false, reason: "empty" };
|
|
5391
|
+
if (raw.length > MAX_PREVIEW_URL) return { ok: false, reason: "too_long" };
|
|
5392
|
+
let u;
|
|
5393
|
+
try {
|
|
5394
|
+
u = new URL(withScheme(raw));
|
|
5395
|
+
} catch {
|
|
5396
|
+
return { ok: false, reason: "bad_url" };
|
|
5397
|
+
}
|
|
5398
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return { ok: false, reason: "scheme" };
|
|
5399
|
+
if (u.username || u.password) return { ok: false, reason: "userinfo" };
|
|
5400
|
+
const hostname = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5401
|
+
if (!isLoopbackHost(hostname)) return { ok: false, reason: "not_local" };
|
|
5402
|
+
const secure = u.protocol === "https:";
|
|
5403
|
+
const port = u.port ? Number(u.port) : secure ? 443 : 80;
|
|
5404
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return { ok: false, reason: "port" };
|
|
5405
|
+
const href = u.href;
|
|
5406
|
+
if (href.length > MAX_PREVIEW_URL) return { ok: false, reason: "too_long" };
|
|
5407
|
+
const hostForUrl = hostname.includes(":") ? `[${hostname}]` : hostname;
|
|
5408
|
+
return {
|
|
5409
|
+
ok: true,
|
|
5410
|
+
target: {
|
|
5411
|
+
href,
|
|
5412
|
+
origin: `${u.protocol}//${hostForUrl}:${port}`,
|
|
5413
|
+
path: `${u.pathname}${u.search}${u.hash}`,
|
|
5414
|
+
secure,
|
|
5415
|
+
hostname,
|
|
5416
|
+
port,
|
|
5417
|
+
connect: connectHosts(hostname),
|
|
5418
|
+
hostHeader: `${hostForUrl}:${port}`
|
|
5419
|
+
}
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
function deniedPort(port, denied) {
|
|
5423
|
+
for (const d of denied) if (d === port) return true;
|
|
5424
|
+
return false;
|
|
5425
|
+
}
|
|
5426
|
+
function displayOrigin(origin) {
|
|
5427
|
+
return origin.replace(/^http:\/\/(.+):80$/, "http://$1").replace(/^https:\/\/(.+):443$/, "https://$1");
|
|
5428
|
+
}
|
|
5429
|
+
function externalHref(raw) {
|
|
5430
|
+
if (typeof raw !== "string" || !raw || raw.length > MAX_PREVIEW_URL) return null;
|
|
5431
|
+
let u;
|
|
5432
|
+
try {
|
|
5433
|
+
u = new URL(raw);
|
|
5434
|
+
} catch {
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return null;
|
|
5438
|
+
if (!u.hostname) return null;
|
|
5439
|
+
const href = u.href;
|
|
5440
|
+
if (/[\s"'<>`\\^{|}]/.test(href) || href.length > MAX_PREVIEW_URL) return null;
|
|
5441
|
+
return href;
|
|
5442
|
+
}
|
|
5443
|
+
var MAX_PREVIEW_URL;
|
|
5444
|
+
var init_url = __esm({
|
|
5445
|
+
"daemon/src/preview/url.ts"() {
|
|
5446
|
+
MAX_PREVIEW_URL = 2048;
|
|
5447
|
+
}
|
|
5448
|
+
});
|
|
5449
|
+
|
|
5450
|
+
// daemon/src/util/openExternal.ts
|
|
5451
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
5452
|
+
function openerFor(platform, href) {
|
|
5453
|
+
if (platform === "win32") return { cmd: "rundll32.exe", args: ["url.dll,FileProtocolHandler", href] };
|
|
5454
|
+
if (platform === "darwin") return { cmd: "open", args: [href] };
|
|
5455
|
+
return { cmd: "xdg-open", args: [href] };
|
|
5456
|
+
}
|
|
5457
|
+
async function openExternal(raw, opts = {}) {
|
|
5458
|
+
const href = externalHref(raw);
|
|
5459
|
+
if (!href) return { ok: false, reason: "bad_url" };
|
|
5460
|
+
const { cmd, args } = openerFor(opts.platform ?? process.platform, href);
|
|
5461
|
+
const run = opts.exec ?? execFile3;
|
|
5462
|
+
return await new Promise((resolve4) => {
|
|
5463
|
+
let settled = false;
|
|
5464
|
+
const done = (v) => {
|
|
5465
|
+
if (!settled) {
|
|
5466
|
+
settled = true;
|
|
5467
|
+
resolve4(v);
|
|
5468
|
+
}
|
|
5469
|
+
};
|
|
5470
|
+
try {
|
|
5471
|
+
const child = run(cmd, args, { windowsHide: true, timeout: 15e3 }, (err) => {
|
|
5472
|
+
done(err ? { ok: false, href, reason: "opener_failed" } : { ok: true, href });
|
|
5473
|
+
});
|
|
5474
|
+
child.on("error", () => done({ ok: false, href, reason: "no_opener" }));
|
|
5475
|
+
} catch {
|
|
5476
|
+
done({ ok: false, href, reason: "no_opener" });
|
|
5477
|
+
}
|
|
5478
|
+
setTimeout(() => done({ ok: true, href }), opts.waitMs ?? 4e3).unref?.();
|
|
5479
|
+
});
|
|
5480
|
+
}
|
|
5481
|
+
var init_openExternal = __esm({
|
|
5482
|
+
"daemon/src/util/openExternal.ts"() {
|
|
5483
|
+
init_url();
|
|
5484
|
+
}
|
|
5485
|
+
});
|
|
5486
|
+
|
|
5487
|
+
// daemon/src/preview/inject.ts
|
|
5488
|
+
function injectScriptTag(html) {
|
|
5489
|
+
const tag = `<script src="${PREVIEW_SCRIPT_PATH}"></script>`;
|
|
5490
|
+
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
5491
|
+
if (head) return html.slice(0, head.index + head[0].length) + tag + html.slice(head.index + head[0].length);
|
|
5492
|
+
const root = /<html(\s[^>]*)?>/i.exec(html);
|
|
5493
|
+
if (root) return html.slice(0, root.index + root[0].length) + tag + html.slice(root.index + root[0].length);
|
|
5494
|
+
const doctype = /^\s*<!doctype[^>]*>/i.exec(html);
|
|
5495
|
+
if (doctype) return html.slice(0, doctype[0].length) + tag + html.slice(doctype[0].length);
|
|
5496
|
+
return tag + html;
|
|
5497
|
+
}
|
|
5498
|
+
var PREVIEW_SCRIPT_PATH, PREVIEW_SCRIPT;
|
|
5499
|
+
var init_inject = __esm({
|
|
5500
|
+
"daemon/src/preview/inject.ts"() {
|
|
5501
|
+
PREVIEW_SCRIPT_PATH = "/__agent_console__/preview.js";
|
|
5502
|
+
PREVIEW_SCRIPT = `(function(){
|
|
5503
|
+
"use strict";
|
|
5504
|
+
if (window.__acPreview) return; window.__acPreview = 1;
|
|
5505
|
+
function mem(){ var m = new Map(); var s = {
|
|
5506
|
+
getItem: function(k){ k = String(k); return m.has(k) ? m.get(k) : null; },
|
|
5507
|
+
setItem: function(k, v){ m.set(String(k), String(v)); },
|
|
5508
|
+
removeItem: function(k){ m.delete(String(k)); },
|
|
5509
|
+
clear: function(){ m.clear(); },
|
|
5510
|
+
key: function(i){ var a = Array.from(m.keys()); return i >= 0 && i < a.length ? a[i] : null; }
|
|
5511
|
+
}; Object.defineProperty(s, "length", { get: function(){ return m.size; } }); return s; }
|
|
5512
|
+
["localStorage", "sessionStorage"].forEach(function(n){
|
|
5513
|
+
var ok = true; try { var st = window[n]; st.getItem("__ac"); } catch (e) { ok = false; }
|
|
5514
|
+
if (!ok) { try { Object.defineProperty(window, n, { value: mem(), configurable: true }); } catch (e) {} }
|
|
5515
|
+
});
|
|
5516
|
+
try { void document.cookie; } catch (e) {
|
|
5517
|
+
var jar = {};
|
|
5518
|
+
try { Object.defineProperty(document, "cookie", { configurable: true,
|
|
5519
|
+
get: function(){ return Object.keys(jar).map(function(k){ return k + "=" + jar[k]; }).join("; "); },
|
|
5520
|
+
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(); }
|
|
5521
|
+
}); } catch (e2) {}
|
|
5522
|
+
}
|
|
5523
|
+
var parentWin = window.parent;
|
|
5524
|
+
if (!parentWin || parentWin === window) return;
|
|
5525
|
+
var last = "";
|
|
5526
|
+
function report(){ try {
|
|
5527
|
+
var msg = { ac_preview: 1, href: String(location.href), title: String(document.title || "") };
|
|
5528
|
+
var key = msg.href + "\\n" + msg.title; if (key === last) return; last = key;
|
|
5529
|
+
parentWin.postMessage(msg, "*");
|
|
5530
|
+
} catch (e) {} }
|
|
5531
|
+
["pushState", "replaceState"].forEach(function(k){
|
|
5532
|
+
var orig = history[k]; if (typeof orig !== "function") return;
|
|
5533
|
+
history[k] = function(){ var r = orig.apply(this, arguments); setTimeout(report, 0); return r; };
|
|
5534
|
+
});
|
|
5535
|
+
window.addEventListener("popstate", report);
|
|
5536
|
+
window.addEventListener("hashchange", report);
|
|
5537
|
+
window.addEventListener("load", report);
|
|
5538
|
+
document.addEventListener("DOMContentLoaded", report);
|
|
5539
|
+
setInterval(report, 1000);
|
|
5540
|
+
window.addEventListener("message", function(e){
|
|
5541
|
+
if (e.source !== parentWin) return;
|
|
5542
|
+
var d = e.data; if (!d || typeof d !== "object") return;
|
|
5543
|
+
var c = d.ac_preview_cmd;
|
|
5544
|
+
if (c === "back") history.back();
|
|
5545
|
+
else if (c === "forward") history.forward();
|
|
5546
|
+
else if (c === "reload") location.reload();
|
|
5547
|
+
else if (c === "ping") { last = ""; report(); }
|
|
5548
|
+
});
|
|
5549
|
+
report();
|
|
5550
|
+
})();
|
|
5551
|
+
`;
|
|
5552
|
+
}
|
|
5553
|
+
});
|
|
5554
|
+
|
|
5555
|
+
// daemon/src/preview/proxy.ts
|
|
5556
|
+
import { createServer as createServer2, request as httpRequest } from "node:http";
|
|
5557
|
+
import { request as httpsRequest } from "node:https";
|
|
5558
|
+
import { connect as netConnect, isIP } from "node:net";
|
|
5559
|
+
import { connect as tlsConnect } from "node:tls";
|
|
5560
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
5561
|
+
function esc(s) {
|
|
5562
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5563
|
+
}
|
|
5564
|
+
function rewriteRequestHeaders(inH, t, proxyOrigin, upgrade) {
|
|
5565
|
+
const out = {};
|
|
5566
|
+
const targetOrigin = displayOrigin(t.origin);
|
|
5567
|
+
for (const [k, v] of Object.entries(inH)) {
|
|
5568
|
+
if (v === void 0) continue;
|
|
5569
|
+
const key = k.toLowerCase();
|
|
5570
|
+
if (!upgrade && HOP.has(key)) continue;
|
|
5571
|
+
if (upgrade && (key === "proxy-connection" || key === "proxy-authorization" || key === "keep-alive" || key === "te" || key === "trailer" || key === "transfer-encoding")) continue;
|
|
5572
|
+
if (key === "cookie" || key === "host" || key === "origin" || key === "referer" || key === "forwarded" || key.startsWith("x-forwarded-")) continue;
|
|
5573
|
+
out[key] = v;
|
|
5574
|
+
}
|
|
5575
|
+
out.host = t.hostHeader.replace(/:(80|443)$/, (m) => m === ":80" && !t.secure || m === ":443" && t.secure ? "" : m);
|
|
5576
|
+
if (inH.origin !== void 0) out.origin = targetOrigin;
|
|
5577
|
+
const ref = inH.referer;
|
|
5578
|
+
if (typeof ref === "string" && (ref === proxyOrigin || ref.startsWith(proxyOrigin + "/"))) out.referer = targetOrigin + ref.slice(proxyOrigin.length);
|
|
5579
|
+
if (inH["sec-fetch-site"] !== void 0) out["sec-fetch-site"] = "same-origin";
|
|
5580
|
+
if (!upgrade) out["accept-encoding"] = "identity";
|
|
5581
|
+
return out;
|
|
5582
|
+
}
|
|
5583
|
+
function rewriteResponseHeaders(inH, t, proxyOrigin) {
|
|
5584
|
+
const out = {};
|
|
5585
|
+
for (const [k, v] of Object.entries(inH)) {
|
|
5586
|
+
if (v === void 0) continue;
|
|
5587
|
+
const key = k.toLowerCase();
|
|
5588
|
+
if (HOP.has(key)) continue;
|
|
5589
|
+
if (key === "x-frame-options" || key === "set-cookie" || key === "referrer-policy" || key.startsWith("access-control-allow-")) continue;
|
|
5590
|
+
if (key === "content-security-policy" || key === "content-security-policy-report-only") {
|
|
5591
|
+
const kept = (Array.isArray(v) ? v : [v]).map(stripFrameAncestors).filter(Boolean);
|
|
5592
|
+
if (kept.length) out[key] = kept.join(", ");
|
|
5593
|
+
continue;
|
|
5594
|
+
}
|
|
5595
|
+
if (key === "location" && typeof v === "string") {
|
|
5596
|
+
out[key] = rewriteLocation(v, t, proxyOrigin);
|
|
5597
|
+
continue;
|
|
5598
|
+
}
|
|
5599
|
+
out[key] = v;
|
|
5600
|
+
}
|
|
5601
|
+
out["access-control-allow-origin"] = "null";
|
|
5602
|
+
out["referrer-policy"] = "no-referrer";
|
|
5603
|
+
const vary = typeof out.vary === "string" ? out.vary : "";
|
|
5604
|
+
out.vary = vary ? /\borigin\b/i.test(vary) ? vary : `${vary}, Origin` : "Origin";
|
|
5605
|
+
return out;
|
|
5606
|
+
}
|
|
5607
|
+
function stripFrameAncestors(csp) {
|
|
5608
|
+
return csp.split(";").map((d) => d.trim()).filter((d) => d && !/^frame-ancestors(\s|$)/i.test(d)).join("; ");
|
|
5609
|
+
}
|
|
5610
|
+
function rewriteLocation(loc, t, proxyOrigin) {
|
|
5611
|
+
let u;
|
|
5612
|
+
try {
|
|
5613
|
+
u = new URL(loc, t.href);
|
|
5614
|
+
} catch {
|
|
5615
|
+
return loc;
|
|
5616
|
+
}
|
|
5617
|
+
if (!/^[a-z][a-z0-9+.-]*:/i.test(loc) && !loc.startsWith("//")) return loc;
|
|
5618
|
+
const port = u.port ? Number(u.port) : u.protocol === "https:" ? 443 : 80;
|
|
5619
|
+
const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5620
|
+
if (u.protocol === (t.secure ? "https:" : "http:") && host === t.hostname && port === t.port) {
|
|
5621
|
+
return proxyOrigin + u.pathname + u.search + u.hash;
|
|
5622
|
+
}
|
|
5623
|
+
return loc;
|
|
5624
|
+
}
|
|
5625
|
+
function looksLikeMagpie(h) {
|
|
5626
|
+
return Object.keys(h).some((k) => k.toLowerCase().startsWith("x-magpie-"));
|
|
5627
|
+
}
|
|
5628
|
+
function magpieIndex(body) {
|
|
5629
|
+
try {
|
|
5630
|
+
const j = JSON.parse(body);
|
|
5631
|
+
return !!j && typeof j === "object" && j.name === "magpie" && (Array.isArray(j.apis) || typeof j.version === "string");
|
|
5632
|
+
} catch {
|
|
5633
|
+
return false;
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
async function identify(t, addrs, timeoutMs) {
|
|
5637
|
+
for (const addr of addrs) {
|
|
5638
|
+
const r = await new Promise((resolve4) => {
|
|
5639
|
+
let settled = false;
|
|
5640
|
+
const done = (v) => {
|
|
5641
|
+
if (!settled) {
|
|
5642
|
+
settled = true;
|
|
5643
|
+
clearTimeout(timer);
|
|
5644
|
+
resolve4(v);
|
|
5645
|
+
}
|
|
5646
|
+
};
|
|
5647
|
+
const reqFn = t.secure ? httpsRequest : httpRequest;
|
|
5648
|
+
const q = reqFn({
|
|
5649
|
+
host: addr,
|
|
5650
|
+
port: t.port,
|
|
5651
|
+
method: "GET",
|
|
5652
|
+
path: "/",
|
|
5653
|
+
headers: { host: t.hostHeader, accept: "application/json", "accept-encoding": "identity", "user-agent": "agent-console-preview-check" },
|
|
5654
|
+
agent: false,
|
|
5655
|
+
...t.secure ? { rejectUnauthorized: false, servername: isIP(t.hostname) ? void 0 : t.hostname } : {}
|
|
5656
|
+
});
|
|
5657
|
+
const timer = setTimeout(() => {
|
|
5658
|
+
q.destroy();
|
|
5659
|
+
done("none");
|
|
5660
|
+
}, timeoutMs);
|
|
5661
|
+
q.on("error", () => done("none"));
|
|
5662
|
+
q.on("response", (res) => {
|
|
5663
|
+
if (looksLikeMagpie(res.headers)) {
|
|
5664
|
+
res.destroy();
|
|
5665
|
+
return done("magpie");
|
|
5666
|
+
}
|
|
5667
|
+
const ct = String(res.headers["content-type"] ?? "").toLowerCase();
|
|
5668
|
+
if (!ct.includes("json")) {
|
|
5669
|
+
res.destroy();
|
|
5670
|
+
return done("other");
|
|
5671
|
+
}
|
|
5672
|
+
const chunks = [];
|
|
5673
|
+
let size = 0;
|
|
5674
|
+
res.on("data", (c) => {
|
|
5675
|
+
size += c.length;
|
|
5676
|
+
if (size > 256 * 1024) {
|
|
5677
|
+
res.destroy();
|
|
5678
|
+
done("other");
|
|
5679
|
+
return;
|
|
5680
|
+
}
|
|
5681
|
+
chunks.push(c);
|
|
5682
|
+
});
|
|
5683
|
+
res.on("end", () => done(magpieIndex(Buffer.concat(chunks).toString("utf8")) ? "magpie" : "other"));
|
|
5684
|
+
res.on("error", () => done("none"));
|
|
5685
|
+
});
|
|
5686
|
+
q.end();
|
|
5687
|
+
});
|
|
5688
|
+
if (r !== "none") return r;
|
|
5689
|
+
}
|
|
5690
|
+
return "none";
|
|
5691
|
+
}
|
|
5692
|
+
var PreviewError, HOP, RETRY_CODES, IDENTITY_TTL, PREVIEW_PING_PATH, PreviewProxy;
|
|
5693
|
+
var init_proxy = __esm({
|
|
5694
|
+
"daemon/src/preview/proxy.ts"() {
|
|
5695
|
+
init_security();
|
|
5696
|
+
init_url();
|
|
5697
|
+
init_inject();
|
|
5698
|
+
PreviewError = class extends Error {
|
|
5699
|
+
code;
|
|
5700
|
+
constructor(code, message) {
|
|
5701
|
+
super(message);
|
|
5702
|
+
this.code = code;
|
|
5703
|
+
}
|
|
5704
|
+
};
|
|
5705
|
+
HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "proxy-connection", "proxy-authorization", "proxy-authenticate", "te", "trailer", "transfer-encoding", "upgrade"]);
|
|
5706
|
+
RETRY_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "EADDRNOTAVAIL", "EAFNOSUPPORT", "ENETUNREACH", "EHOSTUNREACH", "ECONNRESET", "ETIMEDOUT"]);
|
|
5707
|
+
IDENTITY_TTL = 60 * 1e3;
|
|
5708
|
+
PREVIEW_PING_PATH = "/__agent_console__/ping";
|
|
5709
|
+
PreviewProxy = class {
|
|
5710
|
+
constructor(opts) {
|
|
5711
|
+
this.opts = opts;
|
|
5712
|
+
this.idleMs = opts.idleMs ?? 10 * 60 * 1e3;
|
|
5713
|
+
this.maxTargets = opts.maxTargets ?? 8;
|
|
5714
|
+
this.maxHtml = opts.maxHtmlBytes ?? 8 * 1024 * 1024;
|
|
5715
|
+
this.now = opts.now ?? Date.now;
|
|
5716
|
+
this.identityTimeout = opts.identityTimeoutMs ?? 30 * 1e3;
|
|
5717
|
+
}
|
|
5718
|
+
opts;
|
|
5719
|
+
byOrigin = /* @__PURE__ */ new Map();
|
|
5720
|
+
opening = /* @__PURE__ */ new Map();
|
|
5721
|
+
sweepTimer = null;
|
|
5722
|
+
idleMs;
|
|
5723
|
+
maxTargets;
|
|
5724
|
+
maxHtml;
|
|
5725
|
+
now;
|
|
5726
|
+
identityTimeout;
|
|
5727
|
+
/** Ports of the listeners themselves (a preview of a preview is refused) */
|
|
5728
|
+
ports() {
|
|
5729
|
+
return [...this.byOrigin.values()].map((l) => l.port);
|
|
5730
|
+
}
|
|
5731
|
+
get size() {
|
|
5732
|
+
return this.byOrigin.size;
|
|
5733
|
+
}
|
|
5734
|
+
async open(raw) {
|
|
5735
|
+
const c = checkPreviewUrl(raw);
|
|
5736
|
+
if (!c.ok) throw new PreviewError(c.reason, `preview url refused: ${c.reason}`);
|
|
5737
|
+
const t = c.target;
|
|
5738
|
+
if (deniedPort(t.port, [...this.opts.deniedPorts(), ...this.ports()])) {
|
|
5739
|
+
throw new PreviewError("denied_port", `port ${t.port} can't be previewed`);
|
|
5740
|
+
}
|
|
5741
|
+
let l = this.byOrigin.get(t.origin);
|
|
5742
|
+
if (!l) {
|
|
5743
|
+
const who = await identify(t, t.connect, Math.min(3e3, this.identityTimeout));
|
|
5744
|
+
if (who === "magpie") {
|
|
5745
|
+
this.opts.log?.("warn", `preview refused: ${t.origin} answers like Magpie's gateway`);
|
|
5746
|
+
throw new PreviewError("denied_port", `${t.origin} is Magpie's gateway`);
|
|
5747
|
+
}
|
|
5748
|
+
let p = this.opening.get(t.origin);
|
|
5749
|
+
if (!p) {
|
|
5750
|
+
p = this.listen(t).finally(() => this.opening.delete(t.origin));
|
|
5751
|
+
this.opening.set(t.origin, p);
|
|
5752
|
+
}
|
|
5753
|
+
l = await p;
|
|
5754
|
+
}
|
|
5755
|
+
l.lastUsed = this.now();
|
|
5756
|
+
l.nonce = randomBytes3(16).toString("hex");
|
|
5757
|
+
l.canaryAuthority = `acprobe${randomBytes3(16).toString("hex")}.localhost:${l.port}`;
|
|
5758
|
+
l.canaryNonce = randomBytes3(16).toString("hex");
|
|
5759
|
+
this.ensureSweep();
|
|
5760
|
+
return {
|
|
5761
|
+
proxyOrigin: l.proxyOrigin,
|
|
5762
|
+
target: t.origin,
|
|
5763
|
+
url: t.href,
|
|
5764
|
+
path: t.path,
|
|
5765
|
+
proxyHref: l.proxyOrigin + t.path,
|
|
5766
|
+
nonce: l.nonce,
|
|
5767
|
+
canaryOrigin: `http://${l.canaryAuthority}`,
|
|
5768
|
+
canaryNonce: l.canaryNonce
|
|
5769
|
+
};
|
|
5770
|
+
}
|
|
5771
|
+
/** Close the listener for this target (any URL on it, or its origin) */
|
|
5772
|
+
release(raw) {
|
|
5773
|
+
const c = checkPreviewUrl(raw);
|
|
5774
|
+
if (!c.ok) return false;
|
|
5775
|
+
const l = this.byOrigin.get(c.target.origin);
|
|
5776
|
+
if (!l) return false;
|
|
5777
|
+
this.close(l);
|
|
5778
|
+
return true;
|
|
5779
|
+
}
|
|
5780
|
+
closeAll() {
|
|
5781
|
+
for (const l of [...this.byOrigin.values()]) this.close(l);
|
|
5782
|
+
if (this.sweepTimer) clearInterval(this.sweepTimer);
|
|
5783
|
+
this.sweepTimer = null;
|
|
5784
|
+
}
|
|
5785
|
+
/** Close listeners idle for idleMs (no request and no open WebSocket); exposed for tests with a fake clock */
|
|
5786
|
+
sweep() {
|
|
5787
|
+
const now = this.now();
|
|
5788
|
+
for (const l of [...this.byOrigin.values()]) {
|
|
5789
|
+
if (l.tunnels === 0 && now - l.lastUsed > this.idleMs) {
|
|
5790
|
+
this.opts.log?.("info", `preview proxy for ${l.target.origin} idle, closing`);
|
|
5791
|
+
this.close(l);
|
|
5792
|
+
}
|
|
5793
|
+
}
|
|
5794
|
+
if (this.byOrigin.size === 0 && this.sweepTimer) {
|
|
5795
|
+
clearInterval(this.sweepTimer);
|
|
5796
|
+
this.sweepTimer = null;
|
|
5797
|
+
}
|
|
5798
|
+
}
|
|
5799
|
+
ensureSweep() {
|
|
5800
|
+
if (this.sweepTimer) return;
|
|
5801
|
+
this.sweepTimer = setInterval(() => this.sweep(), 60 * 1e3);
|
|
5802
|
+
this.sweepTimer.unref();
|
|
5803
|
+
}
|
|
5804
|
+
close(l) {
|
|
5805
|
+
if (this.byOrigin.get(l.target.origin) === l) this.byOrigin.delete(l.target.origin);
|
|
5806
|
+
try {
|
|
5807
|
+
l.server.close();
|
|
5808
|
+
} catch {
|
|
5809
|
+
}
|
|
5810
|
+
for (const s of l.sockets) s.destroy();
|
|
5811
|
+
l.sockets.clear();
|
|
5812
|
+
}
|
|
5813
|
+
async listen(t) {
|
|
5814
|
+
while (this.byOrigin.size >= this.maxTargets) {
|
|
5815
|
+
const oldest = [...this.byOrigin.values()].sort((a2, b) => a2.lastUsed - b.lastUsed)[0];
|
|
5816
|
+
if (!oldest) break;
|
|
5817
|
+
this.close(oldest);
|
|
5818
|
+
}
|
|
5819
|
+
const server = createServer2();
|
|
5820
|
+
const l = {
|
|
5821
|
+
target: t,
|
|
5822
|
+
server,
|
|
5823
|
+
port: 0,
|
|
5824
|
+
authority: "",
|
|
5825
|
+
proxyOrigin: "",
|
|
5826
|
+
nonce: "",
|
|
5827
|
+
canaryAuthority: "",
|
|
5828
|
+
canaryNonce: "",
|
|
5829
|
+
magpie: false,
|
|
5830
|
+
checkedAt: 0,
|
|
5831
|
+
resolving: null,
|
|
5832
|
+
lastUsed: this.now(),
|
|
5833
|
+
sockets: /* @__PURE__ */ new Set(),
|
|
5834
|
+
tunnels: 0,
|
|
5835
|
+
addr: null
|
|
5836
|
+
};
|
|
5837
|
+
server.on("connection", (s) => {
|
|
5838
|
+
l.sockets.add(s);
|
|
5839
|
+
s.on("close", () => l.sockets.delete(s));
|
|
5840
|
+
});
|
|
5841
|
+
server.on("request", (req, res) => this.onRequest(l, req, res));
|
|
5842
|
+
server.on("upgrade", (req, socket, head) => this.onUpgrade(l, req, socket, head));
|
|
5843
|
+
server.on("clientError", (_e, socket) => {
|
|
5844
|
+
try {
|
|
5845
|
+
socket.destroy();
|
|
5846
|
+
} catch {
|
|
5847
|
+
}
|
|
5848
|
+
});
|
|
5849
|
+
await new Promise((resolve4, reject) => {
|
|
5850
|
+
server.once("error", reject);
|
|
5851
|
+
server.listen(0, "127.0.0.1", () => resolve4());
|
|
5852
|
+
});
|
|
5853
|
+
const a = server.address();
|
|
5854
|
+
l.port = a && typeof a === "object" ? a.port : 0;
|
|
5855
|
+
l.authority = `ac${randomBytes3(16).toString("hex")}.localhost:${l.port}`;
|
|
5856
|
+
l.proxyOrigin = `http://${l.authority}`;
|
|
5857
|
+
this.byOrigin.set(t.origin, l);
|
|
5858
|
+
this.opts.log?.("info", `preview proxy 127.0.0.1:${l.port} \u2192 ${t.origin}`);
|
|
5859
|
+
return l;
|
|
5860
|
+
}
|
|
5861
|
+
// ---- request gate shared by HTTP and WS ----
|
|
5862
|
+
/** null = refuse; otherwise which name matched (canary is ping-only) */
|
|
5863
|
+
gate(l, req) {
|
|
5864
|
+
const host = String(req.headers.host ?? "").toLowerCase();
|
|
5865
|
+
let role = null;
|
|
5866
|
+
if (l.authority && tokenEqual(host, l.authority)) role = "key";
|
|
5867
|
+
else if (l.canaryAuthority && tokenEqual(host, l.canaryAuthority)) role = "canary";
|
|
5868
|
+
else {
|
|
5869
|
+
return checkHost(req.headers.host, l.port).ok ? { code: 403, why: "preview_key" } : { code: 421, why: "bad_host" };
|
|
5870
|
+
}
|
|
5871
|
+
const o = req.headers.origin;
|
|
5872
|
+
if (o !== void 0 && o !== "" && o !== "null") {
|
|
5873
|
+
if (role === "key" && o.toLowerCase() !== l.proxyOrigin) return { code: 403, why: "bad_origin" };
|
|
5874
|
+
if (role === "canary") return { code: 403, why: "bad_origin" };
|
|
5875
|
+
}
|
|
5876
|
+
return { role };
|
|
5877
|
+
}
|
|
5878
|
+
/** A refusal: plain text, no CORS headers (the page that sent it can't read it), nothing forwarded */
|
|
5879
|
+
refuse(res, req, code, why) {
|
|
5880
|
+
res.writeHead(code, {
|
|
5881
|
+
"content-type": "text/plain; charset=utf-8",
|
|
5882
|
+
"cache-control": "no-store",
|
|
5883
|
+
"x-content-type-options": "nosniff",
|
|
5884
|
+
"content-security-policy": "sandbox; default-src 'none'",
|
|
5885
|
+
"referrer-policy": "no-referrer"
|
|
5886
|
+
});
|
|
5887
|
+
res.end(req.method === "HEAD" ? void 0 : why);
|
|
5888
|
+
req.resume();
|
|
5889
|
+
}
|
|
5890
|
+
onRequest(l, req, res) {
|
|
5891
|
+
const g = this.gate(l, req);
|
|
5892
|
+
if ("code" in g) {
|
|
5893
|
+
this.refuse(res, req, g.code, g.why);
|
|
5894
|
+
return;
|
|
5895
|
+
}
|
|
5896
|
+
l.lastUsed = this.now();
|
|
5897
|
+
const path3 = (req.url ?? "/").split("?")[0];
|
|
5898
|
+
if (path3 === PREVIEW_PING_PATH) {
|
|
5899
|
+
const body = g.role === "canary" ? l.canaryNonce : l.nonce;
|
|
5900
|
+
res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "referrer-policy": "no-referrer" });
|
|
5901
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
5902
|
+
req.resume();
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
if (g.role === "canary") {
|
|
5906
|
+
this.refuse(res, req, 403, "preview_canary");
|
|
5907
|
+
return;
|
|
5908
|
+
}
|
|
5909
|
+
if (l.magpie) {
|
|
5910
|
+
this.refuse(res, req, 403, "magpie");
|
|
5911
|
+
return;
|
|
5912
|
+
}
|
|
5913
|
+
if (path3 === PREVIEW_SCRIPT_PATH) {
|
|
5914
|
+
res.writeHead(200, {
|
|
5915
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
5916
|
+
"cache-control": "no-store",
|
|
5917
|
+
"access-control-allow-origin": "null",
|
|
5918
|
+
"x-content-type-options": "nosniff",
|
|
5919
|
+
"referrer-policy": "no-referrer"
|
|
5920
|
+
});
|
|
5921
|
+
res.end(req.method === "HEAD" ? void 0 : PREVIEW_SCRIPT);
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
if (req.method === "OPTIONS" && req.headers["access-control-request-method"]) {
|
|
5925
|
+
const h = {
|
|
5926
|
+
"access-control-allow-origin": "null",
|
|
5927
|
+
"access-control-allow-methods": String(req.headers["access-control-request-method"]).slice(0, 200) + ", GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD",
|
|
5928
|
+
"access-control-max-age": "600",
|
|
5929
|
+
vary: "origin"
|
|
5930
|
+
};
|
|
5931
|
+
const rh = req.headers["access-control-request-headers"];
|
|
5932
|
+
if (typeof rh === "string") h["access-control-allow-headers"] = rh.slice(0, 2e3);
|
|
5933
|
+
res.writeHead(204, h);
|
|
5934
|
+
res.end();
|
|
5935
|
+
req.resume();
|
|
5936
|
+
return;
|
|
5937
|
+
}
|
|
5938
|
+
void this.forward(l, req, res);
|
|
5939
|
+
}
|
|
5940
|
+
/**
|
|
5941
|
+
* The address to forward to, once the target is known not to be Magpie's gateway (asked again after IDENTITY_TTL).
|
|
5942
|
+
* Fails closed: no answer to the identity check → null (error page), Magpie → l.magpie (refused from then on).
|
|
5943
|
+
*/
|
|
5944
|
+
resolveAddr(l) {
|
|
5945
|
+
if (l.magpie) return Promise.resolve(null);
|
|
5946
|
+
if (l.addr && this.now() - l.checkedAt < IDENTITY_TTL) return Promise.resolve(l.addr);
|
|
5947
|
+
if (!l.resolving) {
|
|
5948
|
+
l.resolving = (async () => {
|
|
5949
|
+
const addr = l.addr ?? await this.probeAddr(l);
|
|
5950
|
+
if (!addr) return null;
|
|
5951
|
+
const who = await identify(l.target, [addr], this.identityTimeout);
|
|
5952
|
+
if (who === "magpie") {
|
|
5953
|
+
l.magpie = true;
|
|
5954
|
+
l.addr = null;
|
|
5955
|
+
this.opts.log?.("warn", `preview proxy for ${l.target.origin}: the target answers like Magpie's gateway, refusing`);
|
|
5956
|
+
return null;
|
|
5957
|
+
}
|
|
5958
|
+
if (who === "none") {
|
|
5959
|
+
l.addr = null;
|
|
5960
|
+
return null;
|
|
5961
|
+
}
|
|
5962
|
+
l.addr = addr;
|
|
5963
|
+
l.checkedAt = this.now();
|
|
5964
|
+
return addr;
|
|
5965
|
+
})().finally(() => {
|
|
5966
|
+
l.resolving = null;
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
return l.resolving;
|
|
5970
|
+
}
|
|
5971
|
+
async probeAddr(l) {
|
|
5972
|
+
for (const host of l.target.connect) {
|
|
5973
|
+
const ok = await new Promise((resolve4) => {
|
|
5974
|
+
const s = netConnect({ host, port: l.target.port });
|
|
5975
|
+
const done = (v) => {
|
|
5976
|
+
s.removeAllListeners();
|
|
5977
|
+
s.destroy();
|
|
5978
|
+
resolve4(v);
|
|
5979
|
+
};
|
|
5980
|
+
s.setTimeout(3e3, () => done(false));
|
|
5981
|
+
s.once("connect", () => done(true));
|
|
5982
|
+
s.once("error", () => done(false));
|
|
5983
|
+
});
|
|
5984
|
+
if (ok) return host;
|
|
5985
|
+
}
|
|
5986
|
+
return null;
|
|
5987
|
+
}
|
|
5988
|
+
servername(l) {
|
|
5989
|
+
return isIP(l.target.hostname) ? void 0 : l.target.hostname;
|
|
5990
|
+
}
|
|
5991
|
+
async forward(l, req, res) {
|
|
5992
|
+
const addr = await this.resolveAddr(l);
|
|
5993
|
+
if (l.magpie) {
|
|
5994
|
+
this.refuse(res, req, 403, "magpie");
|
|
5995
|
+
return;
|
|
5996
|
+
}
|
|
5997
|
+
if (!addr) {
|
|
5998
|
+
this.errorPage(l, req, res);
|
|
5999
|
+
return;
|
|
6000
|
+
}
|
|
6001
|
+
const headers = rewriteRequestHeaders(req.headers, l.target, l.proxyOrigin, false);
|
|
6002
|
+
const reqFn = l.target.secure ? httpsRequest : httpRequest;
|
|
6003
|
+
const up = reqFn({
|
|
6004
|
+
host: addr,
|
|
6005
|
+
port: l.target.port,
|
|
6006
|
+
method: req.method,
|
|
6007
|
+
path: req.url ?? "/",
|
|
6008
|
+
headers,
|
|
6009
|
+
...l.target.secure ? { rejectUnauthorized: false, servername: this.servername(l) } : {}
|
|
6010
|
+
});
|
|
6011
|
+
up.on("response", (ur) => this.respond(l, req, res, ur));
|
|
6012
|
+
up.on("error", (e) => {
|
|
6013
|
+
if (RETRY_CODES.has(e.code ?? "")) l.addr = null;
|
|
6014
|
+
if (!res.headersSent) this.errorPage(l, req, res);
|
|
6015
|
+
else res.destroy();
|
|
6016
|
+
});
|
|
6017
|
+
req.on("aborted", () => up.destroy());
|
|
6018
|
+
res.on("close", () => {
|
|
6019
|
+
if (!res.writableFinished) up.destroy();
|
|
6020
|
+
});
|
|
6021
|
+
req.pipe(up);
|
|
6022
|
+
}
|
|
6023
|
+
respond(l, req, res, ur) {
|
|
6024
|
+
l.lastUsed = this.now();
|
|
6025
|
+
if (looksLikeMagpie(ur.headers)) {
|
|
6026
|
+
l.magpie = true;
|
|
6027
|
+
l.addr = null;
|
|
6028
|
+
ur.resume();
|
|
6029
|
+
this.refuse(res, req, 403, "magpie");
|
|
6030
|
+
return;
|
|
6031
|
+
}
|
|
6032
|
+
const h = rewriteResponseHeaders(ur.headers, l.target, l.proxyOrigin);
|
|
6033
|
+
const status = ur.statusCode ?? 502;
|
|
6034
|
+
const ctype = String(ur.headers["content-type"] ?? "").toLowerCase();
|
|
6035
|
+
const encoded = !!ur.headers["content-encoding"] && ur.headers["content-encoding"] !== "identity";
|
|
6036
|
+
const html = ctype.includes("text/html") && !encoded && req.method !== "HEAD" && status !== 204 && status !== 304;
|
|
6037
|
+
if (!html) {
|
|
6038
|
+
res.writeHead(status, h);
|
|
6039
|
+
ur.pipe(res);
|
|
6040
|
+
return;
|
|
6041
|
+
}
|
|
6042
|
+
const chunks = [];
|
|
6043
|
+
let size = 0;
|
|
6044
|
+
let passthrough = false;
|
|
6045
|
+
ur.on("data", (c) => {
|
|
6046
|
+
if (passthrough) return;
|
|
6047
|
+
size += c.length;
|
|
6048
|
+
chunks.push(c);
|
|
6049
|
+
if (size > this.maxHtml) {
|
|
6050
|
+
passthrough = true;
|
|
6051
|
+
res.writeHead(status, h);
|
|
6052
|
+
for (const x of chunks) res.write(x);
|
|
6053
|
+
ur.removeAllListeners("data");
|
|
6054
|
+
ur.removeAllListeners("end");
|
|
6055
|
+
ur.pipe(res);
|
|
6056
|
+
}
|
|
6057
|
+
});
|
|
6058
|
+
ur.on("end", () => {
|
|
6059
|
+
if (passthrough) return;
|
|
6060
|
+
const out = Buffer.from(injectScriptTag(Buffer.concat(chunks).toString("latin1")), "latin1");
|
|
6061
|
+
h["content-length"] = String(out.length);
|
|
6062
|
+
res.writeHead(status, h);
|
|
6063
|
+
res.end(out);
|
|
6064
|
+
});
|
|
6065
|
+
ur.on("error", () => res.destroy());
|
|
6066
|
+
}
|
|
6067
|
+
errorPage(l, req, res) {
|
|
6068
|
+
const where = displayOrigin(l.target.origin).replace(/^https?:\/\//, "");
|
|
6069
|
+
const body = `<!doctype html><html><head><meta charset="utf-8"><title>${esc(where)}</title>
|
|
6070
|
+
<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}
|
|
6071
|
+
@media (prefers-color-scheme:dark){body{background:#1a1a1a;color:#a3a3a3}button{background:#262626;color:#e5e5e5;border-color:#3a3a3a}}
|
|
6072
|
+
.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>
|
|
6073
|
+
<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>
|
|
6074
|
+
<button onclick="location.reload()">\u91CD\u8BD5 / Retry</button></div></body></html>`;
|
|
6075
|
+
res.writeHead(502, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null" });
|
|
6076
|
+
res.end(req.method === "HEAD" ? void 0 : body);
|
|
6077
|
+
req.resume();
|
|
6078
|
+
}
|
|
6079
|
+
// ---- WebSocket (HMR) ----
|
|
6080
|
+
onUpgrade(l, req, socket, head) {
|
|
6081
|
+
const g = this.gate(l, req);
|
|
6082
|
+
if ("code" in g || g.role === "canary" || l.magpie) {
|
|
6083
|
+
const code = "code" in g ? g.code : 403;
|
|
6084
|
+
socket.end(`HTTP/1.1 ${code} ${code === 421 ? "Misdirected Request" : "Forbidden"}\r
|
|
6085
|
+
connection: close\r
|
|
6086
|
+
\r
|
|
6087
|
+
`);
|
|
6088
|
+
return;
|
|
6089
|
+
}
|
|
6090
|
+
l.lastUsed = this.now();
|
|
6091
|
+
socket.on("error", () => {
|
|
6092
|
+
});
|
|
6093
|
+
void this.resolveAddr(l).then((addr) => {
|
|
6094
|
+
if (!addr || socket.destroyed) {
|
|
6095
|
+
socket.end("HTTP/1.1 502 Bad Gateway\r\nconnection: close\r\n\r\n");
|
|
6096
|
+
return;
|
|
6097
|
+
}
|
|
6098
|
+
const up = l.target.secure ? tlsConnect({ host: addr, port: l.target.port, rejectUnauthorized: false, servername: this.servername(l) }) : netConnect({ host: addr, port: l.target.port });
|
|
6099
|
+
const ready = l.target.secure ? "secureConnect" : "connect";
|
|
6100
|
+
l.tunnels++;
|
|
6101
|
+
let counted = true;
|
|
6102
|
+
const done = () => {
|
|
6103
|
+
if (counted) {
|
|
6104
|
+
counted = false;
|
|
6105
|
+
l.tunnels--;
|
|
6106
|
+
l.lastUsed = this.now();
|
|
6107
|
+
}
|
|
6108
|
+
up.destroy();
|
|
6109
|
+
socket.destroy();
|
|
6110
|
+
};
|
|
6111
|
+
up.once(ready, () => {
|
|
6112
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/1.1`];
|
|
6113
|
+
const hs = rewriteRequestHeaders(req.headers, l.target, l.proxyOrigin, true);
|
|
6114
|
+
for (const [k, v] of Object.entries(hs)) {
|
|
6115
|
+
if (v === void 0) continue;
|
|
6116
|
+
for (const one of Array.isArray(v) ? v : [v]) lines.push(`${k}: ${String(one).replace(/[\r\n]/g, "")}`);
|
|
6117
|
+
}
|
|
6118
|
+
up.write(lines.join("\r\n") + "\r\n\r\n");
|
|
6119
|
+
if (head?.length) up.write(head);
|
|
6120
|
+
up.pipe(socket);
|
|
6121
|
+
socket.pipe(up);
|
|
6122
|
+
});
|
|
6123
|
+
up.on("data", () => {
|
|
6124
|
+
l.lastUsed = this.now();
|
|
6125
|
+
});
|
|
6126
|
+
up.on("error", done);
|
|
6127
|
+
up.on("close", done);
|
|
6128
|
+
socket.on("close", done);
|
|
6129
|
+
});
|
|
6130
|
+
}
|
|
6131
|
+
};
|
|
6132
|
+
}
|
|
6133
|
+
});
|
|
6134
|
+
|
|
6135
|
+
// daemon/src/preview/host.ts
|
|
6136
|
+
import os from "node:os";
|
|
6137
|
+
function previewHost(platform = os.platform(), release2 = os.release()) {
|
|
6138
|
+
if (platform !== "darwin") return { platform, darwinMajor: null };
|
|
6139
|
+
const m = /^(\d+)\./.exec(String(release2 ?? "").trim()) ?? /^(\d+)$/.exec(String(release2 ?? "").trim());
|
|
6140
|
+
const major = m ? Number(m[1]) : NaN;
|
|
6141
|
+
return { platform, darwinMajor: Number.isInteger(major) && major > 0 ? major : null };
|
|
6142
|
+
}
|
|
6143
|
+
var init_host = __esm({
|
|
6144
|
+
"daemon/src/preview/host.ts"() {
|
|
6145
|
+
}
|
|
6146
|
+
});
|
|
6147
|
+
|
|
5225
6148
|
// daemon/src/ws/methods.ts
|
|
5226
6149
|
import { z } from "zod";
|
|
5227
|
-
import { existsSync as
|
|
6150
|
+
import { existsSync as existsSync9, statSync as statSync4 } from "node:fs";
|
|
5228
6151
|
import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
5229
6152
|
import { homedir as homedir8 } from "node:os";
|
|
5230
6153
|
import { dirname as dirname3, join as join12, win32 as win32Path } from "node:path";
|
|
5231
|
-
import { execFile as
|
|
6154
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
5232
6155
|
function wrap(schema, fn) {
|
|
5233
6156
|
return (params, ctx) => {
|
|
5234
6157
|
const r = schema.safeParse(params ?? {});
|
|
@@ -5240,6 +6163,10 @@ function wrap(schema, fn) {
|
|
|
5240
6163
|
}
|
|
5241
6164
|
function registerMethods(gw, svc) {
|
|
5242
6165
|
const zEmpty = z.object({}).passthrough().optional();
|
|
6166
|
+
gw.method("app.setLocale", wrap(z.object({ lang: z.string().max(32) }), (p) => {
|
|
6167
|
+
setAppLocale(p.lang);
|
|
6168
|
+
return true;
|
|
6169
|
+
}));
|
|
5243
6170
|
gw.method("app.info", wrap(zEmpty, () => ({
|
|
5244
6171
|
version: svc.cfg.version,
|
|
5245
6172
|
dataDir: svc.cfg.dataDir,
|
|
@@ -5250,6 +6177,33 @@ function registerMethods(gw, svc) {
|
|
|
5250
6177
|
setTimeout(() => process.exit(0), 100);
|
|
5251
6178
|
return { ok: true };
|
|
5252
6179
|
}));
|
|
6180
|
+
gw.method("app.openExternal", wrap(z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL) }), async (p) => openExternal(p.url)));
|
|
6181
|
+
gw.method("preview.open", wrap(
|
|
6182
|
+
z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL), threadId: z.string().max(200).optional() }),
|
|
6183
|
+
async (p) => {
|
|
6184
|
+
if (!svc.preview) throw new ApiError("unavailable", tr("err.previewUnavailable"));
|
|
6185
|
+
let r;
|
|
6186
|
+
try {
|
|
6187
|
+
r = await svc.preview.open(p.url);
|
|
6188
|
+
} catch (e) {
|
|
6189
|
+
if (e instanceof PreviewError) {
|
|
6190
|
+
throw new ApiError(e.code === "denied_port" ? "preview_denied" : "preview_url", tr(e.code === "denied_port" ? "err.previewDenied" : "err.previewUrl"));
|
|
6191
|
+
}
|
|
6192
|
+
throw e;
|
|
6193
|
+
}
|
|
6194
|
+
if (p.threadId) {
|
|
6195
|
+
const meta = await svc.threads.get(p.threadId);
|
|
6196
|
+
if (meta && meta.previewUrl !== r.url) {
|
|
6197
|
+
const next = await svc.threads.patch(p.threadId, { previewUrl: r.url });
|
|
6198
|
+
gw.publish("sidebar", { type: "thread.upsert", thread: next });
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
return { ...r, host: previewHost() };
|
|
6202
|
+
}
|
|
6203
|
+
));
|
|
6204
|
+
gw.method("preview.release", wrap(z.object({ url: z.string().min(1).max(MAX_PREVIEW_URL) }), (p) => ({
|
|
6205
|
+
ok: svc.preview?.release(p.url) ?? false
|
|
6206
|
+
})));
|
|
5253
6207
|
gw.method("settings.get", wrap(zEmpty, () => svc.settings.get()));
|
|
5254
6208
|
gw.method("settings.set", wrap(z.object({ patch: z.record(z.string(), z.unknown()) }), async (p) => {
|
|
5255
6209
|
const checked = parseSettingsPatch(p.patch);
|
|
@@ -5352,10 +6306,10 @@ function registerMethods(gw, svc) {
|
|
|
5352
6306
|
gw.method("projects.open", wrap(z.object({ id: z.string() }), async (p) => {
|
|
5353
6307
|
const proj = await svc.projects.get(p.id);
|
|
5354
6308
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5355
|
-
if (!
|
|
6309
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.dirMissing"));
|
|
5356
6310
|
const cmd = process.platform === "win32" ? "explorer.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
5357
6311
|
const target = process.platform === "win32" ? win32Path.normalize(proj.path) : proj.path;
|
|
5358
|
-
|
|
6312
|
+
execFile4(cmd, [target], { windowsHide: true }, () => {
|
|
5359
6313
|
}).on("error", () => {
|
|
5360
6314
|
});
|
|
5361
6315
|
return { ok: true };
|
|
@@ -5364,7 +6318,7 @@ function registerMethods(gw, svc) {
|
|
|
5364
6318
|
let exists = false;
|
|
5365
6319
|
let isDir2 = false;
|
|
5366
6320
|
try {
|
|
5367
|
-
exists =
|
|
6321
|
+
exists = existsSync9(p.path);
|
|
5368
6322
|
isDir2 = exists && statSync4(p.path).isDirectory();
|
|
5369
6323
|
} catch {
|
|
5370
6324
|
}
|
|
@@ -5393,7 +6347,7 @@ function registerMethods(gw, svc) {
|
|
|
5393
6347
|
async (p) => {
|
|
5394
6348
|
const proj = await svc.projects.get(p.projectId);
|
|
5395
6349
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5396
|
-
if (!
|
|
6350
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
5397
6351
|
if (!p.providerSessionId && !isProviderEnabled(await svc.settings.get(), p.provider)) {
|
|
5398
6352
|
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: catalogById(p.provider)?.name ?? p.provider }));
|
|
5399
6353
|
}
|
|
@@ -5429,7 +6383,8 @@ function registerMethods(gw, svc) {
|
|
|
5429
6383
|
id: z.string(),
|
|
5430
6384
|
title: z.string().optional(),
|
|
5431
6385
|
pinned: z.boolean().optional(),
|
|
5432
|
-
archived: z.boolean().optional()
|
|
6386
|
+
archived: z.boolean().optional(),
|
|
6387
|
+
previewUrl: z.string().max(MAX_PREVIEW_URL).nullable().optional()
|
|
5433
6388
|
}),
|
|
5434
6389
|
async (p) => {
|
|
5435
6390
|
const meta = await svc.threads.get(p.id);
|
|
@@ -5441,6 +6396,14 @@ function registerMethods(gw, svc) {
|
|
|
5441
6396
|
}
|
|
5442
6397
|
if (p.pinned !== void 0) patch.pinned = p.pinned;
|
|
5443
6398
|
if (p.archived !== void 0) patch.archived = p.archived;
|
|
6399
|
+
if (p.previewUrl !== void 0) {
|
|
6400
|
+
if (p.previewUrl === null) patch.previewUrl = null;
|
|
6401
|
+
else {
|
|
6402
|
+
const c = checkPreviewUrl(p.previewUrl);
|
|
6403
|
+
if (!c.ok) throw new ApiError("bad_params", tr("err.previewUrl"));
|
|
6404
|
+
patch.previewUrl = c.target.href;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
5444
6407
|
const next = await svc.threads.patch(p.id, patch);
|
|
5445
6408
|
gw.publish("sidebar", { type: "thread.upsert", thread: next });
|
|
5446
6409
|
return next;
|
|
@@ -5457,7 +6420,7 @@ function registerMethods(gw, svc) {
|
|
|
5457
6420
|
const projectPath = async (projectId) => {
|
|
5458
6421
|
const proj = await svc.projects.get(projectId);
|
|
5459
6422
|
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5460
|
-
if (!
|
|
6423
|
+
if (!existsSync9(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
5461
6424
|
return proj.path;
|
|
5462
6425
|
};
|
|
5463
6426
|
gw.method("git.status", wrap(
|
|
@@ -5564,10 +6527,12 @@ function registerMethods(gw, svc) {
|
|
|
5564
6527
|
id: z.string(),
|
|
5565
6528
|
text: z.string(),
|
|
5566
6529
|
attachments: z.array(z.object({ path: z.string(), mime: z.string(), name: z.string().optional() })).optional(),
|
|
6530
|
+
// 1.2: paths dragged from the Files panel, relative to the project (the daemon resolves and checks them)
|
|
6531
|
+
mentions: z.array(z.object({ rel: z.string().min(1).max(4096), isDir: z.boolean().optional() })).max(MAX_MENTIONS).optional(),
|
|
5567
6532
|
behavior: z.enum(["queue", "steer"]).optional()
|
|
5568
6533
|
}),
|
|
5569
6534
|
async (p) => {
|
|
5570
|
-
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments }, p.behavior ?? "queue");
|
|
6535
|
+
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments, mentions: p.mentions }, p.behavior ?? "queue");
|
|
5571
6536
|
return { ok: true, delivered: delivered ?? "started" };
|
|
5572
6537
|
}
|
|
5573
6538
|
));
|
|
@@ -5638,11 +6603,13 @@ function registerMethods(gw, svc) {
|
|
|
5638
6603
|
L.push(`> ${meta.provider} \xB7 ${meta.model ?? tr("export.defaultModel")} \xB7 ${meta.mode} \xB7 ${new Date(meta.createdAt).toLocaleString(intlLocale())}`, "");
|
|
5639
6604
|
for (const e of events) {
|
|
5640
6605
|
switch (e.type) {
|
|
5641
|
-
case "user_message":
|
|
6606
|
+
case "user_message": {
|
|
6607
|
+
const refs = e.mentions?.length ? "\n\n" + e.mentions.map((m) => `- \`${m.path}${m.isDir ? "/" : ""}\``).join("\n") : "";
|
|
5642
6608
|
L.push(`**${tr("export.me")}**${e.steered ? tr("export.steered") : ""}:
|
|
5643
6609
|
|
|
5644
|
-
${e.text}`, "");
|
|
6610
|
+
${e.text}${refs}`, "");
|
|
5645
6611
|
break;
|
|
6612
|
+
}
|
|
5646
6613
|
case "assistant_message":
|
|
5647
6614
|
L.push(e.text, "");
|
|
5648
6615
|
break;
|
|
@@ -5691,7 +6658,7 @@ ${e.text}
|
|
|
5691
6658
|
await mkdir3(dir, { recursive: true });
|
|
5692
6659
|
}
|
|
5693
6660
|
let dest = join12(dir, filename);
|
|
5694
|
-
if (
|
|
6661
|
+
if (existsSync9(dest)) {
|
|
5695
6662
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5696
6663
|
dest = join12(dir, `${safe}-${stamp2}.md`);
|
|
5697
6664
|
}
|
|
@@ -5702,18 +6669,18 @@ ${e.text}
|
|
|
5702
6669
|
}
|
|
5703
6670
|
function revealInFileManager(filePath) {
|
|
5704
6671
|
if (process.platform === "win32") {
|
|
5705
|
-
|
|
6672
|
+
execFile4("explorer.exe", ["/select," + win32Path.normalize(filePath)], { windowsHide: true }, () => {
|
|
5706
6673
|
}).on("error", () => {
|
|
5707
6674
|
});
|
|
5708
6675
|
return;
|
|
5709
6676
|
}
|
|
5710
6677
|
if (process.platform === "darwin") {
|
|
5711
|
-
|
|
6678
|
+
execFile4("open", ["-R", filePath], () => {
|
|
5712
6679
|
}).on("error", () => {
|
|
5713
6680
|
});
|
|
5714
6681
|
return;
|
|
5715
6682
|
}
|
|
5716
|
-
|
|
6683
|
+
execFile4("xdg-open", [dirname3(filePath)], () => {
|
|
5717
6684
|
}).on("error", () => {
|
|
5718
6685
|
});
|
|
5719
6686
|
}
|
|
@@ -5730,6 +6697,11 @@ var init_methods = __esm({
|
|
|
5730
6697
|
init_launch();
|
|
5731
6698
|
init_modes();
|
|
5732
6699
|
init_i18n();
|
|
6700
|
+
init_mentions();
|
|
6701
|
+
init_openExternal();
|
|
6702
|
+
init_proxy();
|
|
6703
|
+
init_url();
|
|
6704
|
+
init_host();
|
|
5733
6705
|
threadModeSchema = z.enum(["plan", "default", "acceptEdits", "bypassPermissions", "ask"]);
|
|
5734
6706
|
ApiError = class extends Error {
|
|
5735
6707
|
code;
|
|
@@ -5790,10 +6762,14 @@ function sanitizeStored(raw) {
|
|
|
5790
6762
|
}
|
|
5791
6763
|
return out;
|
|
5792
6764
|
}
|
|
5793
|
-
var DEFAULT_SETTINGS, IDLE_CLOSE_MIN, IDLE_CLOSE_MAX, mode, id, settingsPatchSchema, SettingsStore;
|
|
6765
|
+
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
6766
|
var init_settings = __esm({
|
|
5795
6767
|
"daemon/src/store/settings.ts"() {
|
|
5796
6768
|
init_atomic();
|
|
6769
|
+
CONTENT_WIDTHS = ["narrow", "default", "wide", "full"];
|
|
6770
|
+
CHAT_FONT_MIN = 12;
|
|
6771
|
+
CHAT_FONT_MAX = 18;
|
|
6772
|
+
CHAT_FONT_DEFAULT = 13;
|
|
5797
6773
|
DEFAULT_SETTINGS = {
|
|
5798
6774
|
defaultProvider: "claude",
|
|
5799
6775
|
defaultMode: "default",
|
|
@@ -5801,6 +6777,8 @@ var init_settings = __esm({
|
|
|
5801
6777
|
runningSend: "queue",
|
|
5802
6778
|
showThinking: true,
|
|
5803
6779
|
theme: "auto",
|
|
6780
|
+
contentWidth: "default",
|
|
6781
|
+
chatFontSize: CHAT_FONT_DEFAULT,
|
|
5804
6782
|
idleCloseMinutes: 30,
|
|
5805
6783
|
collapsedProjects: [],
|
|
5806
6784
|
panel: { open: false, tab: "changes", width: 440 },
|
|
@@ -5821,10 +6799,13 @@ var init_settings = __esm({
|
|
|
5821
6799
|
showThinking: z2.boolean(),
|
|
5822
6800
|
// auto = follow the host: Magpie's light/dark inside Magpie, the OS outside it
|
|
5823
6801
|
theme: z2.enum(["auto", "light", "dark"]),
|
|
6802
|
+
contentWidth: z2.enum(CONTENT_WIDTHS),
|
|
6803
|
+
chatFontSize: z2.number().int().min(CHAT_FONT_MIN).max(CHAT_FONT_MAX),
|
|
5824
6804
|
idleCloseMinutes: z2.number().int().min(IDLE_CLOSE_MIN).max(IDLE_CLOSE_MAX),
|
|
5825
6805
|
collapsedProjects: z2.array(id).max(1e3),
|
|
5826
6806
|
panel: z2.object({ open: z2.boolean(), tab: z2.string().max(40), width: z2.number().min(0).max(5e3) }).partial(),
|
|
5827
6807
|
sidebarWidth: z2.number().min(0).max(5e3),
|
|
6808
|
+
sidebarHidden: z2.boolean(),
|
|
5828
6809
|
lastProjectId: id.nullable(),
|
|
5829
6810
|
lastThreadId: id.nullable(),
|
|
5830
6811
|
recentDirs: z2.array(z2.string().max(4096)).max(50),
|
|
@@ -5919,8 +6900,8 @@ __export(legacy_exports, {
|
|
|
5919
6900
|
migrateLegacy: () => migrateLegacy
|
|
5920
6901
|
});
|
|
5921
6902
|
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
|
|
6903
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
6904
|
+
import { join as join20 } from "node:path";
|
|
5924
6905
|
function stamp() {
|
|
5925
6906
|
const d = /* @__PURE__ */ new Date();
|
|
5926
6907
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -5929,19 +6910,19 @@ function stamp() {
|
|
|
5929
6910
|
async function migrateLegacy(dataDir, stores) {
|
|
5930
6911
|
const settings = await stores.settings.get();
|
|
5931
6912
|
if (settings.v1Migrated) return { migrated: 0, skipped: true };
|
|
5932
|
-
const oldDir =
|
|
5933
|
-
const oldSettingsPath =
|
|
5934
|
-
if (!
|
|
6913
|
+
const oldDir = join20(dataDir, "sessions");
|
|
6914
|
+
const oldSettingsPath = join20(dataDir, "settings.json");
|
|
6915
|
+
if (!existsSync13(oldDir) && !existsSync13(oldSettingsPath)) {
|
|
5935
6916
|
await stores.settings.set({ v1Migrated: true });
|
|
5936
6917
|
return { migrated: 0, skipped: true };
|
|
5937
6918
|
}
|
|
5938
|
-
const backup =
|
|
6919
|
+
const backup = join20(dataDir, "trash", `legacy-${stamp()}`);
|
|
5939
6920
|
await mkdir8(backup, { recursive: true });
|
|
5940
|
-
if (
|
|
6921
|
+
if (existsSync13(oldDir)) await cp2(oldDir, join20(backup, "sessions"), { recursive: true }).catch(() => {
|
|
5941
6922
|
});
|
|
5942
|
-
if (
|
|
6923
|
+
if (existsSync13(oldSettingsPath)) await cp2(oldSettingsPath, join20(backup, "settings.json")).catch(() => {
|
|
5943
6924
|
});
|
|
5944
|
-
const oldSettings =
|
|
6925
|
+
const oldSettings = existsSync13(oldSettingsPath) ? JSON.parse(await readFile18(oldSettingsPath, "utf8").catch(() => "{}")) : {};
|
|
5945
6926
|
const projectIdByPath = /* @__PURE__ */ new Map();
|
|
5946
6927
|
const lc = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
5947
6928
|
for (const p of oldSettings.projects ?? []) {
|
|
@@ -5955,13 +6936,13 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5955
6936
|
}
|
|
5956
6937
|
let sessionIds = [];
|
|
5957
6938
|
try {
|
|
5958
|
-
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) &&
|
|
6939
|
+
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) && existsSync13(join20(oldDir, d, "meta.json")));
|
|
5959
6940
|
} catch {
|
|
5960
6941
|
}
|
|
5961
6942
|
const metas = [];
|
|
5962
6943
|
for (const id2 of sessionIds) {
|
|
5963
6944
|
try {
|
|
5964
|
-
const m = JSON.parse(await readFile18(
|
|
6945
|
+
const m = JSON.parse(await readFile18(join20(oldDir, id2, "meta.json"), "utf8"));
|
|
5965
6946
|
if (m?.cwd) metas.push(m);
|
|
5966
6947
|
} catch {
|
|
5967
6948
|
}
|
|
@@ -5975,7 +6956,7 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5975
6956
|
extraDirs.push(m.cwd);
|
|
5976
6957
|
}
|
|
5977
6958
|
for (const d of extraDirs) {
|
|
5978
|
-
if (!
|
|
6959
|
+
if (!existsSync13(d)) continue;
|
|
5979
6960
|
try {
|
|
5980
6961
|
const name = d.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? d;
|
|
5981
6962
|
const proj = await stores.projects.create(d, name);
|
|
@@ -5994,7 +6975,7 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5994
6975
|
return best ? projectIdByPath.get(best) : null;
|
|
5995
6976
|
};
|
|
5996
6977
|
let migrated = 0;
|
|
5997
|
-
const threadsDir =
|
|
6978
|
+
const threadsDir = join20(dataDir, "threads");
|
|
5998
6979
|
for (const m of metas) {
|
|
5999
6980
|
try {
|
|
6000
6981
|
const projectId = projectOf(m.cwd);
|
|
@@ -6031,10 +7012,10 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
6031
7012
|
importedFrom: m.cliSessionId ? { provider, sessionId: m.cliSessionId } : void 0,
|
|
6032
7013
|
seq: 0
|
|
6033
7014
|
};
|
|
6034
|
-
const tdir =
|
|
6035
|
-
if (
|
|
6036
|
-
await mkdir8(
|
|
6037
|
-
const oldEvts = (await readFile18(
|
|
7015
|
+
const tdir = join20(threadsDir, meta.id);
|
|
7016
|
+
if (existsSync13(tdir)) meta.id = `t_${m.id}_v1`.slice(0, 80);
|
|
7017
|
+
await mkdir8(join20(threadsDir, meta.id), { recursive: true });
|
|
7018
|
+
const oldEvts = (await readFile18(join20(oldDir, m.id, "events.jsonl"), "utf8").catch(() => "")).split("\n").filter((l) => l.trim()).map((l) => {
|
|
6038
7019
|
try {
|
|
6039
7020
|
return JSON.parse(l);
|
|
6040
7021
|
} catch {
|
|
@@ -6113,9 +7094,9 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
6113
7094
|
seq++;
|
|
6114
7095
|
lines.push({ type: "tool_call", itemId: key, kind: "other", title: o.title, status: "failed", seq, ts: Date.now(), threadId: meta.id });
|
|
6115
7096
|
}
|
|
6116
|
-
for (const ev of lines) await appendJsonl(
|
|
7097
|
+
for (const ev of lines) await appendJsonl(join20(threadsDir, meta.id, "timeline.jsonl"), ev);
|
|
6117
7098
|
meta.seq = seq;
|
|
6118
|
-
await writeJsonAtomic(
|
|
7099
|
+
await writeJsonAtomic(join20(threadsDir, meta.id, "meta.json"), meta);
|
|
6119
7100
|
migrated++;
|
|
6120
7101
|
} catch (e) {
|
|
6121
7102
|
log("warn", "\u8FC1\u79FB\uFF1A\u4F1A\u8BDD\u5931\u8D25", { id: m.id, err: String(e) });
|
|
@@ -6180,8 +7161,8 @@ var init_warm = __esm({
|
|
|
6180
7161
|
|
|
6181
7162
|
// daemon/src/main.ts
|
|
6182
7163
|
import { mkdirSync as mkdirSync2, rmSync as rmSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
6183
|
-
import { join as
|
|
6184
|
-
import { fileURLToPath as
|
|
7164
|
+
import { join as join21, dirname as dirname8 } from "node:path";
|
|
7165
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6185
7166
|
|
|
6186
7167
|
// daemon/src/config.ts
|
|
6187
7168
|
import { randomBytes } from "node:crypto";
|
|
@@ -6236,6 +7217,7 @@ function parseConfig(argv) {
|
|
|
6236
7217
|
let data = null;
|
|
6237
7218
|
let token = null;
|
|
6238
7219
|
let parentPid;
|
|
7220
|
+
let gatePort;
|
|
6239
7221
|
for (let i = 0; i < argv.length; i++) {
|
|
6240
7222
|
const a = argv[i];
|
|
6241
7223
|
if (a === "--port" && argv[i + 1]) port = Number(argv[++i]);
|
|
@@ -6246,6 +7228,8 @@ function parseConfig(argv) {
|
|
|
6246
7228
|
else if (a.startsWith("--token=")) token = a.slice(8);
|
|
6247
7229
|
else if (a === "--parent-pid" && argv[i + 1]) parentPid = Number(argv[++i]);
|
|
6248
7230
|
else if (a.startsWith("--parent-pid=")) parentPid = Number(a.slice(13));
|
|
7231
|
+
else if (a === "--gate-port" && argv[i + 1]) gatePort = Number(argv[++i]);
|
|
7232
|
+
else if (a.startsWith("--gate-port=")) gatePort = Number(a.slice(12));
|
|
6249
7233
|
}
|
|
6250
7234
|
if (port == null || !Number.isInteger(port) || port < 0 || port > 65535) {
|
|
6251
7235
|
const envPort = Number(process.env.AGENT_CONSOLE_PORT || "");
|
|
@@ -6261,7 +7245,8 @@ function parseConfig(argv) {
|
|
|
6261
7245
|
version: pkgVersion(),
|
|
6262
7246
|
idleExitMinutes: Number.isFinite(idleEnv) && idleEnv > 0 ? idleEnv : 60,
|
|
6263
7247
|
buildStamp: daemonBuildStamp(),
|
|
6264
|
-
parentPid: Number.isInteger(parentPid) ? parentPid : void 0
|
|
7248
|
+
parentPid: Number.isInteger(parentPid) ? parentPid : void 0,
|
|
7249
|
+
gatePort: Number.isInteger(gatePort) && gatePort > 0 && gatePort <= 65535 ? gatePort : void 0
|
|
6265
7250
|
};
|
|
6266
7251
|
}
|
|
6267
7252
|
|
|
@@ -6269,35 +7254,13 @@ function parseConfig(argv) {
|
|
|
6269
7254
|
init_log();
|
|
6270
7255
|
|
|
6271
7256
|
// daemon/src/http.ts
|
|
7257
|
+
init_security();
|
|
7258
|
+
init_log();
|
|
7259
|
+
init_i18n();
|
|
6272
7260
|
import { createServer } from "node:http";
|
|
6273
7261
|
import { readFile, stat } from "node:fs/promises";
|
|
6274
7262
|
import { existsSync as existsSync2 } from "node:fs";
|
|
6275
7263
|
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
7264
|
var MIME = {
|
|
6302
7265
|
".html": "text/html; charset=utf-8",
|
|
6303
7266
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -6559,11 +7522,11 @@ init_atomic();
|
|
|
6559
7522
|
init_paths();
|
|
6560
7523
|
init_methods();
|
|
6561
7524
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
6562
|
-
import { existsSync as
|
|
7525
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
|
|
6563
7526
|
import { join as join14 } from "node:path";
|
|
6564
|
-
import { randomBytes as
|
|
7527
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
6565
7528
|
function newId() {
|
|
6566
|
-
return "p_" + Date.now().toString(36) +
|
|
7529
|
+
return "p_" + Date.now().toString(36) + randomBytes4(3).toString("hex");
|
|
6567
7530
|
}
|
|
6568
7531
|
var ProjectsStore = class {
|
|
6569
7532
|
file;
|
|
@@ -6594,7 +7557,7 @@ var ProjectsStore = class {
|
|
|
6594
7557
|
}
|
|
6595
7558
|
async create(path3, name) {
|
|
6596
7559
|
if (!path3 || typeof path3 !== "string") throw new ApiError("bad_params", "\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A");
|
|
6597
|
-
if (!
|
|
7560
|
+
if (!existsSync10(path3) || !statSync5(path3).isDirectory()) {
|
|
6598
7561
|
throw new ApiError("bad_path", "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939");
|
|
6599
7562
|
}
|
|
6600
7563
|
const np = normPath(path3);
|
|
@@ -6636,9 +7599,9 @@ init_methods();
|
|
|
6636
7599
|
init_i18n();
|
|
6637
7600
|
import { mkdir as mkdir6, rename as rename3, readdir as readdir4, readFile as readFile15 } from "node:fs/promises";
|
|
6638
7601
|
import { join as join15 } from "node:path";
|
|
6639
|
-
import { randomBytes as
|
|
7602
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
6640
7603
|
function newId2() {
|
|
6641
|
-
return "t_" + Date.now().toString(36) +
|
|
7604
|
+
return "t_" + Date.now().toString(36) + randomBytes5(3).toString("hex");
|
|
6642
7605
|
}
|
|
6643
7606
|
var ThreadsStore = class {
|
|
6644
7607
|
dir;
|
|
@@ -6784,10 +7747,11 @@ var ThreadsStore = class {
|
|
|
6784
7747
|
};
|
|
6785
7748
|
|
|
6786
7749
|
// daemon/src/ws/gateway.ts
|
|
6787
|
-
|
|
6788
|
-
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
7750
|
+
init_security();
|
|
6789
7751
|
init_log();
|
|
6790
7752
|
init_i18n();
|
|
7753
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
7754
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
6791
7755
|
var Conn = class {
|
|
6792
7756
|
id;
|
|
6793
7757
|
ws;
|
|
@@ -6796,7 +7760,7 @@ var Conn = class {
|
|
|
6796
7760
|
alive = true;
|
|
6797
7761
|
lastMsg = Date.now();
|
|
6798
7762
|
constructor(ws) {
|
|
6799
|
-
this.id = "c_" +
|
|
7763
|
+
this.id = "c_" + randomBytes6(4).toString("hex");
|
|
6800
7764
|
this.ws = ws;
|
|
6801
7765
|
}
|
|
6802
7766
|
send(obj) {
|
|
@@ -6840,6 +7804,8 @@ var Gateway = class {
|
|
|
6840
7804
|
methods = /* @__PURE__ */ new Map();
|
|
6841
7805
|
helloTimers = /* @__PURE__ */ new Map();
|
|
6842
7806
|
onTerminalInput;
|
|
7807
|
+
/** Called after a connection closes, with how many are left (the preview proxy closes when none are) */
|
|
7808
|
+
onConnectionClosed;
|
|
6843
7809
|
method(name, fn) {
|
|
6844
7810
|
this.methods.set(name, fn);
|
|
6845
7811
|
}
|
|
@@ -6890,6 +7856,10 @@ var Gateway = class {
|
|
|
6890
7856
|
});
|
|
6891
7857
|
ws.on("close", () => {
|
|
6892
7858
|
this.conns.delete(conn.id);
|
|
7859
|
+
try {
|
|
7860
|
+
this.onConnectionClosed?.(this.conns.size);
|
|
7861
|
+
} catch {
|
|
7862
|
+
}
|
|
6893
7863
|
const t = this.helloTimers.get(conn.id);
|
|
6894
7864
|
if (t) clearTimeout(t);
|
|
6895
7865
|
this.helloTimers.delete(conn.id);
|
|
@@ -7037,6 +8007,7 @@ var Coalescer = class {
|
|
|
7037
8007
|
init_methods();
|
|
7038
8008
|
init_log();
|
|
7039
8009
|
init_i18n();
|
|
8010
|
+
init_mentions();
|
|
7040
8011
|
init_commands();
|
|
7041
8012
|
var SLASH_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
7042
8013
|
var MAX_WARM_IDLE = 3;
|
|
@@ -7062,6 +8033,17 @@ var AgentManager = class {
|
|
|
7062
8033
|
resolveAttachments(refs) {
|
|
7063
8034
|
return resolveAttachmentRefs(this.attachments, refs);
|
|
7064
8035
|
}
|
|
8036
|
+
/** Mentions from the Files panel → absolute paths inside the project; a path that can't be used gets a notice */
|
|
8037
|
+
async resolveMentionRefs(threadId, projectId, refs) {
|
|
8038
|
+
if (!refs?.length) return [];
|
|
8039
|
+
const proj = await this.projects.get(projectId);
|
|
8040
|
+
if (!proj) return [];
|
|
8041
|
+
const { mentions, warnings } = await resolveMentions(proj.path, refs);
|
|
8042
|
+
if (warnings.length) {
|
|
8043
|
+
await this.appendRaw(threadId, { type: "notice", level: "warn", message: tr("notice.mentionSkipped", { paths: warnings.join(", ") }) });
|
|
8044
|
+
}
|
|
8045
|
+
return mentions;
|
|
8046
|
+
}
|
|
7065
8047
|
attachmentLabels(refs) {
|
|
7066
8048
|
return refs.map((a) => ({ id: a.path, name: (a.name ?? "").trim() || a.path, mime: a.mime }));
|
|
7067
8049
|
}
|
|
@@ -7179,6 +8161,7 @@ var AgentManager = class {
|
|
|
7179
8161
|
// ---- send ----
|
|
7180
8162
|
async send(threadId, input, behavior = "queue") {
|
|
7181
8163
|
const meta = await this.mustThread(threadId);
|
|
8164
|
+
const mentions = await this.resolveMentionRefs(threadId, meta.projectId, input.mentions);
|
|
7182
8165
|
if (meta.archived) {
|
|
7183
8166
|
await this.threads.patch(threadId, { archived: false });
|
|
7184
8167
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
@@ -7202,36 +8185,37 @@ var AgentManager = class {
|
|
|
7202
8185
|
if (behavior === "steer" && provider?.capabilities.steer && rt.session?.steer && !rt.starting) {
|
|
7203
8186
|
try {
|
|
7204
8187
|
const images = await this.resolveAttachments(attachmentRefs);
|
|
7205
|
-
await rt.session.steer({ text: input.text, images });
|
|
8188
|
+
await rt.session.steer({ text: withMentions(input.text, mentions), images, mentions });
|
|
7206
8189
|
await this.appendRaw(threadId, {
|
|
7207
8190
|
type: "user_message",
|
|
7208
8191
|
text: input.text,
|
|
7209
8192
|
steered: true,
|
|
7210
|
-
attachments: this.attachmentLabels(attachmentRefs)
|
|
8193
|
+
attachments: this.attachmentLabels(attachmentRefs),
|
|
8194
|
+
...mentions.length ? { mentions } : {}
|
|
7211
8195
|
});
|
|
7212
8196
|
return "steered";
|
|
7213
8197
|
} catch (e) {
|
|
7214
8198
|
log("info", `thread ${threadId} \u63D2\u8BDD\u5931\u8D25\uFF0C\u6539\u4E3A\u6392\u961F\uFF1A${String(e)}`);
|
|
7215
8199
|
}
|
|
7216
8200
|
}
|
|
7217
|
-
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs };
|
|
8201
|
+
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs, ...mentions.length ? { mentions } : {} };
|
|
7218
8202
|
rt.queue.push(q);
|
|
7219
8203
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
7220
8204
|
return "queued";
|
|
7221
8205
|
}
|
|
7222
|
-
await this.startTurn(threadId, input.text, attachmentRefs);
|
|
8206
|
+
await this.startTurn(threadId, input.text, attachmentRefs, mentions);
|
|
7223
8207
|
return "started";
|
|
7224
8208
|
}
|
|
7225
|
-
async startTurn(threadId, text, attachmentRefs) {
|
|
8209
|
+
async startTurn(threadId, text, attachmentRefs, mentions = []) {
|
|
7226
8210
|
const rt = this.rt(threadId);
|
|
7227
8211
|
rt.starting = true;
|
|
7228
8212
|
try {
|
|
7229
|
-
await this.startTurnInner(threadId, text, attachmentRefs);
|
|
8213
|
+
await this.startTurnInner(threadId, text, attachmentRefs, mentions);
|
|
7230
8214
|
} finally {
|
|
7231
8215
|
rt.starting = false;
|
|
7232
8216
|
}
|
|
7233
8217
|
}
|
|
7234
|
-
async startTurnInner(threadId, text, attachmentRefs) {
|
|
8218
|
+
async startTurnInner(threadId, text, attachmentRefs, mentions) {
|
|
7235
8219
|
const meta = await this.mustThread(threadId);
|
|
7236
8220
|
const rt = this.rt(threadId);
|
|
7237
8221
|
const provider = await this.assertProviderUsable(meta.provider, { allowDisabled: !!meta.providerSessionId });
|
|
@@ -7254,12 +8238,13 @@ var AgentManager = class {
|
|
|
7254
8238
|
await this.appendRaw(threadId, {
|
|
7255
8239
|
type: "user_message",
|
|
7256
8240
|
text,
|
|
7257
|
-
attachments: this.attachmentLabels(attachmentRefs)
|
|
8241
|
+
attachments: this.attachmentLabels(attachmentRefs),
|
|
8242
|
+
...mentions.length ? { mentions } : {}
|
|
7258
8243
|
});
|
|
7259
8244
|
await this.setStatus(threadId, "running");
|
|
7260
8245
|
try {
|
|
7261
8246
|
const images = await this.resolveAttachments(attachmentRefs);
|
|
7262
|
-
await rt.session.send({ text, images });
|
|
8247
|
+
await rt.session.send({ text: withMentions(text, mentions), images, mentions });
|
|
7263
8248
|
} catch (e) {
|
|
7264
8249
|
const msg = String(e?.message ?? e);
|
|
7265
8250
|
await this.appendRaw(threadId, { type: "notice", level: "error", message: tr("notice.sendFailed", { detail: msg }) });
|
|
@@ -7390,7 +8375,7 @@ var AgentManager = class {
|
|
|
7390
8375
|
});
|
|
7391
8376
|
if (next) {
|
|
7392
8377
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
7393
|
-
await this.startTurn(threadId, next.text, next.attachments).catch((e) => {
|
|
8378
|
+
await this.startTurn(threadId, next.text, next.attachments, next.mentions ?? []).catch((e) => {
|
|
7394
8379
|
log("warn", `thread ${threadId} \u6392\u961F\u6D88\u606F\u542F\u52A8\u5931\u8D25\uFF1A${String(e)}`);
|
|
7395
8380
|
});
|
|
7396
8381
|
}
|
|
@@ -7755,15 +8740,15 @@ var AgentManager = class {
|
|
|
7755
8740
|
|
|
7756
8741
|
// daemon/src/git/service.ts
|
|
7757
8742
|
init_i18n();
|
|
7758
|
-
import { execFile as
|
|
7759
|
-
import { existsSync as
|
|
7760
|
-
import { cp, mkdir as mkdir7, readFile as readFile16, rename as rename4, rm, stat as
|
|
7761
|
-
import { join as join16, normalize as normalize2, sep as
|
|
8743
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
8744
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
8745
|
+
import { cp, mkdir as mkdir7, readFile as readFile16, rename as rename4, rm, stat as stat10 } from "node:fs/promises";
|
|
8746
|
+
import { join as join16, normalize as normalize2, sep as sep3 } from "node:path";
|
|
7762
8747
|
var TIMEOUT2 = 15e3;
|
|
7763
8748
|
var DIFF_LIMIT = 1024 * 1024;
|
|
7764
8749
|
function runGit(cwd, args) {
|
|
7765
8750
|
return new Promise((res) => {
|
|
7766
|
-
|
|
8751
|
+
execFile5("git", ["-c", "core.quotepath=false", ...args], { cwd, timeout: TIMEOUT2, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (e, stdout, stderr) => {
|
|
7767
8752
|
const code = e && typeof e.code === "number" ? e.code : e ? 1 : 0;
|
|
7768
8753
|
const missing = e?.code === "ENOENT";
|
|
7769
8754
|
res({ code, out: String(stdout ?? ""), err: missing ? tr("git.missing") : String(stderr || (e?.message ?? "")) });
|
|
@@ -7774,7 +8759,7 @@ function insidePath(projectPath, p) {
|
|
|
7774
8759
|
const rel = normalize2(p).replace(/\\/g, "/");
|
|
7775
8760
|
const abs = normalize2(join16(projectPath, rel));
|
|
7776
8761
|
const proj = normalize2(projectPath);
|
|
7777
|
-
if (abs !== proj && !abs.startsWith(proj +
|
|
8762
|
+
if (abs !== proj && !abs.startsWith(proj + sep3)) return null;
|
|
7778
8763
|
return rel.replace(/\\/g, "/");
|
|
7779
8764
|
}
|
|
7780
8765
|
async function moveAcrossDevices(from, to, ops = {}) {
|
|
@@ -7822,8 +8807,8 @@ var GitService = class {
|
|
|
7822
8807
|
const ls = await runGit(projectPath, ["ls-files", "--error-unmatch", "--", rel]);
|
|
7823
8808
|
if (ls.code !== 0) {
|
|
7824
8809
|
const full = join16(projectPath, rel);
|
|
7825
|
-
if (!
|
|
7826
|
-
const s = await
|
|
8810
|
+
if (!existsSync11(full)) return { error: tr("fs.fileMissing") };
|
|
8811
|
+
const s = await stat10(full);
|
|
7827
8812
|
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
7828
8813
|
if (s.size > DIFF_LIMIT) return { tooLarge: true };
|
|
7829
8814
|
const text = await readFile16(full, "utf8").catch(() => null);
|
|
@@ -7856,7 +8841,7 @@ ${lines}` };
|
|
|
7856
8841
|
return { ok: true };
|
|
7857
8842
|
}
|
|
7858
8843
|
const full = join16(projectPath, rel);
|
|
7859
|
-
if (!
|
|
8844
|
+
if (!existsSync11(full)) return { ok: false, error: tr("fs.fileMissing") };
|
|
7860
8845
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
7861
8846
|
const dest = join16(dataDir, "trash", "discarded", stamp2, rel);
|
|
7862
8847
|
await mkdir7(join16(dest, ".."), { recursive: true });
|
|
@@ -7871,8 +8856,8 @@ ${lines}` };
|
|
|
7871
8856
|
|
|
7872
8857
|
// daemon/src/files/service.ts
|
|
7873
8858
|
init_i18n();
|
|
7874
|
-
import { readdir as readdir5, readFile as readFile17, stat as
|
|
7875
|
-
import { join as join17, normalize as normalize3, sep as
|
|
8859
|
+
import { readdir as readdir5, readFile as readFile17, stat as stat11 } from "node:fs/promises";
|
|
8860
|
+
import { join as join17, normalize as normalize3, sep as sep4, extname as extname3 } from "node:path";
|
|
7876
8861
|
import ignore from "ignore";
|
|
7877
8862
|
function decodeText(buf) {
|
|
7878
8863
|
try {
|
|
@@ -7937,7 +8922,7 @@ function insidePath2(projectPath, p) {
|
|
|
7937
8922
|
const rel = normalize3(p || ".").replace(/\\/g, "/");
|
|
7938
8923
|
const abs = normalize3(join17(projectPath, rel));
|
|
7939
8924
|
const proj = normalize3(projectPath);
|
|
7940
|
-
if (abs !== proj && !abs.startsWith(proj +
|
|
8925
|
+
if (abs !== proj && !abs.startsWith(proj + sep4)) return null;
|
|
7941
8926
|
return rel;
|
|
7942
8927
|
}
|
|
7943
8928
|
var FileService = class {
|
|
@@ -7946,7 +8931,7 @@ var FileService = class {
|
|
|
7946
8931
|
igCache = /* @__PURE__ */ new Map();
|
|
7947
8932
|
async igFor(projectPath) {
|
|
7948
8933
|
const gi = join17(projectPath, ".gitignore");
|
|
7949
|
-
const st = await
|
|
8934
|
+
const st = await stat11(gi).catch(() => null);
|
|
7950
8935
|
const mtime = st?.isFile() ? st.mtimeMs : -1;
|
|
7951
8936
|
const hit = this.igCache.get(projectPath);
|
|
7952
8937
|
if (hit && hit.mtime === mtime) return hit.ig;
|
|
@@ -7965,7 +8950,7 @@ var FileService = class {
|
|
|
7965
8950
|
const rel = insidePath2(projectPath, dir);
|
|
7966
8951
|
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7967
8952
|
const abs = join17(projectPath, rel);
|
|
7968
|
-
const s = await
|
|
8953
|
+
const s = await stat11(abs).catch(() => null);
|
|
7969
8954
|
if (!s?.isDirectory()) return { error: tr("fs.dirMissing") };
|
|
7970
8955
|
const ig = await this.igFor(projectPath);
|
|
7971
8956
|
const dirents = await readdir5(abs, { withFileTypes: true }).catch(() => null);
|
|
@@ -7978,7 +8963,7 @@ var FileService = class {
|
|
|
7978
8963
|
const childRel = rel === "." ? name : `${rel}/${name}`;
|
|
7979
8964
|
let isDir2 = d.isDirectory();
|
|
7980
8965
|
if (d.isSymbolicLink()) {
|
|
7981
|
-
const st = await
|
|
8966
|
+
const st = await stat11(join17(projectPath, childRel)).catch(() => null);
|
|
7982
8967
|
if (!st) continue;
|
|
7983
8968
|
isDir2 = st.isDirectory();
|
|
7984
8969
|
} else if (!d.isFile() && !isDir2) {
|
|
@@ -7996,7 +8981,7 @@ var FileService = class {
|
|
|
7996
8981
|
const rel = insidePath2(projectPath, p);
|
|
7997
8982
|
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7998
8983
|
const abs = join17(projectPath, rel);
|
|
7999
|
-
const s = await
|
|
8984
|
+
const s = await stat11(abs).catch(() => null);
|
|
8000
8985
|
if (!s) return { error: tr("fs.fileMissing") };
|
|
8001
8986
|
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
8002
8987
|
if (s.size > MAX_READ) return { tooLarge: true };
|
|
@@ -8014,7 +8999,7 @@ init_pathscan();
|
|
|
8014
8999
|
init_log();
|
|
8015
9000
|
init_i18n();
|
|
8016
9001
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "node:child_process";
|
|
8017
|
-
import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as
|
|
9002
|
+
import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as existsSync12, statSync as statSync6 } from "node:fs";
|
|
8018
9003
|
import { createRequire as moduleRequire } from "node:module";
|
|
8019
9004
|
import { release } from "node:os";
|
|
8020
9005
|
import { dirname as dirname6, join as join18 } from "node:path";
|
|
@@ -8064,7 +9049,7 @@ function fixSpawnHelper() {
|
|
|
8064
9049
|
const root = dirname6(req.resolve("node-pty/package.json"));
|
|
8065
9050
|
for (const sub of [`prebuilds/darwin-${process.arch}`, "build/Release"]) {
|
|
8066
9051
|
const f = join18(root, sub, "spawn-helper");
|
|
8067
|
-
if (
|
|
9052
|
+
if (existsSync12(f) && !isExecFile(f)) {
|
|
8068
9053
|
chmodSync(f, 493);
|
|
8069
9054
|
log("info", "\u5DF2\u4FEE\u590D node-pty spawn-helper \u7684\u53EF\u6267\u884C\u4F4D", { file: f });
|
|
8070
9055
|
}
|
|
@@ -8296,9 +9281,9 @@ function pidAlive(pid, kill = (p, s) => process.kill(p, s)) {
|
|
|
8296
9281
|
}
|
|
8297
9282
|
function augmentPath(env, nodeDir, platform = process.platform, home) {
|
|
8298
9283
|
const win = platform === "win32";
|
|
8299
|
-
const
|
|
9284
|
+
const sep5 = win ? ";" : platform === process.platform ? delimiter : ":";
|
|
8300
9285
|
const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
|
|
8301
|
-
const cur = (env[key] ?? "").split(
|
|
9286
|
+
const cur = (env[key] ?? "").split(sep5).filter(Boolean);
|
|
8302
9287
|
const norm = (d) => win ? d.replace(/[\\/]+$/, "").toLowerCase() : d.replace(/\/+$/, "");
|
|
8303
9288
|
const seen = new Set(cur.map(norm));
|
|
8304
9289
|
const out = [...cur];
|
|
@@ -8307,7 +9292,87 @@ function augmentPath(env, nodeDir, platform = process.platform, home) {
|
|
|
8307
9292
|
seen.add(norm(d));
|
|
8308
9293
|
out.push(d);
|
|
8309
9294
|
}
|
|
8310
|
-
return out.join(
|
|
9295
|
+
return out.join(sep5);
|
|
9296
|
+
}
|
|
9297
|
+
|
|
9298
|
+
// daemon/src/main.ts
|
|
9299
|
+
init_proxy();
|
|
9300
|
+
|
|
9301
|
+
// daemon/src/preview/ports.ts
|
|
9302
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
9303
|
+
import { homedir as homedir9 } from "node:os";
|
|
9304
|
+
import { basename as basename5, dirname as dirname7, isAbsolute as isAbsolute3, join as join19 } from "node:path";
|
|
9305
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9306
|
+
var MAGPIE_DEFAULT_PORTS = [3425, 3430];
|
|
9307
|
+
var GATE_DEFAULT_PORT = 3488;
|
|
9308
|
+
function portOf(addr) {
|
|
9309
|
+
if (!addr) return null;
|
|
9310
|
+
const m = /:(\d{1,5})\/?$/.exec(addr.trim());
|
|
9311
|
+
const n = m ? Number(m[1]) : NaN;
|
|
9312
|
+
return Number.isInteger(n) && n > 0 && n <= 65535 ? n : null;
|
|
9313
|
+
}
|
|
9314
|
+
function magpieDirFromInstall(p) {
|
|
9315
|
+
const norm = p.replace(/\\/g, "/");
|
|
9316
|
+
const i = norm.lastIndexOf("/plugins/node_modules/");
|
|
9317
|
+
if (i <= 0) return null;
|
|
9318
|
+
return p.slice(0, i);
|
|
9319
|
+
}
|
|
9320
|
+
function magpieConfigDirs(env = process.env, where = {}) {
|
|
9321
|
+
const out = [];
|
|
9322
|
+
const add = (d) => {
|
|
9323
|
+
if (d && isAbsolute3(d) && !out.includes(d)) out.push(d);
|
|
9324
|
+
};
|
|
9325
|
+
let self = where.self;
|
|
9326
|
+
if (self === void 0) {
|
|
9327
|
+
try {
|
|
9328
|
+
self = fileURLToPath3(import.meta.url);
|
|
9329
|
+
} catch {
|
|
9330
|
+
self = "";
|
|
9331
|
+
}
|
|
9332
|
+
}
|
|
9333
|
+
add(magpieDirFromInstall(self));
|
|
9334
|
+
const cwd = where.cwd ?? (env === process.env ? process.cwd() : "");
|
|
9335
|
+
if (cwd && basename5(cwd) === "plugin-work") add(dirname7(cwd));
|
|
9336
|
+
const x = env.XDG_CONFIG_HOME;
|
|
9337
|
+
if (x && isAbsolute3(x)) add(join19(x, "magpie"));
|
|
9338
|
+
const platform = where.platform ?? process.platform;
|
|
9339
|
+
const home = where.home ?? (env === process.env ? homedir9() : platform === "win32" ? env.USERPROFILE : env.HOME);
|
|
9340
|
+
if (home) add(join19(home, ".config", "magpie"));
|
|
9341
|
+
return out;
|
|
9342
|
+
}
|
|
9343
|
+
function savedGatewayPort(file, read = (f) => readFileSync2(f, "utf8")) {
|
|
9344
|
+
let raw;
|
|
9345
|
+
try {
|
|
9346
|
+
raw = read(file);
|
|
9347
|
+
} catch {
|
|
9348
|
+
return null;
|
|
9349
|
+
}
|
|
9350
|
+
try {
|
|
9351
|
+
const s = JSON.parse(raw);
|
|
9352
|
+
const p = typeof s?.port === "number" ? s.port : 0;
|
|
9353
|
+
return Number.isInteger(p) && p >= 1024 && p <= 65535 ? p : MAGPIE_DEFAULT_PORTS[0];
|
|
9354
|
+
} catch {
|
|
9355
|
+
return null;
|
|
9356
|
+
}
|
|
9357
|
+
}
|
|
9358
|
+
function previewDeniedPorts(cfg2, env = process.env, opts = {}) {
|
|
9359
|
+
const out = /* @__PURE__ */ new Set([cfg2.port, GATE_DEFAULT_PORT, ...MAGPIE_DEFAULT_PORTS]);
|
|
9360
|
+
if (cfg2.gatePort) out.add(cfg2.gatePort);
|
|
9361
|
+
for (const k of ["MAGPIE_ADDR", "MAGPIE_WEB_ADDR", "MAGPIE_URL"]) {
|
|
9362
|
+
const p = portOf(env[k]);
|
|
9363
|
+
if (p) out.add(p);
|
|
9364
|
+
}
|
|
9365
|
+
for (const d of opts.configDirs ?? magpieConfigDirs(env)) {
|
|
9366
|
+
const p = savedGatewayPort(join19(d, "settings.json"), opts.read);
|
|
9367
|
+
if (p) out.add(p);
|
|
9368
|
+
}
|
|
9369
|
+
const extra = env.AGENT_CONSOLE_PREVIEW_DENY;
|
|
9370
|
+
if (extra) for (const s of extra.split(/[,\s]+/)) {
|
|
9371
|
+
const n = Number(s);
|
|
9372
|
+
if (Number.isInteger(n) && n > 0 && n <= 65535) out.add(n);
|
|
9373
|
+
}
|
|
9374
|
+
out.delete(0);
|
|
9375
|
+
return [...out];
|
|
8311
9376
|
}
|
|
8312
9377
|
|
|
8313
9378
|
// daemon/src/main.ts
|
|
@@ -8316,7 +9381,7 @@ if (nodeMajor < 20) {
|
|
|
8316
9381
|
console.error(`[agent-console] \u9700\u8981 Node.js 20 \u6216\u66F4\u9AD8\u7248\u672C\uFF0C\u5F53\u524D ${process.version}\uFF08${process.execPath}\uFF09`);
|
|
8317
9382
|
process.exit(3);
|
|
8318
9383
|
}
|
|
8319
|
-
process.env.PATH = augmentPath(process.env,
|
|
9384
|
+
process.env.PATH = augmentPath(process.env, dirname8(process.execPath));
|
|
8320
9385
|
var cfg = parseConfig(process.argv.slice(2));
|
|
8321
9386
|
process.on("uncaughtException", (e) => {
|
|
8322
9387
|
if (e?.code === "EPIPE") return;
|
|
@@ -8328,10 +9393,10 @@ process.on("unhandledRejection", (e) => {
|
|
|
8328
9393
|
async function main() {
|
|
8329
9394
|
initLog(cfg.dataDir);
|
|
8330
9395
|
for (const d of ["threads", "attachments", "trash", "logs"]) {
|
|
8331
|
-
mkdirSync2(
|
|
9396
|
+
mkdirSync2(join21(cfg.dataDir, d), { recursive: true });
|
|
8332
9397
|
}
|
|
8333
|
-
const appDir =
|
|
8334
|
-
const attachments = new AttachmentStore(
|
|
9398
|
+
const appDir = join21(dirname8(fileURLToPath4(import.meta.url)), "..", "..", "app");
|
|
9399
|
+
const attachments = new AttachmentStore(join21(cfg.dataDir, "attachments"));
|
|
8335
9400
|
const { bindAttachmentStore: bindAttachmentStore2 } = await Promise.resolve().then(() => (init_attachmentRefs(), attachmentRefs_exports));
|
|
8336
9401
|
bindAttachmentStore2(attachments);
|
|
8337
9402
|
const settings = new SettingsStore(cfg.dataDir);
|
|
@@ -8348,6 +9413,13 @@ async function main() {
|
|
|
8348
9413
|
const manager = new AgentManager(gateway, threads, projects, settings, attachments);
|
|
8349
9414
|
await manager.recoverOnStart();
|
|
8350
9415
|
const terminals = new TerminalService(gateway);
|
|
9416
|
+
const preview = new PreviewProxy({
|
|
9417
|
+
deniedPorts: () => previewDeniedPorts(cfg),
|
|
9418
|
+
log: (level, msg) => log(level, msg)
|
|
9419
|
+
});
|
|
9420
|
+
gateway.onConnectionClosed = (left) => {
|
|
9421
|
+
if (left === 0) preview.closeAll();
|
|
9422
|
+
};
|
|
8351
9423
|
registerMethods(gateway, {
|
|
8352
9424
|
cfg,
|
|
8353
9425
|
settings,
|
|
@@ -8358,6 +9430,7 @@ async function main() {
|
|
|
8358
9430
|
git: new GitService(),
|
|
8359
9431
|
files: new FileService(),
|
|
8360
9432
|
terminals,
|
|
9433
|
+
preview,
|
|
8361
9434
|
runtime: (id2) => manager.runtimeInfo(id2)
|
|
8362
9435
|
});
|
|
8363
9436
|
const server = createHttpServer(cfg, appDir, attachments, {
|
|
@@ -8390,7 +9463,7 @@ async function main() {
|
|
|
8390
9463
|
const { warmProviderCache: warmProviderCache2 } = await Promise.resolve().then(() => (init_warm(), warm_exports));
|
|
8391
9464
|
await warmProviderCache2(await settings.get());
|
|
8392
9465
|
})().catch((e) => log("warn", "Provider \u9884\u70ED\u5931\u8D25", { err: String(e) }));
|
|
8393
|
-
const daemonJsonPath =
|
|
9466
|
+
const daemonJsonPath = join21(cfg.dataDir, "daemon.json");
|
|
8394
9467
|
await writeJsonAtomic(daemonJsonPath, {
|
|
8395
9468
|
pid: process.pid,
|
|
8396
9469
|
port: cfg.port,
|
|
@@ -8419,6 +9492,7 @@ async function main() {
|
|
|
8419
9492
|
shuttingDown = true;
|
|
8420
9493
|
log("info", "daemon \u5173\u95ED\u4E2D");
|
|
8421
9494
|
terminals.killAll();
|
|
9495
|
+
preview.closeAll();
|
|
8422
9496
|
gateway.closeAll();
|
|
8423
9497
|
try {
|
|
8424
9498
|
rmSync2(daemonJsonPath, { force: true });
|