magpie-agent-console 1.0.0 → 1.1.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 +50 -6
- package/THIRD-PARTY-NOTICES.md +11 -7
- package/app/app.css +1 -1
- package/app/app.js +63 -61
- package/assets/icon.svg +6 -8
- package/daemon/dist/main.js +1767 -711
- package/daemon/dist/main.js.map +1 -1
- package/index.mjs +89 -5
- package/lib/gate.mjs +21 -1
- package/package.json +21 -18
- package/page/boot.js +27 -3
package/daemon/dist/main.js
CHANGED
|
@@ -38,6 +38,15 @@ function initLog(dataDir) {
|
|
|
38
38
|
} catch {
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
function guardConsole() {
|
|
42
|
+
if (guarded) return;
|
|
43
|
+
guarded = true;
|
|
44
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
45
|
+
stream?.on?.("error", () => {
|
|
46
|
+
consoleGone = true;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
41
50
|
function log(level, msg, extra) {
|
|
42
51
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${msg}${extra !== void 0 ? " " + safeJson(extra) : ""}`;
|
|
43
52
|
try {
|
|
@@ -50,8 +59,19 @@ function log(level, msg, extra) {
|
|
|
50
59
|
if (logFile) appendFileSync(logFile, line + "\n", "utf8");
|
|
51
60
|
} catch {
|
|
52
61
|
}
|
|
62
|
+
guardConsole();
|
|
63
|
+
if (consoleGone) return;
|
|
64
|
+
const stream = level === "info" ? process.stdout : process.stderr;
|
|
65
|
+
if (stream?.destroyed || stream?.writableEnded) {
|
|
66
|
+
consoleGone = true;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
53
69
|
const fn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
54
|
-
|
|
70
|
+
try {
|
|
71
|
+
fn(`[agent-console] ${msg}`, extra !== void 0 ? extra : "");
|
|
72
|
+
} catch {
|
|
73
|
+
consoleGone = true;
|
|
74
|
+
}
|
|
55
75
|
}
|
|
56
76
|
function safeJson(v) {
|
|
57
77
|
try {
|
|
@@ -60,12 +80,255 @@ function safeJson(v) {
|
|
|
60
80
|
return String(v);
|
|
61
81
|
}
|
|
62
82
|
}
|
|
63
|
-
var logDir, today, logFile;
|
|
83
|
+
var logDir, today, logFile, consoleGone, guarded;
|
|
64
84
|
var init_log = __esm({
|
|
65
85
|
"daemon/src/log.ts"() {
|
|
66
86
|
logDir = "";
|
|
67
87
|
today = "";
|
|
68
88
|
logFile = "";
|
|
89
|
+
consoleGone = false;
|
|
90
|
+
guarded = false;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// daemon/src/util/i18n.ts
|
|
95
|
+
function normalizeLang(raw) {
|
|
96
|
+
const s = String(raw ?? "").trim().toLowerCase();
|
|
97
|
+
if (!s) return "zh";
|
|
98
|
+
if (s === "zh" || s.startsWith("zh-")) return "zh";
|
|
99
|
+
return "en";
|
|
100
|
+
}
|
|
101
|
+
function setAppLocale(lang) {
|
|
102
|
+
locale = normalizeLang(lang);
|
|
103
|
+
}
|
|
104
|
+
function intlLocale() {
|
|
105
|
+
return locale === "zh" ? "zh-CN" : "en-US";
|
|
106
|
+
}
|
|
107
|
+
function tr(key, params) {
|
|
108
|
+
let s = dict[locale][key] ?? dict.en[key] ?? key;
|
|
109
|
+
if (params) {
|
|
110
|
+
for (const [k, v] of Object.entries(params)) s = s.replaceAll(`{${k}}`, String(v));
|
|
111
|
+
}
|
|
112
|
+
return s;
|
|
113
|
+
}
|
|
114
|
+
var locale, dict;
|
|
115
|
+
var init_i18n = __esm({
|
|
116
|
+
"daemon/src/util/i18n.ts"() {
|
|
117
|
+
locale = "zh";
|
|
118
|
+
dict = {
|
|
119
|
+
zh: {
|
|
120
|
+
"thread.defaultTitle": "\u65B0\u5BF9\u8BDD",
|
|
121
|
+
"notice.daemonRestart": "\u540E\u53F0\u5DF2\u91CD\u542F\uFF0C\u672C\u8F6E\u5DF2\u4E2D\u65AD",
|
|
122
|
+
"notice.sessionStartFailed": "\u4F1A\u8BDD\u542F\u52A8\u5931\u8D25\uFF1A{detail}",
|
|
123
|
+
"notice.sendFailed": "\u53D1\u9001\u5931\u8D25\uFF1A{detail}",
|
|
124
|
+
"notice.effortNextTurn": "\u601D\u8003\u5F3A\u5EA6\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548",
|
|
125
|
+
"notice.effortResetOnModel": "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u601D\u8003\u5F3A\u5EA6\uFF0C\u5DF2\u6062\u590D\u4E3A Default",
|
|
126
|
+
"turn.agentExit": "Agent \u8FDB\u7A0B\u610F\u5916\u9000\u51FA",
|
|
127
|
+
"attachment.missing": "\u9644\u4EF6\u4E0D\u5B58\u5728\u6216\u5DF2\u5931\u6548\uFF08{ref}\uFF09\uFF0C\u8BF7\u91CD\u65B0\u4E0A\u4F20",
|
|
128
|
+
"notice.acpImageByPath": "{agent} \u4E0D\u80FD\u76F4\u63A5\u770B\u56FE\u7247\uFF08\u5B83\u7684 ACP \u521D\u59CB\u5316\u672A\u58F0\u660E\u652F\u6301\u56FE\u7247\u8F93\u5165\uFF09\uFF0C\u5DF2\u6539\u4E3A\u53D1\u9001\u56FE\u7247\u7684\u6587\u4EF6\u8DEF\u5F84\uFF1A{paths}",
|
|
129
|
+
"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
|
+
"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
|
+
"notice.attachmentIssue": "\u9644\u4EF6\u95EE\u9898\uFF1A{detail}",
|
|
132
|
+
"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
|
+
"claude.startFailed": "\u65E0\u6CD5\u542F\u52A8 claude\uFF1A{detail}",
|
|
134
|
+
"claude.exited": "claude \u8FDB\u7A0B\u610F\u5916\u9000\u51FA\uFF08{code}\uFF09",
|
|
135
|
+
"claude.notRunning": "claude \u6CA1\u6709\u5728\u8FD0\u884C\uFF0C\u6D88\u606F\u672A\u53D1\u51FA",
|
|
136
|
+
"claude.closed": "\u4F1A\u8BDD\u5DF2\u5173\u95ED",
|
|
137
|
+
"export.defaultModel": "\u9ED8\u8BA4",
|
|
138
|
+
"bin.notOnPath": "PATH \u91CC\u627E\u4E0D\u5230 {name}",
|
|
139
|
+
"bin.or": " \u6216 ",
|
|
140
|
+
"launch.notFound": "\u627E\u4E0D\u5230\u81EA\u5B9A\u4E49\u7684\u53EF\u6267\u884C\u6587\u4EF6\uFF1A{cmd}\uFF08\u8BF7\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6216 PATH \u91CC\u80FD\u627E\u5230\u7684\u547D\u4EE4\u540D\uFF09",
|
|
141
|
+
"codex.notFound": "\u627E\u4E0D\u5230 codex\uFF08{detail}\uFF09\u3002\u8BF7\u5148\u5B89\u88C5\uFF1A{hint}",
|
|
142
|
+
"provider.disabled": "\u5DF2\u5728\u8BBE\u7F6E\u4E2D\u7981\u7528",
|
|
143
|
+
"provider.planned": "\u5C1A\u672A\u63A5\u5165",
|
|
144
|
+
"provider.disabledNamed": "{name} \u5DF2\u5728\u8BBE\u7F6E\u4E2D\u7981\u7528",
|
|
145
|
+
"provider.plannedNamed": "{name} \u5C1A\u672A\u63A5\u5165\uFF0C\u6682\u4E0D\u80FD\u65B0\u5EFA\u5BF9\u8BDD",
|
|
146
|
+
"provider.unavailable": "{name} \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u7528",
|
|
147
|
+
"provider.unknown": "\u672A\u77E5 Agent\uFF1A{id}",
|
|
148
|
+
"provider.timeout": "\u68C0\u6D4B\u8D85\u65F6",
|
|
149
|
+
"err.projectMissing": "\u9879\u76EE\u4E0D\u5B58\u5728",
|
|
150
|
+
"err.threadMissing": "\u5BF9\u8BDD\u4E0D\u5B58\u5728",
|
|
151
|
+
"err.dirMissing": "\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u6587\u4EF6\u5939",
|
|
152
|
+
"err.projectDirGone": "\u9879\u76EE\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728",
|
|
153
|
+
"err.terminalMissing": "\u7EC8\u7AEF\u4E0D\u5B58\u5728",
|
|
154
|
+
"err.noManager": "\u4F1A\u8BDD\u7BA1\u7406\u5668\u672A\u542F\u52A8",
|
|
155
|
+
"err.badParams": "\u53C2\u6570\u4E0D\u5BF9\uFF1A{detail}",
|
|
156
|
+
"export.me": "\u6211",
|
|
157
|
+
"export.steered": "\uFF08\u63D2\u8BDD\uFF09",
|
|
158
|
+
"export.reasoning": "\u601D\u8003",
|
|
159
|
+
"export.plan": "\u8BA1\u5212",
|
|
160
|
+
"export.todo": "\u5F85\u529E",
|
|
161
|
+
"err.requestGone": "\u6388\u6743/\u63D0\u95EE\u4E0D\u5B58\u5728\u6216\u5DF2\u56DE\u5E94",
|
|
162
|
+
"err.badOption": "optionId \u975E\u6CD5\uFF1A{id}",
|
|
163
|
+
"err.cursorOnly": "\u4EC5 Cursor \u5BF9\u8BDD\u652F\u6301\u8BE5\u9009\u9879",
|
|
164
|
+
"err.busySwitch": "\u5BF9\u8BDD\u8FD0\u884C\u4E2D\uFF0C\u4E0D\u80FD\u66F4\u6362 Agent",
|
|
165
|
+
"err.hasMessages": "\u5DF2\u6709\u6D88\u606F\uFF0C\u4E0D\u80FD\u66F4\u6362 Agent",
|
|
166
|
+
"tool.read": "\u8BFB\u53D6 {x}",
|
|
167
|
+
"tool.edit": "\u7F16\u8F91 {x}",
|
|
168
|
+
"tool.multiEdit": "\u7F16\u8F91 {x}\uFF08{n} \u5904\uFF09",
|
|
169
|
+
"tool.overwrite": "\u8986\u76D6 {x}",
|
|
170
|
+
"tool.create": "\u65B0\u5EFA {x}",
|
|
171
|
+
"tool.notebook": "\u7F16\u8F91\u7B14\u8BB0\u672C {x}",
|
|
172
|
+
"tool.run": "\u8FD0\u884C {x}",
|
|
173
|
+
"tool.output": "\u67E5\u770B\u8F93\u51FA {x}",
|
|
174
|
+
"tool.kill": "\u7ED3\u675F\u8FDB\u7A0B {x}",
|
|
175
|
+
"tool.grep": '\u641C\u7D22 "{x}"',
|
|
176
|
+
"tool.glob": "\u67E5\u627E {x}",
|
|
177
|
+
"tool.fetch": "\u6253\u5F00 {x}",
|
|
178
|
+
"tool.webSearch": '\u641C\u7D22\u7F51\u9875 "{x}"',
|
|
179
|
+
"tool.todo": "\u66F4\u65B0\u5F85\u529E\uFF08{done}/{n}\uFF09",
|
|
180
|
+
"tool.task": "\u5B50\u4EFB\u52A1\uFF1A{x}",
|
|
181
|
+
"tool.generic": "\u5DE5\u5177",
|
|
182
|
+
"tool.editFiles": "\u4FEE\u6539 {n} \u4E2A\u6587\u4EF6",
|
|
183
|
+
"tool.needsApproval": "\u9700\u8981\u6388\u6743",
|
|
184
|
+
"perm.editFiles": "\u4FEE\u6539\u6587\u4EF6",
|
|
185
|
+
"perm.allow": "\u5141\u8BB8",
|
|
186
|
+
"perm.allowSession": "\u672C\u5BF9\u8BDD\u90FD\u5141\u8BB8",
|
|
187
|
+
"perm.deny": "\u62D2\u7EDD",
|
|
188
|
+
"perm.plan": "Agent \u63D0\u4EA4\u4E86\u8BA1\u5212",
|
|
189
|
+
"perm.implement": "\u5B9E\u65BD",
|
|
190
|
+
"http.internal": "\u670D\u52A1\u5185\u90E8\u9519\u8BEF",
|
|
191
|
+
"http.badOrigin": "\u6765\u6E90\u4E0D\u5141\u8BB8",
|
|
192
|
+
"http.badPreflight": "\u9884\u68C0\u4E0D\u5141\u8BB8",
|
|
193
|
+
"http.badToken": "\u4EE4\u724C\u65E0\u6548",
|
|
194
|
+
"http.noAttachment": "\u9644\u4EF6\u4E0D\u5B58\u5728",
|
|
195
|
+
"http.method": "\u65B9\u6CD5\u4E0D\u5141\u8BB8",
|
|
196
|
+
"http.noApp": "app \u8FD8\u6CA1\u6784\u5EFA\uFF08\u7F3A app/index.html\uFF09",
|
|
197
|
+
"http.badPath": "\u8DEF\u5F84\u4E0D\u5408\u6CD5",
|
|
198
|
+
"http.pathDenied": "\u8DEF\u5F84\u4E0D\u5141\u8BB8",
|
|
199
|
+
"http.noFile": "\u6587\u4EF6\u4E0D\u5B58\u5728",
|
|
200
|
+
"http.badType": "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B",
|
|
201
|
+
"http.tooLarge": "\u6587\u4EF6\u592A\u5927\uFF08\u4E0A\u9650 50MB\uFF09",
|
|
202
|
+
"http.noRoute": "\u63A5\u53E3\u4E0D\u5B58\u5728",
|
|
203
|
+
"codex.error": "Codex \u9519\u8BEF",
|
|
204
|
+
"codex.closed": "\u4F1A\u8BDD\u5DF2\u5173\u95ED",
|
|
205
|
+
"codex.noTurnSteer": "\u5F53\u524D\u8F6E\u6B21\u8FD8\u6CA1\u5F00\u59CB\uFF0C\u65E0\u6CD5\u63D2\u8BDD",
|
|
206
|
+
"acp.exited": "{name} \u8FDB\u7A0B\u9000\u51FA ({code})",
|
|
207
|
+
"notice.apiRetry": "API \u91CD\u8BD5\u4E2D\uFF08\u7B2C {n} \u6B21\uFF09",
|
|
208
|
+
"notice.compacted": "\u4E0A\u4E0B\u6587\u5DF2\u538B\u7F29",
|
|
209
|
+
"term.max": "\u6BCF\u4E2A\u9879\u76EE\u6700\u591A {n} \u4E2A\u7EC8\u7AEF",
|
|
210
|
+
"term.title": "\u7EC8\u7AEF {n}",
|
|
211
|
+
"stub.planned": "\u8BE5 Agent \u5C1A\u672A\u5728 Agent Console \u63A5\u5165\uFF0C\u656C\u8BF7\u671F\u5F85",
|
|
212
|
+
"stub.unverified": "\u5DF2\u68C0\u6D4B\u5230 CLI\uFF0C\u4F1A\u8BDD\u9A71\u52A8\u5C1A\u672A\u5B8C\u5168\u9A8C\u8BC1",
|
|
213
|
+
"stub.notFound": "\u672A\u627E\u5230 {bin}",
|
|
214
|
+
"history.untitled": "\uFF08\u65E0\u6807\u9898\uFF09",
|
|
215
|
+
"history.toolCall": "\uFF08\u5386\u53F2\uFF09\u8C03\u7528 {name}",
|
|
216
|
+
"fs.pathDenied": "\u8DEF\u5F84\u4E0D\u5141\u8BB8",
|
|
217
|
+
"fs.dirMissing": "\u76EE\u5F55\u4E0D\u5B58\u5728",
|
|
218
|
+
"fs.dirUnreadable": "\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55",
|
|
219
|
+
"fs.fileMissing": "\u6587\u4EF6\u4E0D\u5B58\u5728",
|
|
220
|
+
"fs.notFile": "\u4E0D\u662F\u6587\u4EF6",
|
|
221
|
+
"fs.unreadable": "\u65E0\u6CD5\u8BFB\u53D6",
|
|
222
|
+
"git.missing": "\u627E\u4E0D\u5230 git\uFF08\u8BF7\u5148\u5B89\u88C5 git \u5E76\u52A0\u5165 PATH\uFF09",
|
|
223
|
+
"git.binary": "\u4E8C\u8FDB\u5236\u6216\u65E0\u6CD5\u8BFB\u53D6"
|
|
224
|
+
},
|
|
225
|
+
en: {
|
|
226
|
+
"thread.defaultTitle": "New chat",
|
|
227
|
+
"notice.daemonRestart": "Daemon restarted \u2014 this turn was interrupted",
|
|
228
|
+
"notice.sessionStartFailed": "Session start failed: {detail}",
|
|
229
|
+
"notice.sendFailed": "Send failed: {detail}",
|
|
230
|
+
"notice.effortNextTurn": "Reasoning effort saved \u2014 takes effect on the next turn",
|
|
231
|
+
"notice.effortResetOnModel": "This model does not support that effort level; reset to Default",
|
|
232
|
+
"turn.agentExit": "Agent process exited unexpectedly",
|
|
233
|
+
"attachment.missing": "Attachment missing or expired ({ref}) \u2014 upload again",
|
|
234
|
+
"notice.acpImageByPath": "{agent} can't see images directly (its ACP initialize doesn't advertise image input); sent the image file path instead: {paths}",
|
|
235
|
+
"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
|
+
"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
|
+
"notice.attachmentIssue": "Attachment issue: {detail}",
|
|
238
|
+
"claude.notFound": "claude was not found ({detail}). Claude Code is not bundled with this plugin; install it first: {hint}",
|
|
239
|
+
"claude.startFailed": "Could not start claude: {detail}",
|
|
240
|
+
"claude.exited": "claude exited unexpectedly ({code})",
|
|
241
|
+
"claude.notRunning": "claude is not running; the message was not sent",
|
|
242
|
+
"claude.closed": "The session is closed",
|
|
243
|
+
"export.defaultModel": "Default",
|
|
244
|
+
"bin.notOnPath": "{name} was not found on PATH",
|
|
245
|
+
"bin.or": " or ",
|
|
246
|
+
"launch.notFound": "Custom executable not found: {cmd} (use an absolute path or a command name on PATH)",
|
|
247
|
+
"codex.notFound": "codex was not found ({detail}). Install it first: {hint}",
|
|
248
|
+
"provider.disabled": "Disabled in Settings",
|
|
249
|
+
"provider.planned": "Not supported yet",
|
|
250
|
+
"provider.disabledNamed": "{name} is disabled in Settings",
|
|
251
|
+
"provider.plannedNamed": "{name} is not supported yet; it can't start new conversations",
|
|
252
|
+
"provider.unavailable": "{name} is not installed or not available",
|
|
253
|
+
"provider.unknown": "Unknown agent: {id}",
|
|
254
|
+
"provider.timeout": "Detection timed out",
|
|
255
|
+
"err.projectMissing": "Project not found",
|
|
256
|
+
"err.threadMissing": "Conversation not found",
|
|
257
|
+
"err.dirMissing": "The folder does not exist or is not a folder",
|
|
258
|
+
"err.projectDirGone": "The project folder no longer exists",
|
|
259
|
+
"err.terminalMissing": "Terminal not found",
|
|
260
|
+
"err.noManager": "The session manager is not running",
|
|
261
|
+
"err.badParams": "Invalid parameters: {detail}",
|
|
262
|
+
"export.me": "Me",
|
|
263
|
+
"export.steered": " (steer)",
|
|
264
|
+
"export.reasoning": "Reasoning",
|
|
265
|
+
"export.plan": "Plan",
|
|
266
|
+
"export.todo": "Todos",
|
|
267
|
+
"err.requestGone": "That approval or question no longer exists or was already answered",
|
|
268
|
+
"err.badOption": "Invalid optionId: {id}",
|
|
269
|
+
"err.cursorOnly": "Only Cursor conversations support this option",
|
|
270
|
+
"err.busySwitch": "The conversation is running; the agent can't be changed",
|
|
271
|
+
"err.hasMessages": "The conversation already has messages; the agent can't be changed",
|
|
272
|
+
"tool.read": "Read {x}",
|
|
273
|
+
"tool.edit": "Edit {x}",
|
|
274
|
+
"tool.multiEdit": "Edit {x} ({n} edits)",
|
|
275
|
+
"tool.overwrite": "Overwrite {x}",
|
|
276
|
+
"tool.create": "Create {x}",
|
|
277
|
+
"tool.notebook": "Edit notebook {x}",
|
|
278
|
+
"tool.run": "Run {x}",
|
|
279
|
+
"tool.output": "Read output {x}",
|
|
280
|
+
"tool.kill": "Stop process {x}",
|
|
281
|
+
"tool.grep": 'Search "{x}"',
|
|
282
|
+
"tool.glob": "Find {x}",
|
|
283
|
+
"tool.fetch": "Open {x}",
|
|
284
|
+
"tool.webSearch": 'Web search "{x}"',
|
|
285
|
+
"tool.todo": "Update todos ({done}/{n})",
|
|
286
|
+
"tool.task": "Subtask: {x}",
|
|
287
|
+
"tool.generic": "Tool",
|
|
288
|
+
"tool.editFiles": "Edit {n} file(s)",
|
|
289
|
+
"tool.needsApproval": "Needs approval",
|
|
290
|
+
"perm.editFiles": "Edit files",
|
|
291
|
+
"perm.allow": "Allow",
|
|
292
|
+
"perm.allowSession": "Allow for this chat",
|
|
293
|
+
"perm.deny": "Deny",
|
|
294
|
+
"perm.plan": "The agent proposed a plan",
|
|
295
|
+
"perm.implement": "Implement",
|
|
296
|
+
"http.internal": "Internal error",
|
|
297
|
+
"http.badOrigin": "Origin not allowed",
|
|
298
|
+
"http.badPreflight": "Preflight not allowed",
|
|
299
|
+
"http.badToken": "Invalid token",
|
|
300
|
+
"http.noAttachment": "Attachment not found",
|
|
301
|
+
"http.method": "Method not allowed",
|
|
302
|
+
"http.noApp": "The app is not built (app/index.html is missing)",
|
|
303
|
+
"http.badPath": "Invalid path",
|
|
304
|
+
"http.pathDenied": "Path not allowed",
|
|
305
|
+
"http.noFile": "File not found",
|
|
306
|
+
"http.badType": "Unsupported file type",
|
|
307
|
+
"http.tooLarge": "File too large (50 MB max)",
|
|
308
|
+
"http.noRoute": "No such endpoint",
|
|
309
|
+
"codex.error": "Codex error",
|
|
310
|
+
"codex.closed": "The session is closed",
|
|
311
|
+
"codex.noTurnSteer": "The turn hasn't started yet; can't steer",
|
|
312
|
+
"acp.exited": "{name} exited ({code})",
|
|
313
|
+
"notice.apiRetry": "Retrying the API (attempt {n})",
|
|
314
|
+
"notice.compacted": "Context compacted",
|
|
315
|
+
"term.max": "At most {n} terminals per project",
|
|
316
|
+
"term.title": "Terminal {n}",
|
|
317
|
+
"stub.planned": "This agent isn't supported in Agent Console yet",
|
|
318
|
+
"stub.unverified": "CLI found; its session driver isn't fully verified yet",
|
|
319
|
+
"stub.notFound": "{bin} was not found",
|
|
320
|
+
"history.untitled": "(untitled)",
|
|
321
|
+
"history.toolCall": "(history) called {name}",
|
|
322
|
+
"fs.pathDenied": "Path not allowed",
|
|
323
|
+
"fs.dirMissing": "Folder not found",
|
|
324
|
+
"fs.dirUnreadable": "Can't read the folder",
|
|
325
|
+
"fs.fileMissing": "File not found",
|
|
326
|
+
"fs.notFile": "Not a file",
|
|
327
|
+
"fs.unreadable": "Can't read the file",
|
|
328
|
+
"git.missing": "git was not found (install git and add it to PATH)",
|
|
329
|
+
"git.binary": "Binary or unreadable"
|
|
330
|
+
}
|
|
331
|
+
};
|
|
69
332
|
}
|
|
70
333
|
});
|
|
71
334
|
|
|
@@ -94,8 +357,8 @@ function normPath(p, platform = process.platform) {
|
|
|
94
357
|
function samePath(a, b) {
|
|
95
358
|
return normPath(a) === normPath(b);
|
|
96
359
|
}
|
|
97
|
-
function validId(
|
|
98
|
-
return typeof
|
|
360
|
+
function validId(id2) {
|
|
361
|
+
return typeof id2 === "string" && ID_RE.test(id2);
|
|
99
362
|
}
|
|
100
363
|
var ID_RE;
|
|
101
364
|
var init_paths = __esm({
|
|
@@ -394,9 +657,9 @@ async function scriptPerm(ctx) {
|
|
|
394
657
|
kind: "tool",
|
|
395
658
|
detail: { command: "npm test", cwd: ".", toolName: "Bash" },
|
|
396
659
|
options: [
|
|
397
|
-
{ id: "accept", label: "
|
|
398
|
-
{ id: "acceptForSession", label: "
|
|
399
|
-
{ id: "decline", label: "
|
|
660
|
+
{ id: "accept", label: tr("perm.allow"), kind: "allow_once" },
|
|
661
|
+
{ id: "acceptForSession", label: tr("perm.allowSession"), kind: "allow_always" },
|
|
662
|
+
{ id: "decline", label: tr("perm.deny"), kind: "reject_once" }
|
|
400
663
|
]
|
|
401
664
|
});
|
|
402
665
|
const ans = await ctx.waitFor(requestId);
|
|
@@ -431,7 +694,7 @@ async function scriptPlan(ctx) {
|
|
|
431
694
|
detail: { markdown: "\u89C1\u4E0A\u65B9\u8BA1\u5212" },
|
|
432
695
|
options: [
|
|
433
696
|
{ id: "implement", label: "\u5F00\u59CB\u5B9E\u65BD", kind: "allow_once" },
|
|
434
|
-
{ id: "reject", label: "
|
|
697
|
+
{ id: "reject", label: tr("perm.deny"), kind: "reject_once" }
|
|
435
698
|
]
|
|
436
699
|
});
|
|
437
700
|
const ans = await ctx.waitFor(requestId);
|
|
@@ -475,10 +738,10 @@ async function scriptAsk(ctx) {
|
|
|
475
738
|
async function scriptSlow(ctx) {
|
|
476
739
|
const { emit } = ctx;
|
|
477
740
|
for (let i = 1; i <= 6; i++) {
|
|
478
|
-
const
|
|
479
|
-
emit({ type: "tool_call", itemId:
|
|
741
|
+
const id2 = "s" + i;
|
|
742
|
+
emit({ type: "tool_call", itemId: id2, kind: "bash", title: `\u6B65\u9AA4 ${i}/6`, status: "running", input: { command: `step ${i}` } });
|
|
480
743
|
if (await sleepAbortable(2e3, ctx)) return;
|
|
481
|
-
emit({ type: "tool_call", itemId:
|
|
744
|
+
emit({ type: "tool_call", itemId: id2, kind: "bash", title: `\u6B65\u9AA4 ${i}/6`, status: "ok", output: `\u6B65\u9AA4 ${i} \u5B8C\u6210`, durationMs: 2e3 });
|
|
482
745
|
}
|
|
483
746
|
emit({ type: "assistant_message", itemId: "sm", text: "6 \u4E2A\u6B65\u9AA4\u5168\u90E8\u8DD1\u5B8C\u3002" });
|
|
484
747
|
}
|
|
@@ -572,6 +835,7 @@ function createMockProvider() {
|
|
|
572
835
|
var mockCapabilities, rid, sleep, MockCrash, MockSession, mockProviderMetrics;
|
|
573
836
|
var init_mock = __esm({
|
|
574
837
|
"daemon/src/providers/mock.ts"() {
|
|
838
|
+
init_i18n();
|
|
575
839
|
init_queue();
|
|
576
840
|
init_diff();
|
|
577
841
|
init_slash();
|
|
@@ -639,7 +903,7 @@ var init_mock = __esm({
|
|
|
639
903
|
if (e.itemId) e.itemId = p + e.itemId;
|
|
640
904
|
this.emit(ev);
|
|
641
905
|
},
|
|
642
|
-
waitFor: (
|
|
906
|
+
waitFor: (id2) => this.waitFor(id2),
|
|
643
907
|
interrupted: () => this.interruptFlag || this.closed,
|
|
644
908
|
steerText: () => this.steered.join("\uFF1B"),
|
|
645
909
|
replyPrefix: () => this.resumePrefix(),
|
|
@@ -680,9 +944,9 @@ var init_mock = __esm({
|
|
|
680
944
|
}
|
|
681
945
|
async interrupt() {
|
|
682
946
|
this.interruptFlag = true;
|
|
683
|
-
for (const [
|
|
947
|
+
for (const [id2, resolve2] of this.pendingReq) {
|
|
684
948
|
resolve2({ cancelled: true });
|
|
685
|
-
this.pendingReq.delete(
|
|
949
|
+
this.pendingReq.delete(id2);
|
|
686
950
|
}
|
|
687
951
|
await sleep(120);
|
|
688
952
|
}
|
|
@@ -692,8 +956,8 @@ var init_mock = __esm({
|
|
|
692
956
|
async setModel(model) {
|
|
693
957
|
this.o.model = model;
|
|
694
958
|
}
|
|
695
|
-
async setMode(
|
|
696
|
-
this.o.mode =
|
|
959
|
+
async setMode(mode2) {
|
|
960
|
+
this.o.mode = mode2;
|
|
697
961
|
}
|
|
698
962
|
async setEffort(effort) {
|
|
699
963
|
this.o.effort = effort;
|
|
@@ -924,7 +1188,7 @@ async function resolveFirstBin(names) {
|
|
|
924
1188
|
if (r.ok) return { ...r, binName };
|
|
925
1189
|
}
|
|
926
1190
|
const label = uniq[0] ?? "cli";
|
|
927
|
-
return { ok: false, reason:
|
|
1191
|
+
return { ok: false, reason: tr("bin.notOnPath", { name: uniq.length > 1 ? uniq.join(tr("bin.or")) : label }) };
|
|
928
1192
|
}
|
|
929
1193
|
function clearBinCache(name) {
|
|
930
1194
|
if (name) cache.delete(name);
|
|
@@ -939,7 +1203,7 @@ async function resolveBinWith(name, deps = {}) {
|
|
|
939
1203
|
const P = win ? path2.win32 : path2.posix;
|
|
940
1204
|
const exists = deps.exists ?? existsSync4;
|
|
941
1205
|
const hits = (deps.find ?? which)(name);
|
|
942
|
-
if (!hits.length) return { ok: false, reason:
|
|
1206
|
+
if (!hits.length) return { ok: false, reason: tr("bin.notOnPath", { name }) };
|
|
943
1207
|
const first = hits[0];
|
|
944
1208
|
if (!win) return { ok: true, cmd: first, prefixArgs: [], display: first, via: "path" };
|
|
945
1209
|
const ext = P.extname(first).toLowerCase();
|
|
@@ -1031,6 +1295,7 @@ var IS_WIN, cache, CACHE_MS, CMD_META, liveTrees, exitHookInstalled;
|
|
|
1031
1295
|
var init_bin = __esm({
|
|
1032
1296
|
"daemon/src/util/bin.ts"() {
|
|
1033
1297
|
init_pathscan();
|
|
1298
|
+
init_i18n();
|
|
1034
1299
|
IS_WIN = process.platform === "win32";
|
|
1035
1300
|
cache = /* @__PURE__ */ new Map();
|
|
1036
1301
|
CACHE_MS = 60 * 1e3;
|
|
@@ -1058,44 +1323,44 @@ function callInfo(name, input = {}, cwd = "") {
|
|
|
1058
1323
|
const file = String(input.file_path ?? input.path ?? input.notebook_path ?? "");
|
|
1059
1324
|
switch (t) {
|
|
1060
1325
|
case "Read":
|
|
1061
|
-
return f("read",
|
|
1326
|
+
return f("read", tr("tool.read", { x: relPath(file, cwd) }), { path: file, offset: input.offset, limit: input.limit });
|
|
1062
1327
|
case "Edit":
|
|
1063
|
-
return f("edit",
|
|
1328
|
+
return f("edit", tr("tool.edit", { x: relPath(file, cwd) }), { path: file, old: input.old_string, new: input.new_string });
|
|
1064
1329
|
case "MultiEdit":
|
|
1065
|
-
return f("edit",
|
|
1330
|
+
return f("edit", tr("tool.multiEdit", { x: relPath(file, cwd), n: (input.edits || []).length }), { path: file, edits: input.edits ?? [] });
|
|
1066
1331
|
case "Write":
|
|
1067
|
-
return f("write",
|
|
1332
|
+
return f("write", tr(input._exists ? "tool.overwrite" : "tool.create", { x: relPath(file, cwd) }), { path: file, content: input.content });
|
|
1068
1333
|
case "NotebookEdit":
|
|
1069
|
-
return f("edit",
|
|
1334
|
+
return f("edit", tr("tool.notebook", { x: relPath(String(input.notebook_path || file), cwd) }), { path: input.notebook_path ?? file });
|
|
1070
1335
|
case "Bash":
|
|
1071
|
-
return f("bash",
|
|
1336
|
+
return f("bash", tr("tool.run", { x: trunc(String(input.command ?? "")) }), { command: input.command, description: input.description });
|
|
1072
1337
|
case "BashOutput":
|
|
1073
|
-
return f("bash",
|
|
1338
|
+
return f("bash", tr("tool.output", { x: String(input.bash_id ?? "") }), { bashId: input.bash_id });
|
|
1074
1339
|
case "KillShell":
|
|
1075
|
-
return f("bash",
|
|
1340
|
+
return f("bash", tr("tool.kill", { x: String(input.shell_id ?? "") }), { shellId: input.shell_id });
|
|
1076
1341
|
case "Grep":
|
|
1077
|
-
return f("search",
|
|
1342
|
+
return f("search", tr("tool.grep", { x: trunc(String(input.pattern ?? ""), 40) }), { pattern: input.pattern, path: input.path, glob: input.glob });
|
|
1078
1343
|
case "Glob":
|
|
1079
1344
|
case "LS":
|
|
1080
|
-
return f("glob",
|
|
1345
|
+
return f("glob", tr("tool.glob", { x: trunc(String(input.pattern ?? input.path ?? ""), 40) }), { pattern: input.pattern, path: input.path });
|
|
1081
1346
|
case "WebFetch":
|
|
1082
|
-
return f("web",
|
|
1347
|
+
return f("web", tr("tool.fetch", { x: hostOf(String(input.url ?? "")) }), { url: input.url });
|
|
1083
1348
|
case "WebSearch":
|
|
1084
|
-
return f("web",
|
|
1349
|
+
return f("web", tr("tool.webSearch", { x: trunc(String(input.query ?? ""), 40) }), { query: input.query });
|
|
1085
1350
|
case "TodoWrite": {
|
|
1086
1351
|
const items = todoItems(input.todos ?? []);
|
|
1087
1352
|
const done = items.filter((x) => x.status === "done").length;
|
|
1088
|
-
return f("todo",
|
|
1353
|
+
return f("todo", tr("tool.todo", { done, n: items.length }), { items });
|
|
1089
1354
|
}
|
|
1090
1355
|
case "Task":
|
|
1091
1356
|
case "Agent":
|
|
1092
|
-
return f("task",
|
|
1357
|
+
return f("task", tr("tool.task", { x: trunc(String(input.description ?? input.prompt ?? ""), 40) }), { description: input.description });
|
|
1093
1358
|
default:
|
|
1094
1359
|
break;
|
|
1095
1360
|
}
|
|
1096
1361
|
const m = /^mcp__([^_]+)__(.+)$/.exec(t);
|
|
1097
1362
|
if (m) return f("mcp", `${m[1]} \xB7 ${m[2]}`, { server: m[1], tool: m[2] });
|
|
1098
|
-
return f("other", t || "
|
|
1363
|
+
return f("other", t || tr("tool.generic"), {});
|
|
1099
1364
|
}
|
|
1100
1365
|
function hostOf(url) {
|
|
1101
1366
|
try {
|
|
@@ -1113,6 +1378,7 @@ function todoItems(todos = []) {
|
|
|
1113
1378
|
var MAX_CMD, trunc;
|
|
1114
1379
|
var init_kinds = __esm({
|
|
1115
1380
|
"daemon/src/agents/kinds.ts"() {
|
|
1381
|
+
init_i18n();
|
|
1116
1382
|
MAX_CMD = 80;
|
|
1117
1383
|
trunc = (s, n = MAX_CMD) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
1118
1384
|
}
|
|
@@ -1126,9 +1392,14 @@ function itemIdFor(st, blockIdx, blkType) {
|
|
|
1126
1392
|
const k = blockKind(blkType);
|
|
1127
1393
|
const n = st.typeSeq.get(k) ?? 0;
|
|
1128
1394
|
st.typeSeq.set(k, n + 1);
|
|
1129
|
-
const
|
|
1130
|
-
st.itemIdByBlock.set(blockIdx,
|
|
1131
|
-
return
|
|
1395
|
+
const id2 = `${st.msgId}:${k}${n}`;
|
|
1396
|
+
st.itemIdByBlock.set(blockIdx, id2);
|
|
1397
|
+
return id2;
|
|
1398
|
+
}
|
|
1399
|
+
function costIsReal(modelUsage) {
|
|
1400
|
+
if (!modelUsage || typeof modelUsage !== "object") return true;
|
|
1401
|
+
const names = Object.keys(modelUsage);
|
|
1402
|
+
return names.every((n) => /claude|opus|sonnet|haiku/i.test(n));
|
|
1132
1403
|
}
|
|
1133
1404
|
function mapSdkMessage(m, st, cwd) {
|
|
1134
1405
|
const out = [];
|
|
@@ -1140,15 +1411,15 @@ function mapSdkMessage(m, st, cwd) {
|
|
|
1140
1411
|
if (sid) out.push({ type: "provider_session_id", id: sid });
|
|
1141
1412
|
}
|
|
1142
1413
|
if (r.subtype === "api_retry") {
|
|
1143
|
-
out.push({ type: "notice", level: "warn", message:
|
|
1414
|
+
out.push({ type: "notice", level: "warn", message: tr("notice.apiRetry", { n: String(r.attempt ?? "?") }) });
|
|
1144
1415
|
}
|
|
1145
1416
|
if (r.subtype === "compact_boundary") {
|
|
1146
|
-
out.push({ type: "notice", level: "info", message: "
|
|
1417
|
+
out.push({ type: "notice", level: "info", message: tr("notice.compacted") });
|
|
1147
1418
|
}
|
|
1148
1419
|
return out;
|
|
1149
1420
|
}
|
|
1150
1421
|
if (r.type === "stream_event") {
|
|
1151
|
-
const e = rec(
|
|
1422
|
+
const e = rec(r.event);
|
|
1152
1423
|
if (e.type === "message_start") {
|
|
1153
1424
|
const mid = str(rec(e.message).id);
|
|
1154
1425
|
if (mid) st.msgId = mid;
|
|
@@ -1227,7 +1498,7 @@ function mapSdkMessage(m, st, cwd) {
|
|
|
1227
1498
|
inputTokens: num("input_tokens"),
|
|
1228
1499
|
outputTokens: num("output_tokens"),
|
|
1229
1500
|
cacheReadTokens: num("cache_read_input_tokens"),
|
|
1230
|
-
costUsd: typeof r.total_cost_usd === "number" ? r.total_cost_usd : 0
|
|
1501
|
+
costUsd: typeof r.total_cost_usd === "number" && costIsReal(r.modelUsage) ? r.total_cost_usd : 0
|
|
1231
1502
|
});
|
|
1232
1503
|
const subtype = str(r.subtype) ?? "";
|
|
1233
1504
|
const interrupted = st.sawInterrupt || /interrupt/i.test(subtype);
|
|
@@ -1249,7 +1520,7 @@ function mapSdkMessage(m, st, cwd) {
|
|
|
1249
1520
|
function mapToolUse(blk, cwd) {
|
|
1250
1521
|
const input = rec(blk.input);
|
|
1251
1522
|
const name = str(blk.name) ?? "tool";
|
|
1252
|
-
const
|
|
1523
|
+
const id2 = str(blk.id) ?? "";
|
|
1253
1524
|
const info = callInfo(name, { ...input, _exists: true }, cwd);
|
|
1254
1525
|
let detail = info.detail;
|
|
1255
1526
|
if (info.kind === "edit" && (input.old_string !== void 0 || Array.isArray(input.edits))) {
|
|
@@ -1262,7 +1533,7 @@ function mapToolUse(blk, cwd) {
|
|
|
1262
1533
|
const out = [
|
|
1263
1534
|
{
|
|
1264
1535
|
type: "tool_call",
|
|
1265
|
-
itemId:
|
|
1536
|
+
itemId: id2,
|
|
1266
1537
|
kind: info.kind,
|
|
1267
1538
|
title: info.title,
|
|
1268
1539
|
status: "running",
|
|
@@ -1285,6 +1556,7 @@ var init_map = __esm({
|
|
|
1285
1556
|
"daemon/src/providers/claude/map.ts"() {
|
|
1286
1557
|
init_kinds();
|
|
1287
1558
|
init_diff();
|
|
1559
|
+
init_i18n();
|
|
1288
1560
|
MAX_OUTPUT = 20 * 1024;
|
|
1289
1561
|
ANSI_RE = /\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g;
|
|
1290
1562
|
clean = (s) => String(s ?? "").replace(ANSI_RE, "");
|
|
@@ -1294,54 +1566,6 @@ var init_map = __esm({
|
|
|
1294
1566
|
}
|
|
1295
1567
|
});
|
|
1296
1568
|
|
|
1297
|
-
// daemon/src/util/i18n.ts
|
|
1298
|
-
function normalizeLang(raw) {
|
|
1299
|
-
const s = String(raw ?? "").trim().toLowerCase();
|
|
1300
|
-
if (!s) return "zh";
|
|
1301
|
-
if (s === "zh" || s.startsWith("zh-")) return "zh";
|
|
1302
|
-
return "en";
|
|
1303
|
-
}
|
|
1304
|
-
function setAppLocale(lang) {
|
|
1305
|
-
locale = normalizeLang(lang);
|
|
1306
|
-
}
|
|
1307
|
-
function tr(key, params) {
|
|
1308
|
-
let s = dict[locale][key] ?? dict.en[key] ?? key;
|
|
1309
|
-
if (params) {
|
|
1310
|
-
for (const [k, v] of Object.entries(params)) s = s.replaceAll(`{${k}}`, String(v));
|
|
1311
|
-
}
|
|
1312
|
-
return s;
|
|
1313
|
-
}
|
|
1314
|
-
var locale, dict;
|
|
1315
|
-
var init_i18n = __esm({
|
|
1316
|
-
"daemon/src/util/i18n.ts"() {
|
|
1317
|
-
locale = "zh";
|
|
1318
|
-
dict = {
|
|
1319
|
-
zh: {
|
|
1320
|
-
"thread.defaultTitle": "\u65B0\u5BF9\u8BDD",
|
|
1321
|
-
"notice.daemonRestart": "\u540E\u53F0\u5DF2\u91CD\u542F\uFF0C\u672C\u8F6E\u5DF2\u4E2D\u65AD",
|
|
1322
|
-
"notice.sessionStartFailed": "\u4F1A\u8BDD\u542F\u52A8\u5931\u8D25\uFF1A{detail}",
|
|
1323
|
-
"notice.sendFailed": "\u53D1\u9001\u5931\u8D25\uFF1A{detail}",
|
|
1324
|
-
"notice.effortNextTurn": "\u601D\u8003\u5F3A\u5EA6\u5DF2\u4FDD\u5B58\uFF0C\u5C06\u5728\u4E0B\u4E00\u8F6E\u751F\u6548",
|
|
1325
|
-
"notice.effortResetOnModel": "\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u601D\u8003\u5F3A\u5EA6\uFF0C\u5DF2\u6062\u590D\u4E3A Default",
|
|
1326
|
-
"turn.agentExit": "Agent \u8FDB\u7A0B\u610F\u5916\u9000\u51FA",
|
|
1327
|
-
"attachment.missing": "\u9644\u4EF6\u4E0D\u5B58\u5728\u6216\u5DF2\u5931\u6548\uFF08{ref}\uFF09\uFF0C\u8BF7\u91CD\u65B0\u4E0A\u4F20",
|
|
1328
|
-
"export.defaultModel": "\u9ED8\u8BA4"
|
|
1329
|
-
},
|
|
1330
|
-
en: {
|
|
1331
|
-
"thread.defaultTitle": "New chat",
|
|
1332
|
-
"notice.daemonRestart": "Daemon restarted \u2014 this turn was interrupted",
|
|
1333
|
-
"notice.sessionStartFailed": "Session start failed: {detail}",
|
|
1334
|
-
"notice.sendFailed": "Send failed: {detail}",
|
|
1335
|
-
"notice.effortNextTurn": "Reasoning effort saved \u2014 takes effect on the next turn",
|
|
1336
|
-
"notice.effortResetOnModel": "This model does not support that effort level; reset to Default",
|
|
1337
|
-
"turn.agentExit": "Agent process exited unexpectedly",
|
|
1338
|
-
"attachment.missing": "Attachment missing or expired ({ref}) \u2014 upload again",
|
|
1339
|
-
"export.defaultModel": "Default"
|
|
1340
|
-
}
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
});
|
|
1344
|
-
|
|
1345
1569
|
// daemon/src/util/attachmentRefs.ts
|
|
1346
1570
|
var attachmentRefs_exports = {};
|
|
1347
1571
|
__export(attachmentRefs_exports, {
|
|
@@ -1395,18 +1619,322 @@ var init_attachmentRefs = __esm({
|
|
|
1395
1619
|
}
|
|
1396
1620
|
});
|
|
1397
1621
|
|
|
1622
|
+
// daemon/src/util/attachmentKinds.ts
|
|
1623
|
+
function baseMime(mime) {
|
|
1624
|
+
return String(mime ?? "").split(";")[0].trim().toLowerCase();
|
|
1625
|
+
}
|
|
1626
|
+
function attachmentKind(mime) {
|
|
1627
|
+
const m = baseMime(mime);
|
|
1628
|
+
if (MODEL_IMAGE_MIMES.has(m)) return "image";
|
|
1629
|
+
if (m.startsWith("text/") || TEXT_APP.has(m)) return "text";
|
|
1630
|
+
return "other";
|
|
1631
|
+
}
|
|
1632
|
+
function pathNote(kind, file) {
|
|
1633
|
+
return `${kind === "image" ? "Attached image" : "Attached file"}: ${file}`;
|
|
1634
|
+
}
|
|
1635
|
+
var MODEL_IMAGE_MIMES, TEXT_APP;
|
|
1636
|
+
var init_attachmentKinds = __esm({
|
|
1637
|
+
"daemon/src/util/attachmentKinds.ts"() {
|
|
1638
|
+
MODEL_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
1639
|
+
TEXT_APP = /* @__PURE__ */ new Set(["application/json", "application/xml", "application/yaml", "application/x-yaml", "application/toml", "application/javascript"]);
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
|
|
1643
|
+
// daemon/src/providers/claude/content.ts
|
|
1644
|
+
import { readFile as readFile4, stat as stat3 } from "node:fs/promises";
|
|
1645
|
+
import { basename } from "node:path";
|
|
1646
|
+
async function buildClaudeContent(text, attachments) {
|
|
1647
|
+
const media = [];
|
|
1648
|
+
const notes = [];
|
|
1649
|
+
const warnings = [];
|
|
1650
|
+
for (const a of attachments) {
|
|
1651
|
+
let file;
|
|
1652
|
+
let size;
|
|
1653
|
+
try {
|
|
1654
|
+
file = await resolveImagePath(a.path);
|
|
1655
|
+
size = (await stat3(file)).size;
|
|
1656
|
+
} catch (e) {
|
|
1657
|
+
warnings.push(`${a.path}: ${String(e?.message ?? e)}`);
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
const kind = attachmentKind(a.mime);
|
|
1661
|
+
try {
|
|
1662
|
+
if (kind === "image" && size <= CLAUDE_MAX_IMAGE_BYTES) {
|
|
1663
|
+
const data = (await readFile4(file)).toString("base64");
|
|
1664
|
+
media.push({ type: "image", source: { type: "base64", media_type: baseMime(a.mime), data } });
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (kind === "text" && size <= CLAUDE_MAX_TEXT_BYTES) {
|
|
1668
|
+
const data = await readFile4(file, "utf8");
|
|
1669
|
+
media.push({ type: "document", source: { type: "text", media_type: "text/plain", data }, title: basename(file), context: file });
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
} catch (e) {
|
|
1673
|
+
warnings.push(`${basename(file)}: ${String(e?.message ?? e)}`);
|
|
1674
|
+
}
|
|
1675
|
+
notes.push(pathNote(kind, file));
|
|
1676
|
+
if (kind === "other") warnings.push(`${basename(file)} (${baseMime(a.mime) || "unknown type"}) is not an image or a text file; sent its path`);
|
|
1677
|
+
else if (kind === "image" && size > CLAUDE_MAX_IMAGE_BYTES) warnings.push(`${basename(file)}: ${(size / 1024 / 1024).toFixed(1)} MB, over Claude's 5 MB image limit; sent its path`);
|
|
1678
|
+
else if (kind === "text" && size > CLAUDE_MAX_TEXT_BYTES) warnings.push(`${basename(file)}: ${(size / 1024 / 1024).toFixed(1)} MB, too large to attach inline; sent its path`);
|
|
1679
|
+
}
|
|
1680
|
+
const fullText = [text, ...notes.length ? [notes.join("\n")] : []].filter(Boolean).join("\n\n");
|
|
1681
|
+
return { blocks: [...media, { type: "text", text: fullText }], warnings };
|
|
1682
|
+
}
|
|
1683
|
+
var CLAUDE_MAX_IMAGE_BYTES, CLAUDE_MAX_TEXT_BYTES;
|
|
1684
|
+
var init_content = __esm({
|
|
1685
|
+
"daemon/src/providers/claude/content.ts"() {
|
|
1686
|
+
init_attachmentRefs();
|
|
1687
|
+
init_attachmentKinds();
|
|
1688
|
+
CLAUDE_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
1689
|
+
CLAUDE_MAX_TEXT_BYTES = 1024 * 1024;
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
|
|
1693
|
+
// daemon/src/providers/launch.ts
|
|
1694
|
+
function launchFromSettings(settings, providerId) {
|
|
1695
|
+
const c = settings.providerCommand?.[providerId];
|
|
1696
|
+
const cmd = typeof c?.executable === "string" ? c.executable.trim() : "";
|
|
1697
|
+
if (!cmd) return void 0;
|
|
1698
|
+
const args = Array.isArray(c?.args) ? c.args.filter((a) => typeof a === "string") : [];
|
|
1699
|
+
return { cmd, args };
|
|
1700
|
+
}
|
|
1701
|
+
function launchArgv(builtin, launch) {
|
|
1702
|
+
return [...launch?.prefixArgs ?? [], ...builtin, ...launch?.args ?? []];
|
|
1703
|
+
}
|
|
1704
|
+
async function resolveLaunch(launch) {
|
|
1705
|
+
const r = await resolveBin(launch.cmd);
|
|
1706
|
+
if (r.ok) return r;
|
|
1707
|
+
return { ok: false, reason: tr("launch.notFound", { cmd: launch.cmd }) };
|
|
1708
|
+
}
|
|
1709
|
+
function launchKey(launch) {
|
|
1710
|
+
return launch ? `${launch.cmd}\0${(launch.prefixArgs ?? []).join("\0")}\0\0${(launch.args ?? []).join("\0")}` : "";
|
|
1711
|
+
}
|
|
1712
|
+
var init_launch = __esm({
|
|
1713
|
+
"daemon/src/providers/launch.ts"() {
|
|
1714
|
+
init_bin();
|
|
1715
|
+
init_i18n();
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
// daemon/src/providers/claude/cli.ts
|
|
1720
|
+
function claudeArgs(o) {
|
|
1721
|
+
const args = [
|
|
1722
|
+
"-p",
|
|
1723
|
+
"--input-format",
|
|
1724
|
+
"stream-json",
|
|
1725
|
+
"--output-format",
|
|
1726
|
+
"stream-json",
|
|
1727
|
+
"--verbose",
|
|
1728
|
+
"--include-partial-messages",
|
|
1729
|
+
"--permission-prompt-tool",
|
|
1730
|
+
"stdio",
|
|
1731
|
+
"--setting-sources",
|
|
1732
|
+
"user,project,local"
|
|
1733
|
+
];
|
|
1734
|
+
const put = (flag, v) => {
|
|
1735
|
+
if (!v) return;
|
|
1736
|
+
if (!SAFE_ARG.test(v)) throw new Error(`Refusing to pass an unexpected value to claude ${flag}: ${JSON.stringify(v).slice(0, 80)}`);
|
|
1737
|
+
args.push(flag, v);
|
|
1738
|
+
};
|
|
1739
|
+
put("--permission-mode", o.permissionMode);
|
|
1740
|
+
if (o.permissionMode === "bypassPermissions") args.push("--allow-dangerously-skip-permissions");
|
|
1741
|
+
put("--model", o.model);
|
|
1742
|
+
put("--effort", o.effort);
|
|
1743
|
+
put("--resume", o.resume);
|
|
1744
|
+
return args;
|
|
1745
|
+
}
|
|
1746
|
+
var SAFE_ARG, ClaudeCli;
|
|
1747
|
+
var init_cli = __esm({
|
|
1748
|
+
"daemon/src/providers/claude/cli.ts"() {
|
|
1749
|
+
init_launch();
|
|
1750
|
+
init_bin();
|
|
1751
|
+
init_queue();
|
|
1752
|
+
SAFE_ARG = /^[A-Za-z0-9][A-Za-z0-9._:\[\]\/@+-]{0,199}$/;
|
|
1753
|
+
ClaudeCli = class {
|
|
1754
|
+
constructor(o, canUseTool) {
|
|
1755
|
+
this.o = o;
|
|
1756
|
+
this.canUseTool = canUseTool;
|
|
1757
|
+
const args = launchArgv(claudeArgs(o), o.launch);
|
|
1758
|
+
this.child = spawnBin(o.bin, args, {
|
|
1759
|
+
cwd: o.cwd,
|
|
1760
|
+
env: { ...o.env ?? process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
|
|
1761
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1762
|
+
group: true
|
|
1763
|
+
});
|
|
1764
|
+
this.child.stdout?.setEncoding("utf8");
|
|
1765
|
+
this.child.stdout?.on("data", (d) => this.onData(d));
|
|
1766
|
+
this.child.stderr?.on("data", (d) => {
|
|
1767
|
+
this.stderrTail = (this.stderrTail + d.toString()).slice(-4096);
|
|
1768
|
+
});
|
|
1769
|
+
this.child.stdin?.on("error", () => {
|
|
1770
|
+
});
|
|
1771
|
+
this.exited = new Promise((resolve2) => {
|
|
1772
|
+
this.child.on("error", (e) => {
|
|
1773
|
+
this.spawnError = e;
|
|
1774
|
+
this.finish();
|
|
1775
|
+
resolve2({ code: null, signal: null });
|
|
1776
|
+
});
|
|
1777
|
+
this.child.on("close", (code, signal) => {
|
|
1778
|
+
this.finish();
|
|
1779
|
+
resolve2({ code, signal });
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
this.initialized = this.request({ subtype: "initialize" }, 6e4).catch(() => null);
|
|
1783
|
+
}
|
|
1784
|
+
o;
|
|
1785
|
+
canUseTool;
|
|
1786
|
+
/** Conversation messages (everything that is not a control message) */
|
|
1787
|
+
messages = new AsyncQueue();
|
|
1788
|
+
exited;
|
|
1789
|
+
/** The answer to initialize (commands, models, account …), null when it failed */
|
|
1790
|
+
initialized;
|
|
1791
|
+
child;
|
|
1792
|
+
buf = "";
|
|
1793
|
+
seq = 0;
|
|
1794
|
+
pending = /* @__PURE__ */ new Map();
|
|
1795
|
+
cancels = /* @__PURE__ */ new Map();
|
|
1796
|
+
stderrTail = "";
|
|
1797
|
+
dead = false;
|
|
1798
|
+
spawnError = null;
|
|
1799
|
+
get stderr() {
|
|
1800
|
+
return this.stderrTail;
|
|
1801
|
+
}
|
|
1802
|
+
get failure() {
|
|
1803
|
+
return this.spawnError;
|
|
1804
|
+
}
|
|
1805
|
+
get alive() {
|
|
1806
|
+
return !this.dead;
|
|
1807
|
+
}
|
|
1808
|
+
finish() {
|
|
1809
|
+
if (this.dead) return;
|
|
1810
|
+
this.dead = true;
|
|
1811
|
+
for (const [, p] of this.pending) {
|
|
1812
|
+
if (p.timer) clearTimeout(p.timer);
|
|
1813
|
+
p.reject(new Error("claude exited"));
|
|
1814
|
+
}
|
|
1815
|
+
this.pending.clear();
|
|
1816
|
+
for (const [, c] of this.cancels) c.abort();
|
|
1817
|
+
this.cancels.clear();
|
|
1818
|
+
this.messages.close();
|
|
1819
|
+
}
|
|
1820
|
+
onData(d) {
|
|
1821
|
+
this.buf += d;
|
|
1822
|
+
let i;
|
|
1823
|
+
while ((i = this.buf.indexOf("\n")) >= 0) {
|
|
1824
|
+
const line = this.buf.slice(0, i).trim();
|
|
1825
|
+
this.buf = this.buf.slice(i + 1);
|
|
1826
|
+
if (!line) continue;
|
|
1827
|
+
let m;
|
|
1828
|
+
try {
|
|
1829
|
+
m = JSON.parse(line);
|
|
1830
|
+
} catch {
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
if (m && typeof m === "object") this.onMessage(m);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
onMessage(m) {
|
|
1837
|
+
if (m.type === "control_response") {
|
|
1838
|
+
const r = m.response ?? {};
|
|
1839
|
+
const id2 = String(r.request_id ?? "");
|
|
1840
|
+
const p = this.pending.get(id2);
|
|
1841
|
+
if (!p) return;
|
|
1842
|
+
this.pending.delete(id2);
|
|
1843
|
+
if (p.timer) clearTimeout(p.timer);
|
|
1844
|
+
if (r.subtype === "success") p.resolve(r.response ?? {});
|
|
1845
|
+
else p.reject(new Error(String(r.error ?? "control request failed")));
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (m.type === "control_request") {
|
|
1849
|
+
void this.onControlRequest(String(m.request_id ?? ""), m.request ?? {});
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
if (m.type === "control_cancel_request") {
|
|
1853
|
+
const id2 = String(m.request_id ?? "");
|
|
1854
|
+
this.cancels.get(id2)?.abort();
|
|
1855
|
+
this.cancels.delete(id2);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (m.type === "keep_alive") return;
|
|
1859
|
+
this.messages.push(m);
|
|
1860
|
+
}
|
|
1861
|
+
async onControlRequest(id2, req) {
|
|
1862
|
+
if (req.subtype !== "can_use_tool") {
|
|
1863
|
+
this.write({ type: "control_response", response: { subtype: "error", request_id: id2, error: `Unsupported control request: ${String(req.subtype)}` } });
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
const ac = new AbortController();
|
|
1867
|
+
this.cancels.set(id2, ac);
|
|
1868
|
+
try {
|
|
1869
|
+
const decision = await this.canUseTool({
|
|
1870
|
+
toolName: String(req.tool_name ?? ""),
|
|
1871
|
+
input: req.input && typeof req.input === "object" ? req.input : {},
|
|
1872
|
+
toolUseId: typeof req.tool_use_id === "string" ? req.tool_use_id : void 0,
|
|
1873
|
+
signal: ac.signal
|
|
1874
|
+
});
|
|
1875
|
+
if (ac.signal.aborted && !this.cancels.has(id2)) return;
|
|
1876
|
+
this.write({ type: "control_response", response: { subtype: "success", request_id: id2, response: decision } });
|
|
1877
|
+
} catch (e) {
|
|
1878
|
+
this.write({ type: "control_response", response: { subtype: "error", request_id: id2, error: String(e?.message ?? e) } });
|
|
1879
|
+
} finally {
|
|
1880
|
+
this.cancels.delete(id2);
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
write(o) {
|
|
1884
|
+
if (this.dead || !this.child.stdin || this.child.stdin.destroyed) return false;
|
|
1885
|
+
try {
|
|
1886
|
+
this.child.stdin.write(JSON.stringify(o) + "\n");
|
|
1887
|
+
return true;
|
|
1888
|
+
} catch {
|
|
1889
|
+
return false;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
/** Send one control request and wait for its answer */
|
|
1893
|
+
request(request, timeoutMs = 1e4) {
|
|
1894
|
+
if (this.dead) return Promise.reject(new Error("claude is not running"));
|
|
1895
|
+
const id2 = `ac_${++this.seq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1896
|
+
return new Promise((resolve2, reject) => {
|
|
1897
|
+
const p = { resolve: resolve2, reject };
|
|
1898
|
+
p.timer = setTimeout(() => {
|
|
1899
|
+
if (this.pending.delete(id2)) reject(new Error(`claude did not answer ${String(request.subtype)} in ${Math.round(timeoutMs / 1e3)} s`));
|
|
1900
|
+
}, timeoutMs);
|
|
1901
|
+
this.pending.set(id2, p);
|
|
1902
|
+
if (!this.write({ type: "control_request", request_id: id2, request })) {
|
|
1903
|
+
this.pending.delete(id2);
|
|
1904
|
+
clearTimeout(p.timer);
|
|
1905
|
+
reject(new Error("claude is not running"));
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
/** One user message into the conversation */
|
|
1910
|
+
sendUser(content, sessionId) {
|
|
1911
|
+
return this.write({ type: "user", message: { role: "user", content }, parent_tool_use_id: null, session_id: sessionId ?? "" });
|
|
1912
|
+
}
|
|
1913
|
+
interrupt() {
|
|
1914
|
+
return this.request({ subtype: "interrupt" }, 5e3);
|
|
1915
|
+
}
|
|
1916
|
+
setModel(model) {
|
|
1917
|
+
return this.request(model ? { subtype: "set_model", model } : { subtype: "set_model" });
|
|
1918
|
+
}
|
|
1919
|
+
setPermissionMode(mode2) {
|
|
1920
|
+
return this.request({ subtype: "set_permission_mode", mode: mode2 });
|
|
1921
|
+
}
|
|
1922
|
+
/** End stdin (the CLI finishes and exits), then end the process tree if it lingers */
|
|
1923
|
+
async close(graceMs = 2e3) {
|
|
1924
|
+
try {
|
|
1925
|
+
this.child.stdin?.end();
|
|
1926
|
+
} catch {
|
|
1927
|
+
}
|
|
1928
|
+
const done = await Promise.race([this.exited.then(() => true), new Promise((r) => setTimeout(() => r(false), graceMs))]);
|
|
1929
|
+
if (!done) await killTree(this.child, { graceMs: 500 });
|
|
1930
|
+
this.finish();
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1398
1936
|
// daemon/src/providers/claude/session.ts
|
|
1399
|
-
|
|
1400
|
-
import { readFile as readFile4 } from "node:fs/promises";
|
|
1401
|
-
function claudeExecutable(o) {
|
|
1402
|
-
if (!o.binCmd) return null;
|
|
1403
|
-
if (o.binVia === "cmd-shim" || o.binVia === "ps1") return null;
|
|
1404
|
-
const last = o.binPrefixArgs[o.binPrefixArgs.length - 1];
|
|
1405
|
-
if (last && /\.(c|m)?js$/i.test(last)) return { path: last, node: true };
|
|
1406
|
-
if (o.binPrefixArgs.length) return null;
|
|
1407
|
-
return { path: o.binCmd, node: false };
|
|
1408
|
-
}
|
|
1409
|
-
var EFFORTS, rid2, ClaudeSession;
|
|
1937
|
+
var EFFORTS, MODES, rid2, ClaudeSession;
|
|
1410
1938
|
var init_session = __esm({
|
|
1411
1939
|
"daemon/src/providers/claude/session.ts"() {
|
|
1412
1940
|
init_queue();
|
|
@@ -1414,9 +1942,12 @@ var init_session = __esm({
|
|
|
1414
1942
|
init_diff();
|
|
1415
1943
|
init_map();
|
|
1416
1944
|
init_log();
|
|
1417
|
-
|
|
1945
|
+
init_content();
|
|
1946
|
+
init_i18n();
|
|
1418
1947
|
init_slash();
|
|
1948
|
+
init_cli();
|
|
1419
1949
|
EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
1950
|
+
MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
1420
1951
|
rid2 = () => Math.random().toString(36).slice(2, 10);
|
|
1421
1952
|
ClaudeSession = class {
|
|
1422
1953
|
constructor(o) {
|
|
@@ -1427,9 +1958,7 @@ var init_session = __esm({
|
|
|
1427
1958
|
o;
|
|
1428
1959
|
events;
|
|
1429
1960
|
q = new AsyncQueue();
|
|
1430
|
-
|
|
1431
|
-
query = null;
|
|
1432
|
-
abort = new AbortController();
|
|
1961
|
+
cli = null;
|
|
1433
1962
|
sessionId;
|
|
1434
1963
|
turnId = null;
|
|
1435
1964
|
turnNum = 0;
|
|
@@ -1438,69 +1967,60 @@ var init_session = __esm({
|
|
|
1438
1967
|
needsRestart = false;
|
|
1439
1968
|
restarting = null;
|
|
1440
1969
|
closed = false;
|
|
1441
|
-
|
|
1970
|
+
stopping = /* @__PURE__ */ new WeakSet();
|
|
1442
1971
|
pumpDone = null;
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1972
|
+
/** Mode the running CLI was started with (bypassPermissions has to be allowed at launch) */
|
|
1973
|
+
launchedMode = null;
|
|
1974
|
+
// The CLI is only started before the first send (or a command list request)
|
|
1975
|
+
ensureCli() {
|
|
1976
|
+
if (this.cli && !this.needsRestart) return this.cli;
|
|
1447
1977
|
this.st = newMapState();
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
options.pathToClaudeCodeExecutable = exe.path;
|
|
1465
|
-
if (exe.node) options.executable = "node";
|
|
1466
|
-
}
|
|
1467
|
-
if (this.o.effort && EFFORTS.includes(this.o.effort)) {
|
|
1468
|
-
options.effort = this.o.effort;
|
|
1469
|
-
}
|
|
1470
|
-
const q = query({ prompt: this.input, options });
|
|
1471
|
-
this.query = q;
|
|
1978
|
+
const effort = this.o.effort && EFFORTS.includes(this.o.effort) ? this.o.effort : null;
|
|
1979
|
+
const cli = new ClaudeCli(
|
|
1980
|
+
{
|
|
1981
|
+
bin: this.o.bin,
|
|
1982
|
+
cwd: this.o.cwd,
|
|
1983
|
+
model: this.o.model,
|
|
1984
|
+
permissionMode: MODES.includes(this.o.mode) ? this.o.mode : "default",
|
|
1985
|
+
effort,
|
|
1986
|
+
resume: this.sessionId,
|
|
1987
|
+
env: this.o.env,
|
|
1988
|
+
launch: this.o.launch
|
|
1989
|
+
},
|
|
1990
|
+
(req) => this.canUseTool(req)
|
|
1991
|
+
);
|
|
1992
|
+
this.cli = cli;
|
|
1993
|
+
this.launchedMode = this.o.mode;
|
|
1472
1994
|
this.needsRestart = false;
|
|
1473
|
-
this.pumpDone = this.pump(
|
|
1474
|
-
return
|
|
1995
|
+
this.pumpDone = this.pump(cli);
|
|
1996
|
+
return cli;
|
|
1475
1997
|
}
|
|
1476
|
-
async pump(
|
|
1477
|
-
|
|
1478
|
-
for
|
|
1479
|
-
for (const ev of mapSdkMessage(m, this.st, this.o.cwd)) this.emit(ev);
|
|
1480
|
-
}
|
|
1481
|
-
} catch (e) {
|
|
1482
|
-
if (!this.abort.signal.aborted && !this.closed) {
|
|
1483
|
-
this.emit({ type: "notice", level: "error", message: "Claude \u4F1A\u8BDD\u5F02\u5E38\uFF1A" + this.errText(e) });
|
|
1484
|
-
}
|
|
1998
|
+
async pump(cli) {
|
|
1999
|
+
for await (const m of cli.messages) {
|
|
2000
|
+
for (const ev of mapSdkMessage(m, this.st, this.o.cwd)) this.emit(ev);
|
|
1485
2001
|
}
|
|
1486
|
-
|
|
2002
|
+
const exit = await cli.exited;
|
|
2003
|
+
if (this.closed || this.stopping.has(cli)) return;
|
|
2004
|
+
const detail = cli.failure ? tr("claude.startFailed", { detail: String(cli.failure.message) }) : tr("claude.exited", { code: String(exit.code ?? exit.signal ?? "?") });
|
|
2005
|
+
const tail = cli.stderr.trim().slice(-400);
|
|
2006
|
+
this.emit({ type: "notice", level: "error", message: tail ? `${detail} \u2014 stderr: ${tail}` : detail });
|
|
2007
|
+
if (this.turnId) this.emit({ type: "turn_ended", turnId: this.turnId, status: "failed", error: detail });
|
|
2008
|
+
this.turnId = null;
|
|
2009
|
+
if (this.cli === cli) {
|
|
1487
2010
|
this.needsRestart = true;
|
|
1488
2011
|
this.q.close();
|
|
1489
2012
|
}
|
|
1490
2013
|
}
|
|
1491
2014
|
emit(ev) {
|
|
1492
2015
|
if (ev.type === "turn_ended" && !ev.turnId) ev.turnId = this.turnId ?? "unknown";
|
|
2016
|
+
if (ev.type === "turn_ended") this.turnId = null;
|
|
1493
2017
|
if (ev.type === "provider_session_id" && ev.id) this.sessionId = ev.id;
|
|
1494
2018
|
this.q.push(ev);
|
|
1495
2019
|
}
|
|
1496
|
-
errText(e) {
|
|
1497
|
-
const base = String(e?.message ?? e);
|
|
1498
|
-
return this.stderrTail ? `${base}\uFF1Bstderr: ${this.stderrTail.slice(-400)}` : base;
|
|
1499
|
-
}
|
|
1500
2020
|
async send(input) {
|
|
1501
|
-
if (this.closed) throw new Error("
|
|
2021
|
+
if (this.closed) throw new Error(tr("claude.closed"));
|
|
1502
2022
|
if (this.needsRestart) await this.restart();
|
|
1503
|
-
const
|
|
2023
|
+
const cli = this.ensureCli();
|
|
1504
2024
|
this.turnNum++;
|
|
1505
2025
|
this.turnId = "u_" + Date.now().toString(36) + "_" + this.turnNum;
|
|
1506
2026
|
this.st.sawInterrupt = false;
|
|
@@ -1511,43 +2031,24 @@ var init_session = __esm({
|
|
|
1511
2031
|
mode: this.o.mode,
|
|
1512
2032
|
effort: this.o.effort
|
|
1513
2033
|
});
|
|
1514
|
-
const
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
try {
|
|
1518
|
-
const disk = await resolveImagePath(img.path);
|
|
1519
|
-
const data = await readFile4(disk);
|
|
1520
|
-
blocks.push({
|
|
1521
|
-
type: "image",
|
|
1522
|
-
source: { type: "base64", media_type: img.mime, data: data.toString("base64") }
|
|
1523
|
-
});
|
|
1524
|
-
} catch (e) {
|
|
1525
|
-
this.emit({ type: "notice", level: "warn", message: `\u56FE\u7247\u8BFB\u53D6\u5931\u8D25 ${img.path}\uFF1A${String(e)}` });
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
blocks.push({ type: "text", text: input.text });
|
|
1529
|
-
this.input.push({
|
|
1530
|
-
type: "user",
|
|
1531
|
-
message: { role: "user", content },
|
|
1532
|
-
parent_tool_use_id: null,
|
|
1533
|
-
session_id: this.sessionId ?? ""
|
|
1534
|
-
});
|
|
1535
|
-
void q;
|
|
2034
|
+
const built = await buildClaudeContent(input.text, input.images ?? []);
|
|
2035
|
+
for (const w of built.warnings) this.emit({ type: "notice", level: "warn", message: tr("notice.attachmentIssue", { detail: w }) });
|
|
2036
|
+
if (!cli.sendUser(built.blocks, this.sessionId)) throw new Error(tr("claude.notRunning"));
|
|
1536
2037
|
}
|
|
1537
2038
|
async restart() {
|
|
1538
2039
|
if (this.restarting) return this.restarting;
|
|
1539
2040
|
this.restarting = (async () => {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
this.
|
|
1544
|
-
|
|
2041
|
+
const old = this.cli;
|
|
2042
|
+
this.cli = null;
|
|
2043
|
+
if (old) {
|
|
2044
|
+
this.stopping.add(old);
|
|
2045
|
+
await old.close().catch(() => {
|
|
2046
|
+
});
|
|
1545
2047
|
}
|
|
1546
2048
|
if (this.pumpDone) await this.pumpDone.catch(() => {
|
|
1547
2049
|
});
|
|
1548
|
-
this.input = new AsyncQueue();
|
|
1549
2050
|
try {
|
|
1550
|
-
this.
|
|
2051
|
+
this.ensureCli();
|
|
1551
2052
|
} finally {
|
|
1552
2053
|
this.restarting = null;
|
|
1553
2054
|
}
|
|
@@ -1555,9 +2056,10 @@ var init_session = __esm({
|
|
|
1555
2056
|
return this.restarting;
|
|
1556
2057
|
}
|
|
1557
2058
|
// ---- permissions (§7.2.4) ----
|
|
1558
|
-
canUseTool(
|
|
2059
|
+
canUseTool(req) {
|
|
2060
|
+
const { toolName, input, signal } = req;
|
|
1559
2061
|
const requestId = "perm_" + rid2();
|
|
1560
|
-
const {
|
|
2062
|
+
const opts = { toolUseID: req.toolUseId };
|
|
1561
2063
|
if (toolName === "ExitPlanMode") {
|
|
1562
2064
|
return this.handlePlanMode(requestId, input, signal, opts.toolUseID);
|
|
1563
2065
|
}
|
|
@@ -1576,24 +2078,24 @@ var init_session = __esm({
|
|
|
1576
2078
|
kind: "tool",
|
|
1577
2079
|
detail,
|
|
1578
2080
|
options: [
|
|
1579
|
-
{ id: "allow", label: "
|
|
1580
|
-
{ id: "allow_always", label: "
|
|
1581
|
-
{ id: "deny", label: "
|
|
2081
|
+
{ id: "allow", label: tr("perm.allow"), kind: "allow_once" },
|
|
2082
|
+
{ id: "allow_always", label: tr("perm.allowSession"), kind: "allow_always" },
|
|
2083
|
+
{ id: "deny", label: tr("perm.deny"), kind: "reject_once" }
|
|
1582
2084
|
]
|
|
1583
2085
|
});
|
|
1584
2086
|
return new Promise((resolve2) => {
|
|
1585
|
-
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve2(this.
|
|
2087
|
+
this.pending.set(requestId, { kind: "permission", resolve: (a) => resolve2(this.toPermissionDecision(a, input)) });
|
|
1586
2088
|
signal.addEventListener("abort", () => {
|
|
1587
2089
|
if (this.pending.delete(requestId)) {
|
|
1588
2090
|
this.emit({ type: "permission_resolved", requestId, cancelled: true });
|
|
1589
|
-
resolve2({ behavior: "deny", message: "
|
|
2091
|
+
resolve2({ behavior: "deny", message: "Cancelled by the user", interrupt: true });
|
|
1590
2092
|
}
|
|
1591
2093
|
}, { once: true });
|
|
1592
2094
|
});
|
|
1593
2095
|
}
|
|
1594
|
-
|
|
2096
|
+
toPermissionDecision(a, input) {
|
|
1595
2097
|
if (a.cancelled || a.optionId === "deny") {
|
|
1596
|
-
return { behavior: "deny", message: "
|
|
2098
|
+
return { behavior: "deny", message: "The user denied this action", ...a.cancelled ? { interrupt: true } : {} };
|
|
1597
2099
|
}
|
|
1598
2100
|
return { behavior: "allow", updatedInput: input };
|
|
1599
2101
|
}
|
|
@@ -1605,12 +2107,12 @@ var init_session = __esm({
|
|
|
1605
2107
|
this.emit({
|
|
1606
2108
|
type: "permission_request",
|
|
1607
2109
|
requestId,
|
|
1608
|
-
title: "
|
|
2110
|
+
title: tr("perm.plan"),
|
|
1609
2111
|
kind: "plan",
|
|
1610
2112
|
detail: { plan: planText },
|
|
1611
2113
|
options: [
|
|
1612
|
-
{ id: "implement", label: "
|
|
1613
|
-
{ id: "reject", label: "
|
|
2114
|
+
{ id: "implement", label: tr("perm.implement"), kind: "allow_once" },
|
|
2115
|
+
{ id: "reject", label: tr("perm.deny"), kind: "reject_once" }
|
|
1614
2116
|
]
|
|
1615
2117
|
});
|
|
1616
2118
|
const answer = await new Promise((resolve2) => {
|
|
@@ -1623,10 +2125,10 @@ var init_session = __esm({
|
|
|
1623
2125
|
}, { once: true });
|
|
1624
2126
|
});
|
|
1625
2127
|
if (answer.cancelled || answer.optionId === "reject") {
|
|
1626
|
-
return { behavior: "deny", message: "
|
|
2128
|
+
return { behavior: "deny", message: "The user rejected the plan; revise it based on their feedback", ...answer.cancelled ? { interrupt: true } : {} };
|
|
1627
2129
|
}
|
|
1628
2130
|
try {
|
|
1629
|
-
await this.
|
|
2131
|
+
await this.cli?.setPermissionMode("default");
|
|
1630
2132
|
this.o.mode = "default";
|
|
1631
2133
|
this.o.onModeChange?.("default");
|
|
1632
2134
|
} catch (e) {
|
|
@@ -1646,7 +2148,7 @@ var init_session = __esm({
|
|
|
1646
2148
|
}, { once: true });
|
|
1647
2149
|
});
|
|
1648
2150
|
if (answer.cancelled) {
|
|
1649
|
-
return { behavior: "deny", message: "
|
|
2151
|
+
return { behavior: "deny", message: "The user cancelled the question", interrupt: true };
|
|
1650
2152
|
}
|
|
1651
2153
|
const questions = input.questions ?? [];
|
|
1652
2154
|
const normalized = {};
|
|
@@ -1667,38 +2169,45 @@ var init_session = __esm({
|
|
|
1667
2169
|
}
|
|
1668
2170
|
async interrupt() {
|
|
1669
2171
|
this.st.sawInterrupt = true;
|
|
1670
|
-
for (const [
|
|
1671
|
-
this.emit({ type: "permission_resolved", requestId:
|
|
2172
|
+
for (const [id2, p] of this.pending) {
|
|
2173
|
+
this.emit({ type: "permission_resolved", requestId: id2, cancelled: true });
|
|
1672
2174
|
p.resolve({ cancelled: true });
|
|
1673
2175
|
}
|
|
1674
2176
|
this.pending.clear();
|
|
2177
|
+
const cli = this.cli;
|
|
2178
|
+
if (!cli || !cli.alive) return;
|
|
1675
2179
|
try {
|
|
1676
|
-
|
|
1677
|
-
if (q) {
|
|
1678
|
-
await Promise.race([
|
|
1679
|
-
q.interrupt(),
|
|
1680
|
-
new Promise((_, rej) => setTimeout(() => rej(new Error("interrupt \u8D85\u65F6")), 3e3))
|
|
1681
|
-
]);
|
|
1682
|
-
}
|
|
2180
|
+
await cli.interrupt();
|
|
1683
2181
|
} catch (e) {
|
|
1684
|
-
log("warn", "interrupt
|
|
2182
|
+
log("warn", "claude interrupt failed", { err: String(e) });
|
|
2183
|
+
this.stopping.add(cli);
|
|
1685
2184
|
this.needsRestart = true;
|
|
2185
|
+
void cli.close(500);
|
|
2186
|
+
if (this.turnId) this.emit({ type: "turn_ended", turnId: this.turnId, status: "interrupted" });
|
|
1686
2187
|
}
|
|
1687
2188
|
}
|
|
1688
2189
|
async setModel(model) {
|
|
1689
2190
|
this.o.model = model;
|
|
2191
|
+
if (!this.cli?.alive) return;
|
|
1690
2192
|
try {
|
|
1691
|
-
await this.
|
|
2193
|
+
await this.cli.setModel(model);
|
|
1692
2194
|
} catch (e) {
|
|
1693
|
-
log("warn", "
|
|
2195
|
+
log("warn", "claude set_model failed", { err: String(e) });
|
|
2196
|
+
this.needsRestart = true;
|
|
1694
2197
|
}
|
|
1695
2198
|
}
|
|
1696
|
-
async setMode(
|
|
1697
|
-
this.o.mode =
|
|
2199
|
+
async setMode(mode2) {
|
|
2200
|
+
this.o.mode = mode2;
|
|
2201
|
+
if (!this.cli?.alive) return;
|
|
2202
|
+
if (mode2 === "bypassPermissions" && this.launchedMode !== "bypassPermissions") {
|
|
2203
|
+
this.needsRestart = true;
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
1698
2206
|
try {
|
|
1699
|
-
await this.
|
|
2207
|
+
await this.cli.setPermissionMode(mode2);
|
|
1700
2208
|
} catch (e) {
|
|
1701
|
-
log("warn", "
|
|
2209
|
+
log("warn", "claude set_permission_mode failed", { err: String(e) });
|
|
2210
|
+
this.needsRestart = true;
|
|
1702
2211
|
}
|
|
1703
2212
|
}
|
|
1704
2213
|
async setEffort(effort) {
|
|
@@ -1707,14 +2216,14 @@ var init_session = __esm({
|
|
|
1707
2216
|
}
|
|
1708
2217
|
async listCommands() {
|
|
1709
2218
|
try {
|
|
1710
|
-
const
|
|
1711
|
-
const commands =
|
|
2219
|
+
const init = await this.ensureCli().initialized;
|
|
2220
|
+
const commands = Array.isArray(init?.commands) ? init.commands : [];
|
|
1712
2221
|
const map = /* @__PURE__ */ new Map();
|
|
1713
2222
|
for (const cmd of commands) {
|
|
1714
|
-
if (!map.has(cmd.name)) {
|
|
2223
|
+
if (cmd?.name && !map.has(cmd.name)) {
|
|
1715
2224
|
map.set(cmd.name, {
|
|
1716
2225
|
name: cmd.name,
|
|
1717
|
-
description: cmd.description,
|
|
2226
|
+
description: cmd.description ?? "",
|
|
1718
2227
|
argumentHint: cmd.argumentHint,
|
|
1719
2228
|
kind: classifyClaudeSlashCommand(cmd.name)
|
|
1720
2229
|
});
|
|
@@ -1729,10 +2238,11 @@ var init_session = __esm({
|
|
|
1729
2238
|
this.closed = true;
|
|
1730
2239
|
for (const [, p] of this.pending) p.resolve({ cancelled: true });
|
|
1731
2240
|
this.pending.clear();
|
|
1732
|
-
this.
|
|
1733
|
-
|
|
1734
|
-
this.
|
|
1735
|
-
|
|
2241
|
+
const cli = this.cli;
|
|
2242
|
+
if (cli) {
|
|
2243
|
+
this.stopping.add(cli);
|
|
2244
|
+
await cli.close().catch(() => {
|
|
2245
|
+
});
|
|
1736
2246
|
}
|
|
1737
2247
|
if (this.pumpDone) {
|
|
1738
2248
|
await Promise.race([this.pumpDone.catch(() => {
|
|
@@ -1754,7 +2264,7 @@ var init_models = __esm({
|
|
|
1754
2264
|
});
|
|
1755
2265
|
|
|
1756
2266
|
// daemon/src/history/import.ts
|
|
1757
|
-
import { readdir, readFile as readFile5, stat as
|
|
2267
|
+
import { readdir, readFile as readFile5, stat as stat4 } from "node:fs/promises";
|
|
1758
2268
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1759
2269
|
import { join as join6 } from "node:path";
|
|
1760
2270
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -1806,10 +2316,10 @@ async function listClaudeHistory(cwd) {
|
|
|
1806
2316
|
if (p.userText) count++;
|
|
1807
2317
|
}
|
|
1808
2318
|
if (!match) continue;
|
|
1809
|
-
const s = await
|
|
2319
|
+
const s = await stat4(path3).catch(() => null);
|
|
1810
2320
|
out.push({
|
|
1811
2321
|
sessionId: f.replace(/\.jsonl$/, ""),
|
|
1812
|
-
title: title || "
|
|
2322
|
+
title: title || tr("history.untitled"),
|
|
1813
2323
|
updatedAt: s ? Math.round(s.mtimeMs) : 0,
|
|
1814
2324
|
messageCount: count
|
|
1815
2325
|
});
|
|
@@ -1847,6 +2357,7 @@ var CLAUDE_ROOT, rec2, str2;
|
|
|
1847
2357
|
var init_import = __esm({
|
|
1848
2358
|
"daemon/src/history/import.ts"() {
|
|
1849
2359
|
init_paths();
|
|
2360
|
+
init_i18n();
|
|
1850
2361
|
CLAUDE_ROOT = join6(homedir3(), ".claude", "projects");
|
|
1851
2362
|
rec2 = (v) => v && typeof v === "object" ? v : {};
|
|
1852
2363
|
str2 = (v) => typeof v === "string" ? v : void 0;
|
|
@@ -1875,16 +2386,18 @@ function runVersion(r, args, timeoutMs = 5e3) {
|
|
|
1875
2386
|
});
|
|
1876
2387
|
});
|
|
1877
2388
|
}
|
|
1878
|
-
function createClaudeProvider() {
|
|
2389
|
+
function createClaudeProvider(opts = {}) {
|
|
2390
|
+
const resolve2 = opts.resolve ?? resolveBin;
|
|
2391
|
+
const resolveCustom = opts.resolve ? (l) => opts.resolve(l.cmd) : resolveLaunch;
|
|
1879
2392
|
return {
|
|
1880
2393
|
id: "claude",
|
|
1881
2394
|
name: "Claude Code",
|
|
1882
2395
|
capabilities: claudeCapabilities,
|
|
1883
|
-
async detect() {
|
|
1884
|
-
const bin = await
|
|
2396
|
+
async detect(launch) {
|
|
2397
|
+
const bin = launch ? await resolveCustom(launch) : await resolve2("claude");
|
|
1885
2398
|
if (!bin.ok) return { available: false, reason: bin.reason, installHint: INSTALL_HINT };
|
|
1886
2399
|
try {
|
|
1887
|
-
const version = await runVersion(bin, ["--version"]);
|
|
2400
|
+
const version = await runVersion(bin, [...launch?.prefixArgs ?? [], "--version"]);
|
|
1888
2401
|
return { available: true, bin: bin.display, version: version.trim().split("\n")[0] || "" };
|
|
1889
2402
|
} catch {
|
|
1890
2403
|
return { available: true, bin: bin.display, version: "" };
|
|
@@ -1894,8 +2407,8 @@ function createClaudeProvider() {
|
|
|
1894
2407
|
return listClaudeModels();
|
|
1895
2408
|
},
|
|
1896
2409
|
async createSession(o) {
|
|
1897
|
-
const bin = await
|
|
1898
|
-
if (!bin.ok) throw new Error(
|
|
2410
|
+
const bin = o.launch ? await resolveCustom(o.launch) : await resolve2("claude");
|
|
2411
|
+
if (!bin.ok) throw new Error(tr("claude.notFound", { detail: bin.reason, hint: INSTALL_HINT }));
|
|
1899
2412
|
return new ClaudeSession({
|
|
1900
2413
|
threadId: o.threadId,
|
|
1901
2414
|
cwd: o.cwd,
|
|
@@ -1903,9 +2416,8 @@ function createClaudeProvider() {
|
|
|
1903
2416
|
mode: o.mode,
|
|
1904
2417
|
effort: o.effort,
|
|
1905
2418
|
resumeId: o.resumeId,
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
binVia: bin.via
|
|
2419
|
+
bin,
|
|
2420
|
+
launch: o.launch
|
|
1909
2421
|
});
|
|
1910
2422
|
},
|
|
1911
2423
|
// §7.6: jsonl sessions under ~/.claude/projects matched by cwd
|
|
@@ -1941,6 +2453,8 @@ var init_provider = __esm({
|
|
|
1941
2453
|
init_models();
|
|
1942
2454
|
init_import();
|
|
1943
2455
|
init_map();
|
|
2456
|
+
init_i18n();
|
|
2457
|
+
init_launch();
|
|
1944
2458
|
INSTALL_HINT = "npm i -g @anthropic-ai/claude-code";
|
|
1945
2459
|
claudeCapabilities = {
|
|
1946
2460
|
resume: true,
|
|
@@ -1958,7 +2472,7 @@ var init_provider = __esm({
|
|
|
1958
2472
|
});
|
|
1959
2473
|
|
|
1960
2474
|
// daemon/src/history/codex.ts
|
|
1961
|
-
import { readdir as readdir2, readFile as readFile6, stat as
|
|
2475
|
+
import { readdir as readdir2, readFile as readFile6, stat as stat5 } from "node:fs/promises";
|
|
1962
2476
|
import { join as join7 } from "node:path";
|
|
1963
2477
|
import { homedir as homedir4 } from "node:os";
|
|
1964
2478
|
function textOf(content, kind) {
|
|
@@ -1969,7 +2483,7 @@ async function* walk(dir, depth = 0) {
|
|
|
1969
2483
|
if (depth > 6) return;
|
|
1970
2484
|
for (const name of await readdir2(dir).catch(() => [])) {
|
|
1971
2485
|
const p = join7(dir, name);
|
|
1972
|
-
const s = await
|
|
2486
|
+
const s = await stat5(p).catch(() => null);
|
|
1973
2487
|
if (!s) continue;
|
|
1974
2488
|
if (s.isDirectory()) yield* walk(p, depth + 1);
|
|
1975
2489
|
else if (/^rollout-.*\.jsonl$/.test(name)) yield p;
|
|
@@ -2012,8 +2526,8 @@ async function listCodexHistory(cwd) {
|
|
|
2012
2526
|
}
|
|
2013
2527
|
}
|
|
2014
2528
|
if (!match) continue;
|
|
2015
|
-
const s = await
|
|
2016
|
-
out.push({ sessionId, title: title || "
|
|
2529
|
+
const s = await stat5(path3).catch(() => null);
|
|
2530
|
+
out.push({ sessionId, title: title || tr("history.untitled"), updatedAt: s ? Math.round(s.mtimeMs) : 0, messageCount: count });
|
|
2017
2531
|
}
|
|
2018
2532
|
out.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
2019
2533
|
return out;
|
|
@@ -2066,7 +2580,7 @@ async function readCodexHistory(sessionId, cwd) {
|
|
|
2066
2580
|
}
|
|
2067
2581
|
} else if (p.type === "function_call" || p.type === "custom_tool_call") {
|
|
2068
2582
|
const name = str3(p.name) ?? "tool";
|
|
2069
|
-
out.push({ type: "notice", level: "info", message:
|
|
2583
|
+
out.push({ type: "notice", level: "info", message: tr("history.toolCall", { name }) });
|
|
2070
2584
|
}
|
|
2071
2585
|
continue;
|
|
2072
2586
|
}
|
|
@@ -2090,7 +2604,7 @@ async function readCodexHistory(sessionId, cwd) {
|
|
|
2090
2604
|
const p2 = rec3(r.payload);
|
|
2091
2605
|
if (p2.type === "function_call" || p2.type === "custom_tool_call") {
|
|
2092
2606
|
const name = str3(p2.name) ?? "tool";
|
|
2093
|
-
out.push({ type: "notice", level: "info", message:
|
|
2607
|
+
out.push({ type: "notice", level: "info", message: tr("history.toolCall", { name }) });
|
|
2094
2608
|
}
|
|
2095
2609
|
}
|
|
2096
2610
|
out.push({ type: "turn_ended", turnId: "import", status: "completed" });
|
|
@@ -2100,6 +2614,7 @@ var CODEX_ROOT, rec3, str3, WRAPPERS;
|
|
|
2100
2614
|
var init_codex = __esm({
|
|
2101
2615
|
"daemon/src/history/codex.ts"() {
|
|
2102
2616
|
init_paths();
|
|
2617
|
+
init_i18n();
|
|
2103
2618
|
CODEX_ROOT = join7(homedir4(), ".codex", "sessions");
|
|
2104
2619
|
rec3 = (v) => v && typeof v === "object" ? v : {};
|
|
2105
2620
|
str3 = (v) => typeof v === "string" ? v : void 0;
|
|
@@ -2186,10 +2701,10 @@ var init_rpc = __esm({
|
|
|
2186
2701
|
async dispatch(msg) {
|
|
2187
2702
|
const hasId = msg.id !== void 0 && msg.id !== null;
|
|
2188
2703
|
if (hasId && ("result" in msg || "error" in msg)) {
|
|
2189
|
-
const
|
|
2190
|
-
const p = this.pending.get(
|
|
2704
|
+
const id2 = Number(msg.id);
|
|
2705
|
+
const p = this.pending.get(id2);
|
|
2191
2706
|
if (p) {
|
|
2192
|
-
this.pending.delete(
|
|
2707
|
+
this.pending.delete(id2);
|
|
2193
2708
|
if (msg.error) {
|
|
2194
2709
|
const e = msg.error;
|
|
2195
2710
|
p.reject(new CodexRpcError(e.message ?? "RPC error", e.code));
|
|
@@ -2200,12 +2715,12 @@ var init_rpc = __esm({
|
|
|
2200
2715
|
return;
|
|
2201
2716
|
}
|
|
2202
2717
|
if (hasId && msg.method) {
|
|
2203
|
-
const
|
|
2718
|
+
const id2 = Number(msg.id);
|
|
2204
2719
|
try {
|
|
2205
2720
|
const result = this.onServerRequest ? await this.onServerRequest(String(msg.method), msg.params ?? {}) : void 0;
|
|
2206
|
-
this.send({ id, result: result ?? null });
|
|
2721
|
+
this.send({ id: id2, result: result ?? null });
|
|
2207
2722
|
} catch (e) {
|
|
2208
|
-
this.send({ id, error: { code: -32603, message: String(e?.message ?? e) } });
|
|
2723
|
+
this.send({ id: id2, error: { code: -32603, message: String(e?.message ?? e) } });
|
|
2209
2724
|
}
|
|
2210
2725
|
return;
|
|
2211
2726
|
}
|
|
@@ -2226,13 +2741,13 @@ var init_rpc = __esm({
|
|
|
2226
2741
|
}
|
|
2227
2742
|
request(method, params, timeoutMs = 6e4) {
|
|
2228
2743
|
if (this.exited) return Promise.reject(new CodexProcessExitedError());
|
|
2229
|
-
const
|
|
2744
|
+
const id2 = this.nextId++;
|
|
2230
2745
|
return new Promise((resolvePromise, reject) => {
|
|
2231
2746
|
const timer = setTimeout(() => {
|
|
2232
|
-
this.pending.delete(
|
|
2747
|
+
this.pending.delete(id2);
|
|
2233
2748
|
reject(new CodexRpcError(`\u8BF7\u6C42\u8D85\u65F6\uFF1A${method}`));
|
|
2234
2749
|
}, timeoutMs);
|
|
2235
|
-
this.pending.set(
|
|
2750
|
+
this.pending.set(id2, {
|
|
2236
2751
|
resolve: (v) => {
|
|
2237
2752
|
clearTimeout(timer);
|
|
2238
2753
|
resolvePromise(v);
|
|
@@ -2242,7 +2757,7 @@ var init_rpc = __esm({
|
|
|
2242
2757
|
reject(e);
|
|
2243
2758
|
}
|
|
2244
2759
|
});
|
|
2245
|
-
this.send({ jsonrpc: "2.0", id, method, params: params ?? {} });
|
|
2760
|
+
this.send({ jsonrpc: "2.0", id: id2, method, params: params ?? {} });
|
|
2246
2761
|
});
|
|
2247
2762
|
}
|
|
2248
2763
|
notify(method, params) {
|
|
@@ -2277,9 +2792,41 @@ var init_rpc = __esm({
|
|
|
2277
2792
|
}
|
|
2278
2793
|
});
|
|
2279
2794
|
|
|
2795
|
+
// daemon/src/providers/codex/input.ts
|
|
2796
|
+
import { basename as basename2 } from "node:path";
|
|
2797
|
+
async function buildCodexInput(text, attachments) {
|
|
2798
|
+
const images = [];
|
|
2799
|
+
const notes = [];
|
|
2800
|
+
const warnings = [];
|
|
2801
|
+
for (const a of attachments) {
|
|
2802
|
+
let file;
|
|
2803
|
+
try {
|
|
2804
|
+
file = await resolveImagePath(a.path);
|
|
2805
|
+
} catch (e) {
|
|
2806
|
+
warnings.push(`${a.path}: ${String(e?.message ?? e)}`);
|
|
2807
|
+
continue;
|
|
2808
|
+
}
|
|
2809
|
+
const kind = attachmentKind(a.mime);
|
|
2810
|
+
if (kind === "image") {
|
|
2811
|
+
images.push({ type: "localImage", path: file });
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
notes.push(pathNote(kind, file));
|
|
2815
|
+
if (kind === "other") warnings.push(`${basename2(file)} (${baseMime(a.mime) || "unknown type"}) is not an image or a text file; sent its path`);
|
|
2816
|
+
}
|
|
2817
|
+
const fullText = [text, ...notes.length ? [notes.join("\n")] : []].filter(Boolean).join("\n\n");
|
|
2818
|
+
return { items: [{ type: "text", text: fullText }, ...images], warnings };
|
|
2819
|
+
}
|
|
2820
|
+
var init_input = __esm({
|
|
2821
|
+
"daemon/src/providers/codex/input.ts"() {
|
|
2822
|
+
init_attachmentRefs();
|
|
2823
|
+
init_attachmentKinds();
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2826
|
+
|
|
2280
2827
|
// daemon/src/providers/codex/map.ts
|
|
2281
2828
|
function newCodexMapState() {
|
|
2282
|
-
return { currentTurnId: null, filesChanged: 0, diffText: "" };
|
|
2829
|
+
return { currentTurnId: null, filesChanged: 0, diffText: "", fileChangesByItemId: /* @__PURE__ */ new Map(), reasoningRaw: /* @__PURE__ */ new Map() };
|
|
2283
2830
|
}
|
|
2284
2831
|
function mapNotification(method, params, st) {
|
|
2285
2832
|
const out = [];
|
|
@@ -2291,6 +2838,8 @@ function mapNotification(method, params, st) {
|
|
|
2291
2838
|
if (tid) st.currentTurnId = tid;
|
|
2292
2839
|
st.filesChanged = 0;
|
|
2293
2840
|
st.diffText = "";
|
|
2841
|
+
st.fileChangesByItemId.clear();
|
|
2842
|
+
st.reasoningRaw.clear();
|
|
2294
2843
|
break;
|
|
2295
2844
|
}
|
|
2296
2845
|
case "item/agentMessage/delta": {
|
|
@@ -2306,7 +2855,10 @@ function mapNotification(method, params, st) {
|
|
|
2306
2855
|
case "item/reasoning/textDelta": {
|
|
2307
2856
|
const itemId = str4(params.itemId) ?? "";
|
|
2308
2857
|
const delta = str4(params.delta) ?? "";
|
|
2309
|
-
if (itemId && delta)
|
|
2858
|
+
if (itemId && delta) {
|
|
2859
|
+
if (method === "item/reasoning/textDelta") st.reasoningRaw.set(itemId, (st.reasoningRaw.get(itemId) ?? "") + delta);
|
|
2860
|
+
out.push({ type: "reasoning_delta", itemId, text: delta });
|
|
2861
|
+
}
|
|
2310
2862
|
break;
|
|
2311
2863
|
}
|
|
2312
2864
|
case "item/commandExecution/outputDelta":
|
|
@@ -2327,17 +2879,18 @@ function mapNotification(method, params, st) {
|
|
|
2327
2879
|
type: "tool_call",
|
|
2328
2880
|
itemId,
|
|
2329
2881
|
kind: "bash",
|
|
2330
|
-
title:
|
|
2882
|
+
title: tr("tool.run", { x: trunc2(cmd) }),
|
|
2331
2883
|
status: "running",
|
|
2332
2884
|
detail: { command: cmd, cwd: str4(item.cwd) }
|
|
2333
2885
|
});
|
|
2334
2886
|
} else if (type === "fileChange") {
|
|
2335
2887
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
2888
|
+
if (itemId) st.fileChangesByItemId.set(itemId, changes);
|
|
2336
2889
|
out.push({
|
|
2337
2890
|
type: "tool_call",
|
|
2338
2891
|
itemId,
|
|
2339
2892
|
kind: "edit",
|
|
2340
|
-
title:
|
|
2893
|
+
title: tr("tool.editFiles", { n: changes.length }),
|
|
2341
2894
|
status: "running",
|
|
2342
2895
|
detail: { changes }
|
|
2343
2896
|
});
|
|
@@ -2356,7 +2909,7 @@ function mapNotification(method, params, st) {
|
|
|
2356
2909
|
type: "tool_call",
|
|
2357
2910
|
itemId,
|
|
2358
2911
|
kind: "web",
|
|
2359
|
-
title:
|
|
2912
|
+
title: tr("tool.webSearch", { x: trunc2(str4(item.query) ?? "", 40) }),
|
|
2360
2913
|
status: "running"
|
|
2361
2914
|
});
|
|
2362
2915
|
}
|
|
@@ -2371,7 +2924,10 @@ function mapNotification(method, params, st) {
|
|
|
2371
2924
|
if (text) out.push({ type: "assistant_message", itemId, text });
|
|
2372
2925
|
} else if (type === "reasoning") {
|
|
2373
2926
|
const summary = Array.isArray(item.summary) ? item.summary : [];
|
|
2374
|
-
const
|
|
2927
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
2928
|
+
const strings = (a) => a.filter((s) => typeof s === "string").join("\n");
|
|
2929
|
+
const text = strings(summary) || strings(content) || st.reasoningRaw.get(itemId) || "";
|
|
2930
|
+
st.reasoningRaw.delete(itemId);
|
|
2375
2931
|
if (text) out.push({ type: "reasoning", itemId, text });
|
|
2376
2932
|
} else if (type === "commandExecution") {
|
|
2377
2933
|
const cmd = str4(item.command) ?? "";
|
|
@@ -2381,7 +2937,7 @@ function mapNotification(method, params, st) {
|
|
|
2381
2937
|
type: "tool_call",
|
|
2382
2938
|
itemId,
|
|
2383
2939
|
kind: "bash",
|
|
2384
|
-
title:
|
|
2940
|
+
title: tr("tool.run", { x: trunc2(cmd) }),
|
|
2385
2941
|
status: exitCode === 0 ? "ok" : "failed",
|
|
2386
2942
|
output: output.length > MAX_OUTPUT2 ? output.slice(0, MAX_OUTPUT2) : output,
|
|
2387
2943
|
exitCode,
|
|
@@ -2389,13 +2945,14 @@ function mapNotification(method, params, st) {
|
|
|
2389
2945
|
});
|
|
2390
2946
|
} else if (type === "fileChange") {
|
|
2391
2947
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
2948
|
+
if (itemId) st.fileChangesByItemId.set(itemId, changes);
|
|
2392
2949
|
const istatus = str4(item.status);
|
|
2393
2950
|
st.filesChanged += changes.length;
|
|
2394
2951
|
out.push({
|
|
2395
2952
|
type: "tool_call",
|
|
2396
2953
|
itemId,
|
|
2397
2954
|
kind: "edit",
|
|
2398
|
-
title:
|
|
2955
|
+
title: tr("tool.editFiles", { n: changes.length }),
|
|
2399
2956
|
status: istatus === "failed" ? "failed" : "ok",
|
|
2400
2957
|
detail: { changes }
|
|
2401
2958
|
});
|
|
@@ -2417,7 +2974,7 @@ function mapNotification(method, params, st) {
|
|
|
2417
2974
|
type: "tool_call",
|
|
2418
2975
|
itemId,
|
|
2419
2976
|
kind: "web",
|
|
2420
|
-
title:
|
|
2977
|
+
title: tr("tool.webSearch", { x: trunc2(str4(item.query) ?? "", 40) }),
|
|
2421
2978
|
status: "ok"
|
|
2422
2979
|
});
|
|
2423
2980
|
} else if (type === "plan") {
|
|
@@ -2474,7 +3031,7 @@ function mapNotification(method, params, st) {
|
|
|
2474
3031
|
break;
|
|
2475
3032
|
}
|
|
2476
3033
|
case "error": {
|
|
2477
|
-
const msg = str4(params.message) ?? "
|
|
3034
|
+
const msg = str4(params.message) ?? tr("codex.error");
|
|
2478
3035
|
out.push({ type: "notice", level: "error", message: msg });
|
|
2479
3036
|
break;
|
|
2480
3037
|
}
|
|
@@ -2486,6 +3043,7 @@ function mapNotification(method, params, st) {
|
|
|
2486
3043
|
var MAX_OUTPUT2, trunc2, rec4, str4;
|
|
2487
3044
|
var init_map2 = __esm({
|
|
2488
3045
|
"daemon/src/providers/codex/map.ts"() {
|
|
3046
|
+
init_i18n();
|
|
2489
3047
|
MAX_OUTPUT2 = 20 * 1024;
|
|
2490
3048
|
trunc2 = (s, n = 80) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
|
|
2491
3049
|
rec4 = (v) => v && typeof v === "object" ? v : {};
|
|
@@ -2494,9 +3052,9 @@ var init_map2 = __esm({
|
|
|
2494
3052
|
});
|
|
2495
3053
|
|
|
2496
3054
|
// daemon/src/providers/codex/commands.ts
|
|
2497
|
-
import { readdir as readdir3, readFile as readFile7, stat as
|
|
3055
|
+
import { readdir as readdir3, readFile as readFile7, stat as stat6 } from "node:fs/promises";
|
|
2498
3056
|
import { homedir as homedir5 } from "node:os";
|
|
2499
|
-
import { basename, join as join8 } from "node:path";
|
|
3057
|
+
import { basename as basename3, join as join8 } from "node:path";
|
|
2500
3058
|
function parseFrontMatter(raw) {
|
|
2501
3059
|
if (!raw.startsWith("---\n")) return { meta: {}, body: raw };
|
|
2502
3060
|
const end = raw.indexOf("\n---\n", 4);
|
|
@@ -2528,11 +3086,11 @@ async function readPromptFiles(dir) {
|
|
|
2528
3086
|
if (!f.endsWith(".md")) continue;
|
|
2529
3087
|
const path3 = join8(dir, f);
|
|
2530
3088
|
try {
|
|
2531
|
-
const st = await
|
|
3089
|
+
const st = await stat6(path3);
|
|
2532
3090
|
if (!st.isFile()) continue;
|
|
2533
3091
|
const raw = await readFile7(path3, "utf8");
|
|
2534
3092
|
const { meta, body } = parseFrontMatter(raw);
|
|
2535
|
-
const name =
|
|
3093
|
+
const name = basename3(f, ".md");
|
|
2536
3094
|
out.push({
|
|
2537
3095
|
name,
|
|
2538
3096
|
description: meta.description || firstLine(body) || name,
|
|
@@ -2556,7 +3114,7 @@ async function readSkillDirs(root) {
|
|
|
2556
3114
|
for (const d of dirs) {
|
|
2557
3115
|
const skillPath = join8(root, d, "SKILL.md");
|
|
2558
3116
|
try {
|
|
2559
|
-
const st = await
|
|
3117
|
+
const st = await stat6(skillPath);
|
|
2560
3118
|
if (!st.isFile()) continue;
|
|
2561
3119
|
const raw = await readFile7(skillPath, "utf8");
|
|
2562
3120
|
const { meta, body } = parseFrontMatter(raw);
|
|
@@ -2609,13 +3167,15 @@ var init_commands = __esm({
|
|
|
2609
3167
|
var rid3, MODE_PRESETS, CodexSession;
|
|
2610
3168
|
var init_session2 = __esm({
|
|
2611
3169
|
"daemon/src/providers/codex/session.ts"() {
|
|
2612
|
-
|
|
3170
|
+
init_input();
|
|
3171
|
+
init_i18n();
|
|
2613
3172
|
init_queue();
|
|
2614
3173
|
init_rpc();
|
|
2615
3174
|
init_bin();
|
|
2616
3175
|
init_map2();
|
|
2617
3176
|
init_commands();
|
|
2618
3177
|
init_log();
|
|
3178
|
+
init_launch();
|
|
2619
3179
|
rid3 = () => Math.random().toString(36).slice(2, 10);
|
|
2620
3180
|
MODE_PRESETS = {
|
|
2621
3181
|
plan: { approvalPolicy: "on-request", sandbox: "read-only", sandboxPolicy: { type: "readOnly" } },
|
|
@@ -2659,7 +3219,7 @@ var init_session2 = __esm({
|
|
|
2659
3219
|
const rpc = new CodexRpc(
|
|
2660
3220
|
argvFor(
|
|
2661
3221
|
{ ok: true, cmd: this.o.binCmd, prefixArgs: this.o.binPrefixArgs, display: this.o.binCmd, via: this.o.binVia ?? "path", shim: this.o.binShim },
|
|
2662
|
-
["app-server"]
|
|
3222
|
+
launchArgv(["app-server"], this.o.launch)
|
|
2663
3223
|
),
|
|
2664
3224
|
{ cwd: this.o.cwd }
|
|
2665
3225
|
);
|
|
@@ -2705,7 +3265,7 @@ var init_session2 = __esm({
|
|
|
2705
3265
|
}
|
|
2706
3266
|
})();
|
|
2707
3267
|
this.started = attempt.catch((e) => {
|
|
2708
|
-
if (this.started ===
|
|
3268
|
+
if (this.started === guarded2) {
|
|
2709
3269
|
this.started = null;
|
|
2710
3270
|
const rpc = this.rpc;
|
|
2711
3271
|
this.rpc = null;
|
|
@@ -2718,21 +3278,18 @@ var init_session2 = __esm({
|
|
|
2718
3278
|
}
|
|
2719
3279
|
throw e;
|
|
2720
3280
|
});
|
|
2721
|
-
const
|
|
3281
|
+
const guarded2 = this.started;
|
|
2722
3282
|
return this.started;
|
|
2723
3283
|
}
|
|
2724
3284
|
async send(input) {
|
|
2725
|
-
if (this.closed) throw new Error("
|
|
3285
|
+
if (this.closed) throw new Error(tr("codex.closed"));
|
|
2726
3286
|
await this.start();
|
|
2727
3287
|
const rpc = this.rpc;
|
|
2728
3288
|
const preset = MODE_PRESETS[this.o.mode];
|
|
2729
3289
|
const entries = this.codexSlash ?? await loadCodexSlashEntries(this.o.cwd);
|
|
2730
3290
|
this.codexSlash = entries;
|
|
2731
3291
|
const wireText = expandCodexSlash(input.text, entries);
|
|
2732
|
-
const items =
|
|
2733
|
-
for (const img of input.images ?? []) {
|
|
2734
|
-
items.push({ type: "localImage", path: await resolveImagePath(img.path) });
|
|
2735
|
-
}
|
|
3292
|
+
const { items, warnings } = await buildCodexInput(wireText, input.images ?? []);
|
|
2736
3293
|
this.interruptWanted = false;
|
|
2737
3294
|
const turnId = "u_" + Date.now().toString(36) + "_" + rid3();
|
|
2738
3295
|
if (this.ourTurnId && this.st.currentTurnId) this.staleCodexTurns.add(this.st.currentTurnId);
|
|
@@ -2745,6 +3302,7 @@ var init_session2 = __esm({
|
|
|
2745
3302
|
mode: this.o.mode,
|
|
2746
3303
|
effort: this.o.effort
|
|
2747
3304
|
});
|
|
3305
|
+
for (const w of warnings) this.emit({ type: "notice", level: "warn", message: tr("notice.attachmentIssue", { detail: w }) });
|
|
2748
3306
|
try {
|
|
2749
3307
|
await rpc.request("turn/start", {
|
|
2750
3308
|
threadId: this.sessionThreadId ?? void 0,
|
|
@@ -2764,11 +3322,9 @@ var init_session2 = __esm({
|
|
|
2764
3322
|
}
|
|
2765
3323
|
}
|
|
2766
3324
|
async steer(input) {
|
|
2767
|
-
if (!this.rpc || !this.st.currentTurnId) throw new Error("
|
|
2768
|
-
const items =
|
|
2769
|
-
for (const
|
|
2770
|
-
items.push({ type: "localImage", path: await resolveImagePath(img.path) });
|
|
2771
|
-
}
|
|
3325
|
+
if (!this.rpc || !this.st.currentTurnId) throw new Error(tr("codex.noTurnSteer"));
|
|
3326
|
+
const { items, warnings } = await buildCodexInput(input.text, input.images ?? []);
|
|
3327
|
+
for (const w of warnings) this.emit({ type: "notice", level: "warn", message: tr("notice.attachmentIssue", { detail: w }) });
|
|
2772
3328
|
try {
|
|
2773
3329
|
await this.rpc.request("turn/steer", {
|
|
2774
3330
|
threadId: this.threadId(),
|
|
@@ -2813,13 +3369,13 @@ var init_session2 = __esm({
|
|
|
2813
3369
|
this.emit({
|
|
2814
3370
|
type: "permission_request",
|
|
2815
3371
|
requestId,
|
|
2816
|
-
title:
|
|
3372
|
+
title: tr("tool.run", { x: command.slice(0, 80) }),
|
|
2817
3373
|
kind: "tool",
|
|
2818
3374
|
detail: { command, cwd: params.cwd, reason: params.reason, approvalId: params.approvalId, itemId: params.itemId },
|
|
2819
3375
|
options: [
|
|
2820
|
-
{ id: "accept", label: "
|
|
2821
|
-
{ id: "acceptForSession", label: "
|
|
2822
|
-
{ id: "decline", label: "
|
|
3376
|
+
{ id: "accept", label: tr("perm.allow"), kind: "allow_once" },
|
|
3377
|
+
{ id: "acceptForSession", label: tr("perm.allowSession"), kind: "allow_always" },
|
|
3378
|
+
{ id: "decline", label: tr("perm.deny"), kind: "reject_once" }
|
|
2823
3379
|
]
|
|
2824
3380
|
});
|
|
2825
3381
|
const a = await this.waitFor(requestId);
|
|
@@ -2828,18 +3384,19 @@ var init_session2 = __esm({
|
|
|
2828
3384
|
return { decision: opt };
|
|
2829
3385
|
}
|
|
2830
3386
|
case "item/fileChange/requestApproval": {
|
|
2831
|
-
const
|
|
3387
|
+
const itemId = typeof params.itemId === "string" ? params.itemId : "";
|
|
3388
|
+
const changes = Array.isArray(params.changes) && params.changes.length ? params.changes : itemId ? this.st.fileChangesByItemId.get(itemId) ?? [] : [];
|
|
2832
3389
|
const requestId = "perm_" + rid3();
|
|
2833
3390
|
this.emit({
|
|
2834
3391
|
type: "permission_request",
|
|
2835
3392
|
requestId,
|
|
2836
|
-
title: "
|
|
3393
|
+
title: tr("perm.editFiles"),
|
|
2837
3394
|
kind: "tool",
|
|
2838
3395
|
detail: { changes, approvalId: params.approvalId, itemId: params.itemId, reason: params.reason },
|
|
2839
3396
|
options: [
|
|
2840
|
-
{ id: "accept", label: "
|
|
2841
|
-
{ id: "acceptForSession", label: "
|
|
2842
|
-
{ id: "decline", label: "
|
|
3397
|
+
{ id: "accept", label: tr("perm.allow"), kind: "allow_once" },
|
|
3398
|
+
{ id: "acceptForSession", label: tr("perm.allowSession"), kind: "allow_always" },
|
|
3399
|
+
{ id: "decline", label: tr("perm.deny"), kind: "reject_once" }
|
|
2843
3400
|
]
|
|
2844
3401
|
});
|
|
2845
3402
|
const a = await this.waitFor(requestId);
|
|
@@ -2880,8 +3437,8 @@ var init_session2 = __esm({
|
|
|
2880
3437
|
async setModel(model) {
|
|
2881
3438
|
this.o.model = model;
|
|
2882
3439
|
}
|
|
2883
|
-
async setMode(
|
|
2884
|
-
this.o.mode =
|
|
3440
|
+
async setMode(mode2) {
|
|
3441
|
+
this.o.mode = mode2;
|
|
2885
3442
|
}
|
|
2886
3443
|
async setEffort(effort) {
|
|
2887
3444
|
this.o.effort = effort;
|
|
@@ -2906,7 +3463,18 @@ var init_session2 = __esm({
|
|
|
2906
3463
|
}
|
|
2907
3464
|
});
|
|
2908
3465
|
|
|
3466
|
+
// daemon/src/providers/types.ts
|
|
3467
|
+
var PLACEHOLDER_MODEL_NAME;
|
|
3468
|
+
var init_types = __esm({
|
|
3469
|
+
"daemon/src/providers/types.ts"() {
|
|
3470
|
+
PLACEHOLDER_MODEL_NAME = "Default";
|
|
3471
|
+
}
|
|
3472
|
+
});
|
|
3473
|
+
|
|
2909
3474
|
// daemon/src/providers/codex/provider.ts
|
|
3475
|
+
import { homedir as homedir6 } from "node:os";
|
|
3476
|
+
import { join as join9 } from "node:path";
|
|
3477
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
2910
3478
|
function runVersion2(r, args, timeoutMs = 5e3) {
|
|
2911
3479
|
return new Promise((resolvePromise, reject) => {
|
|
2912
3480
|
let out = "";
|
|
@@ -2936,8 +3504,8 @@ function normalizeEfforts(raw) {
|
|
|
2936
3504
|
}
|
|
2937
3505
|
return out;
|
|
2938
3506
|
}
|
|
2939
|
-
async function fetchModels(r, cwd) {
|
|
2940
|
-
const rpc = new CodexRpc(argvFor(r, ["app-server"]), { cwd });
|
|
3507
|
+
async function fetchModels(r, cwd, launch) {
|
|
3508
|
+
const rpc = new CodexRpc(argvFor(r, launchArgv(["app-server"], launch)), { cwd });
|
|
2941
3509
|
try {
|
|
2942
3510
|
await rpc.request("initialize", {
|
|
2943
3511
|
clientInfo: { name: "agent-console", title: "Agent Console", version: "0" },
|
|
@@ -2963,39 +3531,97 @@ async function fetchModels(r, cwd) {
|
|
|
2963
3531
|
await rpc.close();
|
|
2964
3532
|
}
|
|
2965
3533
|
}
|
|
3534
|
+
function tomlValue(raw) {
|
|
3535
|
+
const v = raw.trim().replace(/\s+#.*$/, "");
|
|
3536
|
+
const quoted = v.match(/^"([\s\S]*)"$/) ?? v.match(/^'([\s\S]*)'$/);
|
|
3537
|
+
if (quoted) return quoted[1];
|
|
3538
|
+
if (v.startsWith("[") && v.endsWith("]")) {
|
|
3539
|
+
return v.slice(1, -1).split(",").map((x) => tomlValue(x)).filter((x) => typeof x === "string");
|
|
3540
|
+
}
|
|
3541
|
+
return null;
|
|
3542
|
+
}
|
|
3543
|
+
function parseCodexConfig(text) {
|
|
3544
|
+
let section = "";
|
|
3545
|
+
const values = /* @__PURE__ */ new Map();
|
|
3546
|
+
for (const line of text.split(/\r?\n/)) {
|
|
3547
|
+
const s = line.trim();
|
|
3548
|
+
if (!s || s.startsWith("#")) continue;
|
|
3549
|
+
const header = s.match(/^\[([^\]]+)\]$/);
|
|
3550
|
+
if (header) {
|
|
3551
|
+
section = header[1];
|
|
3552
|
+
continue;
|
|
3553
|
+
}
|
|
3554
|
+
const m = s.match(/^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/);
|
|
3555
|
+
if (!m) continue;
|
|
3556
|
+
const value = tomlValue(m[2]);
|
|
3557
|
+
if (typeof value === "string" || Array.isArray(value)) values.set(`${section}\0${m[1]}`, value);
|
|
3558
|
+
}
|
|
3559
|
+
const top = (key) => values.get(`\0${key}`);
|
|
3560
|
+
const profile = top("profile");
|
|
3561
|
+
const profileName = typeof profile === "string" ? profile : null;
|
|
3562
|
+
const profileValue = (key) => profileName ? values.get(`profiles.${profileName}\0${key}`) : void 0;
|
|
3563
|
+
const modelProvider = profileValue("model_provider") ?? top("model_provider");
|
|
3564
|
+
const model = profileValue("model") ?? top("model");
|
|
3565
|
+
const provider = typeof modelProvider === "string" ? modelProvider : null;
|
|
3566
|
+
const models2 = /* @__PURE__ */ new Set();
|
|
3567
|
+
if (typeof model === "string" && model) models2.add(model);
|
|
3568
|
+
for (const [key, value] of values) {
|
|
3569
|
+
if (provider && key === `model_providers.${provider}\0models` && Array.isArray(value)) {
|
|
3570
|
+
for (const m of value) models2.add(m);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
return { modelProvider: provider, model: typeof model === "string" ? model : null, providerModels: [...models2] };
|
|
3574
|
+
}
|
|
3575
|
+
async function readCodexConfig() {
|
|
3576
|
+
const home = process.env.CODEX_HOME?.trim() || join9(homedir6(), ".codex");
|
|
3577
|
+
const file = home.toLowerCase().endsWith(".toml") ? home : join9(home, "config.toml");
|
|
3578
|
+
try {
|
|
3579
|
+
return parseCodexConfig(await readFile8(file, "utf8"));
|
|
3580
|
+
} catch {
|
|
3581
|
+
return { modelProvider: null, model: null, providerModels: [] };
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
function isChatGptProvider(id2) {
|
|
3585
|
+
return !id2 || /^(openai|chatgpt)$/i.test(id2);
|
|
3586
|
+
}
|
|
2966
3587
|
function createCodexProvider(version) {
|
|
2967
3588
|
return {
|
|
2968
3589
|
id: "codex",
|
|
2969
3590
|
name: "Codex",
|
|
2970
3591
|
capabilities: codexCapabilities,
|
|
2971
|
-
async detect() {
|
|
2972
|
-
const bin = await resolveBin("codex");
|
|
3592
|
+
async detect(launch) {
|
|
3593
|
+
const bin = launch ? await resolveLaunch(launch) : await resolveBin("codex");
|
|
2973
3594
|
if (!bin.ok) {
|
|
2974
3595
|
return { available: false, reason: bin.reason, installHint: INSTALL_HINT2 };
|
|
2975
3596
|
}
|
|
2976
3597
|
try {
|
|
2977
|
-
const v = await runVersion2(bin, ["--version"]);
|
|
3598
|
+
const v = await runVersion2(bin, [...launch?.prefixArgs ?? [], "--version"]);
|
|
2978
3599
|
return { available: true, bin: bin.display, version: v.trim().split("\n")[0] || "" };
|
|
2979
3600
|
} catch (e) {
|
|
2980
3601
|
return { available: true, bin: bin.display, version: "", reason: String(e) };
|
|
2981
3602
|
}
|
|
2982
3603
|
},
|
|
2983
|
-
async listModels() {
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3604
|
+
async listModels(launch) {
|
|
3605
|
+
const config = await readCodexConfig();
|
|
3606
|
+
if (!isChatGptProvider(config.modelProvider)) {
|
|
3607
|
+
const rows = config.providerModels.map((id2, i) => ({ id: id2, name: id2, isDefault: i === 0 }));
|
|
3608
|
+
return rows.length ? rows : [{ id: "", name: `${PLACEHOLDER_MODEL_NAME} (${config.modelProvider})`, isDefault: true }];
|
|
3609
|
+
}
|
|
3610
|
+
if (!launch && modelCache && Date.now() - modelCache.at < MODEL_CACHE_MS) return modelCache.models;
|
|
3611
|
+
const bin = launch ? await resolveLaunch(launch) : await resolveBin("codex");
|
|
3612
|
+
if (!bin.ok) return [{ id: "", name: PLACEHOLDER_MODEL_NAME }];
|
|
2987
3613
|
try {
|
|
2988
|
-
const models2 = await fetchModels(bin, process.cwd());
|
|
3614
|
+
const models2 = await fetchModels(bin, process.cwd(), launch);
|
|
2989
3615
|
modelCache = { at: Date.now(), models: models2 };
|
|
2990
3616
|
return models2;
|
|
2991
3617
|
} catch (e) {
|
|
2992
3618
|
log("warn", "codex model/list \u5931\u8D25", { err: String(e) });
|
|
2993
|
-
return [{ id: "", name:
|
|
3619
|
+
return [{ id: "", name: PLACEHOLDER_MODEL_NAME }];
|
|
2994
3620
|
}
|
|
2995
3621
|
},
|
|
2996
3622
|
async createSession(o) {
|
|
2997
|
-
const bin = await resolveBin("codex");
|
|
2998
|
-
if (!bin.ok) throw new Error(
|
|
3623
|
+
const bin = o.launch ? await resolveLaunch(o.launch) : await resolveBin("codex");
|
|
3624
|
+
if (!bin.ok) throw new Error(tr("codex.notFound", { detail: bin.reason, hint: INSTALL_HINT2 }));
|
|
2999
3625
|
return new CodexSession({
|
|
3000
3626
|
threadId: o.threadId,
|
|
3001
3627
|
cwd: o.cwd,
|
|
@@ -3007,6 +3633,7 @@ function createCodexProvider(version) {
|
|
|
3007
3633
|
binPrefixArgs: bin.prefixArgs,
|
|
3008
3634
|
binVia: bin.via,
|
|
3009
3635
|
binShim: bin.shim,
|
|
3636
|
+
launch: o.launch,
|
|
3010
3637
|
version
|
|
3011
3638
|
});
|
|
3012
3639
|
},
|
|
@@ -3028,6 +3655,9 @@ var init_provider2 = __esm({
|
|
|
3028
3655
|
init_rpc();
|
|
3029
3656
|
init_session2();
|
|
3030
3657
|
init_log();
|
|
3658
|
+
init_i18n();
|
|
3659
|
+
init_launch();
|
|
3660
|
+
init_types();
|
|
3031
3661
|
INSTALL_HINT2 = "npm i -g @openai/codex";
|
|
3032
3662
|
codexCapabilities = {
|
|
3033
3663
|
resume: true,
|
|
@@ -3040,7 +3670,9 @@ var init_provider2 = __esm({
|
|
|
3040
3670
|
// tokenUsage yes, cost no
|
|
3041
3671
|
effort: [],
|
|
3042
3672
|
// effort values depend on the model — filled from model/list after detect
|
|
3043
|
-
|
|
3673
|
+
// No "plan": app-server 0.16x has no plan/collaboration mode on turn/start, and a read-only "plan" was the same preset as default (B9)
|
|
3674
|
+
modes: ["default", "acceptEdits", "bypassPermissions"],
|
|
3675
|
+
modeLabels: { default: "Read Only", acceptEdits: "Auto", bypassPermissions: "Full Access" }
|
|
3044
3676
|
};
|
|
3045
3677
|
modelCache = null;
|
|
3046
3678
|
MODEL_CACHE_MS = 10 * 60 * 1e3;
|
|
@@ -3048,8 +3680,8 @@ var init_provider2 = __esm({
|
|
|
3048
3680
|
});
|
|
3049
3681
|
|
|
3050
3682
|
// daemon/src/providers/catalog.ts
|
|
3051
|
-
function catalogById(
|
|
3052
|
-
return PROVIDER_CATALOG.find((p) => p.id ===
|
|
3683
|
+
function catalogById(id2) {
|
|
3684
|
+
return PROVIDER_CATALOG.find((p) => p.id === id2);
|
|
3053
3685
|
}
|
|
3054
3686
|
function providerCreatable(status) {
|
|
3055
3687
|
return status !== "planned";
|
|
@@ -3059,12 +3691,12 @@ var init_catalog = __esm({
|
|
|
3059
3691
|
"daemon/src/providers/catalog.ts"() {
|
|
3060
3692
|
PROVIDER_CATALOG = [
|
|
3061
3693
|
{ id: "claude", name: "Claude Code", bins: ["claude"], installHint: "npm i -g @anthropic-ai/claude-code", status: "verified" },
|
|
3062
|
-
{ id: "codex", name: "Codex", bins: ["codex"], installHint: "
|
|
3063
|
-
{ id: "cursor", name: "Cursor", bins: ["cursor-agent", "cursor"], installHint: "
|
|
3064
|
-
{ id: "copilot", name: "Copilot", bins: ["copilot"], installHint: "
|
|
3065
|
-
{ id: "opencode", name: "OpenCode", bins: ["opencode"], installHint: "npm i -g opencode
|
|
3066
|
-
{ id: "pi", name: "Pi", bins: ["pi-acp", "pi", "pi-coding-agent"], installHint: "npm i -g pi-acp
|
|
3067
|
-
{ id: "devin", name: "Devin CLI", bins: ["devin"], installHint: "
|
|
3694
|
+
{ id: "codex", name: "Codex", bins: ["codex"], installHint: "npm i -g @openai/codex", status: "verified" },
|
|
3695
|
+
{ id: "cursor", name: "Cursor", bins: ["cursor-agent", "cursor"], installHint: "curl https://cursor.com/install -fsS | bash", status: "verified" },
|
|
3696
|
+
{ id: "copilot", name: "Copilot", bins: ["copilot"], installHint: "npm i -g @github/copilot", status: "verified" },
|
|
3697
|
+
{ id: "opencode", name: "OpenCode", bins: ["opencode"], installHint: "npm i -g opencode-ai", status: "verified" },
|
|
3698
|
+
{ id: "pi", name: "Pi", bins: ["pi-acp", "pi", "pi-coding-agent"], installHint: "npm i -g pi-acp", status: "verified" },
|
|
3699
|
+
{ id: "devin", name: "Devin CLI", bins: ["devin"], installHint: "https://docs.devin.ai/cli", status: "verified" },
|
|
3068
3700
|
{ id: "dsh", name: "dsh", bins: ["dsh"], installHint: "npm i -g @deepseek-ai/dsh", status: "verified" }
|
|
3069
3701
|
];
|
|
3070
3702
|
}
|
|
@@ -3101,11 +3733,11 @@ var init_spawn = __esm({
|
|
|
3101
3733
|
});
|
|
3102
3734
|
|
|
3103
3735
|
// daemon/src/providers/acp/clientHandlers.ts
|
|
3104
|
-
import { readFile as
|
|
3736
|
+
import { readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
|
|
3105
3737
|
import { methods } from "@agentclientprotocol/sdk";
|
|
3106
3738
|
function registerAcpClientHandlers(app, bridge) {
|
|
3107
3739
|
app.onRequest(methods.client.fs.readTextFile, async (ctx) => {
|
|
3108
|
-
const text = await
|
|
3740
|
+
const text = await readFile9(ctx.params.path, "utf8");
|
|
3109
3741
|
return { content: text };
|
|
3110
3742
|
});
|
|
3111
3743
|
app.onRequest(methods.client.fs.writeTextFile, async (ctx) => {
|
|
@@ -3127,7 +3759,7 @@ function registerAcpClientHandlers(app, bridge) {
|
|
|
3127
3759
|
bridge.push({
|
|
3128
3760
|
type: "permission_request",
|
|
3129
3761
|
requestId,
|
|
3130
|
-
title: tc.title ?? tc.name ?? "
|
|
3762
|
+
title: tc.title ?? tc.name ?? tr("tool.needsApproval"),
|
|
3131
3763
|
kind: tc.kind ?? "tool",
|
|
3132
3764
|
detail: tc.rawInput ?? void 0,
|
|
3133
3765
|
options
|
|
@@ -3141,6 +3773,7 @@ function registerAcpClientHandlers(app, bridge) {
|
|
|
3141
3773
|
}
|
|
3142
3774
|
var init_clientHandlers = __esm({
|
|
3143
3775
|
"daemon/src/providers/acp/clientHandlers.ts"() {
|
|
3776
|
+
init_i18n();
|
|
3144
3777
|
}
|
|
3145
3778
|
});
|
|
3146
3779
|
|
|
@@ -3169,7 +3802,13 @@ function textFromContent(content) {
|
|
|
3169
3802
|
return "";
|
|
3170
3803
|
}
|
|
3171
3804
|
function newMapCtx() {
|
|
3172
|
-
return { itemPrefix: "a" + rid4() + "_", messageIds: /* @__PURE__ */ new Map() };
|
|
3805
|
+
return { itemPrefix: "a" + rid4() + "_", messageIds: /* @__PURE__ */ new Map(), seg: 0, lastWasText: false };
|
|
3806
|
+
}
|
|
3807
|
+
function endTextSegment(ctx) {
|
|
3808
|
+
if (ctx.lastWasText) {
|
|
3809
|
+
ctx.seg++;
|
|
3810
|
+
ctx.lastWasText = false;
|
|
3811
|
+
}
|
|
3173
3812
|
}
|
|
3174
3813
|
function mapSessionUpdate(update, ctx) {
|
|
3175
3814
|
const out = [];
|
|
@@ -3178,19 +3817,22 @@ function mapSessionUpdate(update, ctx) {
|
|
|
3178
3817
|
case "agent_message_chunk": {
|
|
3179
3818
|
const text = textFromContent(update.content);
|
|
3180
3819
|
if (!text) break;
|
|
3181
|
-
const itemId =
|
|
3820
|
+
const itemId = `${ctx.itemPrefix}${mid ?? "msg"}_${ctx.seg}`;
|
|
3182
3821
|
if (mid) ctx.messageIds.set(mid, itemId);
|
|
3822
|
+
ctx.lastWasText = true;
|
|
3183
3823
|
out.push({ type: "assistant_delta", itemId, text });
|
|
3184
3824
|
break;
|
|
3185
3825
|
}
|
|
3186
3826
|
case "agent_thought_chunk": {
|
|
3187
3827
|
const text = textFromContent(update.content);
|
|
3188
3828
|
if (!text) break;
|
|
3189
|
-
const itemId =
|
|
3829
|
+
const itemId = `${ctx.itemPrefix}t_${mid ?? "think"}_${ctx.seg}`;
|
|
3830
|
+
ctx.lastWasText = true;
|
|
3190
3831
|
out.push({ type: "reasoning_delta", itemId, text });
|
|
3191
3832
|
break;
|
|
3192
3833
|
}
|
|
3193
3834
|
case "tool_call":
|
|
3835
|
+
endTextSegment(ctx);
|
|
3194
3836
|
out.push({
|
|
3195
3837
|
type: "tool_call",
|
|
3196
3838
|
itemId: ctx.itemPrefix + update.toolCallId,
|
|
@@ -3201,17 +3843,19 @@ function mapSessionUpdate(update, ctx) {
|
|
|
3201
3843
|
});
|
|
3202
3844
|
break;
|
|
3203
3845
|
case "tool_call_update":
|
|
3846
|
+
endTextSegment(ctx);
|
|
3204
3847
|
out.push({
|
|
3205
3848
|
type: "tool_call",
|
|
3206
3849
|
itemId: ctx.itemPrefix + update.toolCallId,
|
|
3207
3850
|
kind: mapToolKind(update.kind),
|
|
3208
|
-
title: update.title ?? update.name ?? "
|
|
3851
|
+
title: update.title ?? update.name ?? "",
|
|
3209
3852
|
status: mapToolStatus(update.status),
|
|
3210
3853
|
input: update.rawInput,
|
|
3211
3854
|
output: typeof update.rawOutput === "string" ? update.rawOutput : void 0
|
|
3212
3855
|
});
|
|
3213
3856
|
break;
|
|
3214
3857
|
case "plan":
|
|
3858
|
+
endTextSegment(ctx);
|
|
3215
3859
|
out.push({
|
|
3216
3860
|
type: "plan",
|
|
3217
3861
|
itemId: ctx.itemPrefix + "plan",
|
|
@@ -3257,6 +3901,125 @@ var init_mapUpdate = __esm({
|
|
|
3257
3901
|
}
|
|
3258
3902
|
});
|
|
3259
3903
|
|
|
3904
|
+
// daemon/src/providers/acp/promptCaps.ts
|
|
3905
|
+
function promptCapsFromInitialize(res) {
|
|
3906
|
+
const pc = res?.agentCapabilities?.promptCapabilities;
|
|
3907
|
+
return {
|
|
3908
|
+
image: pc?.image === true,
|
|
3909
|
+
audio: pc?.audio === true,
|
|
3910
|
+
embeddedContext: pc?.embeddedContext === true
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
function rememberPromptCaps(providerId, caps) {
|
|
3914
|
+
if (providerId) learned.set(providerId, caps);
|
|
3915
|
+
}
|
|
3916
|
+
function knownPromptCaps(providerId) {
|
|
3917
|
+
return learned.get(providerId);
|
|
3918
|
+
}
|
|
3919
|
+
var learned;
|
|
3920
|
+
var init_promptCaps = __esm({
|
|
3921
|
+
"daemon/src/providers/acp/promptCaps.ts"() {
|
|
3922
|
+
learned = /* @__PURE__ */ new Map();
|
|
3923
|
+
}
|
|
3924
|
+
});
|
|
3925
|
+
|
|
3926
|
+
// daemon/src/providers/acp/sessionCaps.ts
|
|
3927
|
+
function sessionOpenFromInitialize(res) {
|
|
3928
|
+
const caps = res?.agentCapabilities;
|
|
3929
|
+
const resume = caps?.sessionCapabilities?.resume != null && typeof caps.sessionCapabilities.resume === "object";
|
|
3930
|
+
return { load: caps?.loadSession === true, resume };
|
|
3931
|
+
}
|
|
3932
|
+
function pickReconnect(open) {
|
|
3933
|
+
if (open.load) return "load";
|
|
3934
|
+
if (open.resume) return "resume";
|
|
3935
|
+
return "none";
|
|
3936
|
+
}
|
|
3937
|
+
var init_sessionCaps = __esm({
|
|
3938
|
+
"daemon/src/providers/acp/sessionCaps.ts"() {
|
|
3939
|
+
}
|
|
3940
|
+
});
|
|
3941
|
+
|
|
3942
|
+
// daemon/src/providers/acp/prompt.ts
|
|
3943
|
+
import { readFile as readFile10, stat as stat7 } from "node:fs/promises";
|
|
3944
|
+
import { basename as basename4 } from "node:path";
|
|
3945
|
+
import { pathToFileURL } from "node:url";
|
|
3946
|
+
async function buildAcpPrompt(text, attachments, caps) {
|
|
3947
|
+
const media = [];
|
|
3948
|
+
const links = [];
|
|
3949
|
+
const pathLines = [];
|
|
3950
|
+
const imagesByPath = [];
|
|
3951
|
+
const warnings = [];
|
|
3952
|
+
let inlineBytes = 0;
|
|
3953
|
+
const byPath = (file, mime, size, label) => {
|
|
3954
|
+
links.push({ type: "resource_link", uri: pathToFileURL(file).href, name: basename4(file), mimeType: mime, ...size !== null ? { size } : {} });
|
|
3955
|
+
pathLines.push(`${label}: ${file}`);
|
|
3956
|
+
};
|
|
3957
|
+
for (const a of attachments) {
|
|
3958
|
+
const mime = a.mime || "application/octet-stream";
|
|
3959
|
+
let file;
|
|
3960
|
+
let size = null;
|
|
3961
|
+
try {
|
|
3962
|
+
file = await resolveImagePath(a.path);
|
|
3963
|
+
size = (await stat7(file)).size;
|
|
3964
|
+
} catch (e) {
|
|
3965
|
+
warnings.push(`${a.path}: ${String(e?.message ?? e)}`);
|
|
3966
|
+
continue;
|
|
3967
|
+
}
|
|
3968
|
+
const image = attachmentKind(mime) === "image";
|
|
3969
|
+
const audio = mime.startsWith("audio/");
|
|
3970
|
+
if (image && caps.image || audio && caps.audio) {
|
|
3971
|
+
if (size > MAX_INLINE_MEDIA_BYTES || inlineBytes + size > MAX_INLINE_TOTAL_BYTES) {
|
|
3972
|
+
const limit = size > MAX_INLINE_MEDIA_BYTES ? MAX_INLINE_MEDIA_BYTES : MAX_INLINE_TOTAL_BYTES;
|
|
3973
|
+
warnings.push(`${basename4(file)}: ${(size / 1024 / 1024).toFixed(1)} MB, over the ${limit / 1024 / 1024} MB inline limit; sent as a file path`);
|
|
3974
|
+
byPath(file, mime, size, image ? "Attached image" : "Attached audio");
|
|
3975
|
+
if (image) imagesByPath.push(file);
|
|
3976
|
+
continue;
|
|
3977
|
+
}
|
|
3978
|
+
try {
|
|
3979
|
+
const data = (await readFile10(file)).toString("base64");
|
|
3980
|
+
media.push(image ? { type: "image", data, mimeType: mime } : { type: "audio", data, mimeType: mime });
|
|
3981
|
+
inlineBytes += size;
|
|
3982
|
+
} catch (e) {
|
|
3983
|
+
warnings.push(`${basename4(file)}: ${String(e?.message ?? e)}`);
|
|
3984
|
+
byPath(file, mime, size, image ? "Attached image" : "Attached audio");
|
|
3985
|
+
if (image) imagesByPath.push(file);
|
|
3986
|
+
}
|
|
3987
|
+
continue;
|
|
3988
|
+
}
|
|
3989
|
+
if (image || audio) {
|
|
3990
|
+
byPath(file, mime, size, image ? "Attached image" : "Attached audio");
|
|
3991
|
+
if (image) imagesByPath.push(file);
|
|
3992
|
+
continue;
|
|
3993
|
+
}
|
|
3994
|
+
if (isText(mime) && caps.embeddedContext && size <= MAX_EMBED_TEXT_BYTES && inlineBytes + size <= MAX_INLINE_TOTAL_BYTES) {
|
|
3995
|
+
try {
|
|
3996
|
+
const body = await readFile10(file, "utf8");
|
|
3997
|
+
media.push({ type: "resource", resource: { uri: pathToFileURL(file).href, mimeType: mime, text: body } });
|
|
3998
|
+
inlineBytes += size;
|
|
3999
|
+
continue;
|
|
4000
|
+
} catch {
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
byPath(file, mime, size, "Attached file");
|
|
4004
|
+
}
|
|
4005
|
+
const fullText = [text, ...pathLines.length ? [pathLines.join("\n")] : []].filter(Boolean).join("\n\n");
|
|
4006
|
+
const blocks = [];
|
|
4007
|
+
if (fullText) blocks.push({ type: "text", text: fullText });
|
|
4008
|
+
blocks.push(...media, ...links);
|
|
4009
|
+
return { blocks, imagesByPath, warnings };
|
|
4010
|
+
}
|
|
4011
|
+
var MAX_INLINE_MEDIA_BYTES, MAX_INLINE_TOTAL_BYTES, MAX_EMBED_TEXT_BYTES, isText;
|
|
4012
|
+
var init_prompt = __esm({
|
|
4013
|
+
"daemon/src/providers/acp/prompt.ts"() {
|
|
4014
|
+
init_attachmentRefs();
|
|
4015
|
+
init_attachmentKinds();
|
|
4016
|
+
MAX_INLINE_MEDIA_BYTES = 10 * 1024 * 1024;
|
|
4017
|
+
MAX_INLINE_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
4018
|
+
MAX_EMBED_TEXT_BYTES = 1024 * 1024;
|
|
4019
|
+
isText = (mime) => attachmentKind(mime) === "text";
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
|
|
3260
4023
|
// daemon/src/providers/acp/session.ts
|
|
3261
4024
|
import { client, methods as methods2, PROTOCOL_VERSION } from "@agentclientprotocol/sdk";
|
|
3262
4025
|
var AcpSession;
|
|
@@ -3265,14 +4028,20 @@ var init_session3 = __esm({
|
|
|
3265
4028
|
init_slash();
|
|
3266
4029
|
init_queue();
|
|
3267
4030
|
init_bin();
|
|
4031
|
+
init_launch();
|
|
3268
4032
|
init_spawn();
|
|
3269
4033
|
init_clientHandlers();
|
|
3270
4034
|
init_mapUpdate();
|
|
4035
|
+
init_promptCaps();
|
|
4036
|
+
init_sessionCaps();
|
|
4037
|
+
init_prompt();
|
|
4038
|
+
init_i18n();
|
|
3271
4039
|
init_log();
|
|
3272
4040
|
AcpSession = class {
|
|
3273
4041
|
constructor(o) {
|
|
3274
4042
|
this.o = o;
|
|
3275
4043
|
this.events = this.q;
|
|
4044
|
+
this.startModel = o.model;
|
|
3276
4045
|
this.cursorFast = o.cursorFast ?? false;
|
|
3277
4046
|
this.cursorAutoReview = false;
|
|
3278
4047
|
this.commandsReady = new Promise((r) => this.settleCommands = r);
|
|
@@ -3300,22 +4069,19 @@ var init_session3 = __esm({
|
|
|
3300
4069
|
slashCommands = [];
|
|
3301
4070
|
activeTurnId = null;
|
|
3302
4071
|
configOptions = null;
|
|
4072
|
+
// initialize → agentCapabilities.promptCapabilities (ACP baseline until initialize answers: text + resource_link)
|
|
4073
|
+
promptCaps = { image: false, audio: false, embeddedContext: false };
|
|
4074
|
+
initCaps = this.promptCaps;
|
|
4075
|
+
startModel = null;
|
|
3303
4076
|
wakePump = null;
|
|
3304
4077
|
// listCommands waits for the first batch of commands (or confirmation there are none), following Paseo's waitForInitialCommands
|
|
3305
4078
|
commandsReady;
|
|
3306
4079
|
settleCommands;
|
|
3307
4080
|
async boot() {
|
|
3308
|
-
const bin = this.o.launch ? {
|
|
3309
|
-
ok: true,
|
|
3310
|
-
cmd: this.o.launch.cmd,
|
|
3311
|
-
prefixArgs: this.o.launch.prefixArgs,
|
|
3312
|
-
display: this.o.launch.cmd,
|
|
3313
|
-
via: "custom",
|
|
3314
|
-
binName: "custom"
|
|
3315
|
-
} : await resolveFirstBin(this.o.binCandidates?.length ? this.o.binCandidates : [this.o.binName]);
|
|
4081
|
+
const bin = this.o.launch ? await resolveLaunch(this.o.launch).then((r) => r.ok ? { ...r, binName: "custom" } : r) : await resolveFirstBin(this.o.binCandidates?.length ? this.o.binCandidates : [this.o.binName]);
|
|
3316
4082
|
if (!bin.ok) throw new Error(bin.reason);
|
|
3317
4083
|
try {
|
|
3318
|
-
this.child = spawnResolved(bin, this.o.acpArgs, { cwd: this.o.cwd });
|
|
4084
|
+
this.child = spawnResolved(bin, launchArgv(this.o.acpArgs, this.o.launch), { cwd: this.o.cwd });
|
|
3319
4085
|
this.child.stderr?.on("data", (d) => {
|
|
3320
4086
|
const t = d.toString().trim();
|
|
3321
4087
|
if (t) log("info", `[${this.o.binName} acp] ${t.slice(0, 500)}`);
|
|
@@ -3323,37 +4089,50 @@ var init_session3 = __esm({
|
|
|
3323
4089
|
this.child.on("error", (e) => this.failInit(e));
|
|
3324
4090
|
this.child.on("close", (code) => {
|
|
3325
4091
|
if (!this.closed && code !== 0) {
|
|
3326
|
-
this.q.push({ type: "notice", level: "error", message:
|
|
4092
|
+
this.q.push({ type: "notice", level: "error", message: tr("acp.exited", { name: this.o.binName, code: String(code) }) });
|
|
3327
4093
|
}
|
|
3328
4094
|
});
|
|
3329
4095
|
const app = client({ name: "magpie-agent-console" });
|
|
3330
4096
|
registerAcpClientHandlers(app, {
|
|
3331
4097
|
push: (ev) => this.q.push(ev),
|
|
3332
|
-
waitFor: (
|
|
4098
|
+
waitFor: (id2) => this.waitPerm(id2),
|
|
3333
4099
|
autoAcceptPermissions: () => this.cursorAutoReview
|
|
3334
4100
|
});
|
|
3335
4101
|
const stream = acpStreamFromChild(this.child);
|
|
3336
4102
|
this.conn = app.connect(stream);
|
|
3337
4103
|
this.ctx = this.conn.agent;
|
|
3338
|
-
await this.ctx.request(methods2.agent.initialize, {
|
|
4104
|
+
const initRes = await this.ctx.request(methods2.agent.initialize, {
|
|
3339
4105
|
protocolVersion: PROTOCOL_VERSION,
|
|
3340
4106
|
// Cursor: declare support for the parameterized model picker (as Paseo does); only then does it expose levels / Fast as separate config options
|
|
3341
4107
|
clientCapabilities: this.o.flavor === "cursor" ? { _meta: { parameterizedModelPicker: true } } : {},
|
|
3342
4108
|
clientInfo: { name: "Agent Console", version: this.o.appVersion }
|
|
3343
4109
|
});
|
|
4110
|
+
this.promptCaps = promptCapsFromInitialize(initRes);
|
|
4111
|
+
this.initCaps = this.promptCaps;
|
|
4112
|
+
rememberPromptCaps(this.o.providerId, this.promptCaps);
|
|
3344
4113
|
const cwd = absCwd(this.o.cwd);
|
|
3345
4114
|
const ctx = this.ctx;
|
|
3346
4115
|
if (!ctx) throw new Error("ACP \u672A\u8FDE\u63A5");
|
|
3347
4116
|
if (this.o.resumeId) {
|
|
3348
|
-
const
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
4117
|
+
const how = pickReconnect(sessionOpenFromInitialize(initRes));
|
|
4118
|
+
if (how === "load" || how === "resume") {
|
|
4119
|
+
const method = how === "load" ? methods2.agent.session.load : methods2.agent.session.resume;
|
|
4120
|
+
const restored = await ctx.request(method, {
|
|
4121
|
+
sessionId: this.o.resumeId,
|
|
4122
|
+
cwd,
|
|
4123
|
+
mcpServers: []
|
|
4124
|
+
});
|
|
4125
|
+
this.active = ctx.attachSession({
|
|
4126
|
+
sessionId: this.o.resumeId,
|
|
4127
|
+
modes: restored.modes,
|
|
4128
|
+
configOptions: restored.configOptions
|
|
4129
|
+
});
|
|
4130
|
+
if (Array.isArray(restored.configOptions)) this.configOptions = restored.configOptions;
|
|
4131
|
+
} else {
|
|
4132
|
+
this.active = await ctx.buildSession(cwd).start();
|
|
4133
|
+
const agent = this.o.providerName ?? this.o.binName;
|
|
4134
|
+
this.q.push({ type: "notice", level: "warn", message: tr("notice.acpSessionNotRestored", { agent }) });
|
|
4135
|
+
}
|
|
3357
4136
|
} else {
|
|
3358
4137
|
this.active = await ctx.buildSession(cwd).start();
|
|
3359
4138
|
}
|
|
@@ -3441,9 +4220,9 @@ var init_session3 = __esm({
|
|
|
3441
4220
|
}
|
|
3442
4221
|
return (this.active?.modes?.availableModes ?? []).map((m) => ({ id: m.id, name: m.name, configId: null }));
|
|
3443
4222
|
}
|
|
3444
|
-
resolveAcpModeId(
|
|
4223
|
+
resolveAcpModeId(mode2) {
|
|
3445
4224
|
if (this.o.modeMap) {
|
|
3446
|
-
const want = this.o.modeMap[
|
|
4225
|
+
const want = this.o.modeMap[mode2];
|
|
3447
4226
|
if (!want) return null;
|
|
3448
4227
|
const hit = this.modeChoices().find(
|
|
3449
4228
|
(m) => m.id === want || m.id.endsWith(`#${want}`) || m.name.toLowerCase() === want
|
|
@@ -3452,24 +4231,24 @@ var init_session3 = __esm({
|
|
|
3452
4231
|
}
|
|
3453
4232
|
if (this.o.flavor === "cursor") {
|
|
3454
4233
|
const map = { default: "agent", plan: "plan", ask: "ask" };
|
|
3455
|
-
const want = map[
|
|
4234
|
+
const want = map[mode2] ?? "agent";
|
|
3456
4235
|
const avail = this.active?.modes?.availableModes ?? [];
|
|
3457
4236
|
const hit = avail.find((m) => m.id === want || m.name.toLowerCase() === want);
|
|
3458
4237
|
return hit?.id ?? want;
|
|
3459
4238
|
}
|
|
3460
|
-
return
|
|
4239
|
+
return mode2 === "plan" ? "plan" : "default";
|
|
3461
4240
|
}
|
|
3462
|
-
async applySessionMode(
|
|
4241
|
+
async applySessionMode(mode2) {
|
|
3463
4242
|
if (!this.ctx || !this.active) return;
|
|
3464
4243
|
const hasAllowAll = this.currentConfig().some((o) => o.id === "allow_all");
|
|
3465
|
-
if (this.o.modeMap?.[
|
|
4244
|
+
if (this.o.modeMap?.[mode2] === "allow-all") {
|
|
3466
4245
|
const agentId = this.modeChoices().find((m) => m.id === "agent" || m.id.endsWith("#agent"));
|
|
3467
4246
|
if (agentId?.configId) await this.setOption(agentId.configId, agentId.id);
|
|
3468
4247
|
if (hasAllowAll) await this.setOption("allow_all", "on");
|
|
3469
4248
|
return;
|
|
3470
4249
|
}
|
|
3471
4250
|
if (hasAllowAll && this.o.modeMap) await this.setOption("allow_all", "off");
|
|
3472
|
-
const modeId = this.resolveAcpModeId(
|
|
4251
|
+
const modeId = this.resolveAcpModeId(mode2);
|
|
3473
4252
|
if (!modeId) return;
|
|
3474
4253
|
try {
|
|
3475
4254
|
const viaConfig = this.modeChoices().find((m) => m.id === modeId)?.configId;
|
|
@@ -3482,7 +4261,7 @@ var init_session3 = __esm({
|
|
|
3482
4261
|
modeId
|
|
3483
4262
|
});
|
|
3484
4263
|
} catch (e) {
|
|
3485
|
-
log("info", `ACP setMode(${
|
|
4264
|
+
log("info", `ACP setMode(${mode2}): ${String(e)}`);
|
|
3486
4265
|
}
|
|
3487
4266
|
}
|
|
3488
4267
|
/**
|
|
@@ -3562,19 +4341,32 @@ var init_session3 = __esm({
|
|
|
3562
4341
|
effort: this.o.effort
|
|
3563
4342
|
});
|
|
3564
4343
|
const text = input.text.trim();
|
|
3565
|
-
|
|
4344
|
+
const attachments = input.images ?? [];
|
|
4345
|
+
if (!text && !attachments.length) {
|
|
4346
|
+
this.running = false;
|
|
4347
|
+
this.q.push({ type: "turn_ended", turnId: this.turnId, status: "completed" });
|
|
4348
|
+
return;
|
|
4349
|
+
}
|
|
4350
|
+
const built = await buildAcpPrompt(text, attachments, this.promptCaps);
|
|
4351
|
+
const agent = this.o.providerName ?? this.o.binName;
|
|
4352
|
+
if (built.imagesByPath.length && !this.promptCaps.image) {
|
|
4353
|
+
const switched = this.o.imagesFollowStartModel && this.initCaps.image && this.o.model !== this.startModel;
|
|
4354
|
+
this.q.push({ type: "notice", level: "warn", message: tr(switched ? "notice.acpImageByPathSwitched" : "notice.acpImageByPath", { agent, paths: built.imagesByPath.join(", ") }) });
|
|
4355
|
+
}
|
|
4356
|
+
for (const w of built.warnings) this.q.push({ type: "notice", level: "warn", message: tr("notice.attachmentIssue", { detail: w }) });
|
|
4357
|
+
if (!built.blocks.length) {
|
|
3566
4358
|
this.running = false;
|
|
3567
4359
|
this.q.push({ type: "turn_ended", turnId: this.turnId, status: "completed" });
|
|
3568
4360
|
return;
|
|
3569
4361
|
}
|
|
3570
|
-
void this.runTurn(
|
|
4362
|
+
void this.runTurn(built.blocks);
|
|
3571
4363
|
}
|
|
3572
4364
|
// Send this turn's prompt; reading updates is the job of the persistent pumpUpdates() (following Paseo: session/update is handled at session level)
|
|
3573
|
-
async runTurn(
|
|
4365
|
+
async runTurn(prompt) {
|
|
3574
4366
|
this.activeTurnId = this.turnId;
|
|
3575
4367
|
this.wakePump?.();
|
|
3576
4368
|
this.wakePump = null;
|
|
3577
|
-
this.active.prompt(
|
|
4369
|
+
this.active.prompt(prompt).catch(() => {
|
|
3578
4370
|
});
|
|
3579
4371
|
}
|
|
3580
4372
|
endTurn(ev) {
|
|
@@ -3628,12 +4420,15 @@ var init_session3 = __esm({
|
|
|
3628
4420
|
async setModel(model) {
|
|
3629
4421
|
this.o.model = model;
|
|
3630
4422
|
await this.ready();
|
|
4423
|
+
if (this.o.imagesFollowStartModel) {
|
|
4424
|
+
this.promptCaps = model === this.startModel ? this.initCaps : { ...this.initCaps, image: false };
|
|
4425
|
+
}
|
|
3631
4426
|
await this.applySessionModel(model);
|
|
3632
4427
|
}
|
|
3633
|
-
async setMode(
|
|
3634
|
-
this.o.mode =
|
|
4428
|
+
async setMode(mode2) {
|
|
4429
|
+
this.o.mode = mode2;
|
|
3635
4430
|
await this.ready();
|
|
3636
|
-
await this.applySessionMode(
|
|
4431
|
+
await this.applySessionMode(mode2);
|
|
3637
4432
|
}
|
|
3638
4433
|
async setEffort(effort) {
|
|
3639
4434
|
this.o.effort = effort;
|
|
@@ -3706,34 +4501,38 @@ function createAcpProvider(cfg2) {
|
|
|
3706
4501
|
return {
|
|
3707
4502
|
id: cfg2.id,
|
|
3708
4503
|
name: cfg2.name,
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
const
|
|
4504
|
+
// images is what the Agent's own initialize says (probe at warm-up / providers.list, or any session); until then unknown
|
|
4505
|
+
get capabilities() {
|
|
4506
|
+
const learned2 = knownPromptCaps(cfg2.id);
|
|
4507
|
+
return learned2 ? { ...caps, images: learned2.image, imagesKnown: true } : { ...caps, images: false, imagesKnown: false };
|
|
4508
|
+
},
|
|
4509
|
+
async detect(launch) {
|
|
4510
|
+
const r = launch ? await resolveLaunch(launch) : await resolveProviderBin(cfg2);
|
|
3712
4511
|
if (!r.ok) {
|
|
3713
4512
|
return { available: false, reason: r.reason, installHint: cfg2.installHint };
|
|
3714
4513
|
}
|
|
3715
4514
|
let version;
|
|
4515
|
+
const versionArgs = cfg2.id === "devin" ? ["version"] : ["cursor", "opencode"].includes(cfg2.id) ? ["--version"] : null;
|
|
3716
4516
|
try {
|
|
3717
|
-
if (
|
|
3718
|
-
version = (await runVersion3(r, [
|
|
3719
|
-
} else if (cfg2.id === "devin") {
|
|
3720
|
-
version = (await runVersion3(r, ["version"])).split(/\r?\n/)[0]?.trim();
|
|
3721
|
-
} else if (cfg2.id === "opencode") {
|
|
3722
|
-
version = (await runVersion3(r, ["--version"])).split(/\r?\n/)[0]?.trim();
|
|
4517
|
+
if (versionArgs) {
|
|
4518
|
+
version = (await runVersion3(r, [...launch?.prefixArgs ?? [], ...versionArgs])).split(/\r?\n/)[0]?.trim();
|
|
3723
4519
|
}
|
|
3724
4520
|
} catch {
|
|
3725
4521
|
}
|
|
3726
4522
|
return { available: true, bin: r.display, version };
|
|
3727
4523
|
},
|
|
3728
|
-
async listModels() {
|
|
3729
|
-
const
|
|
3730
|
-
if (!
|
|
3731
|
-
|
|
3732
|
-
|
|
4524
|
+
async listModels(launch) {
|
|
4525
|
+
const resolved = launch ? await resolveLaunch(launch) : await resolveProviderBin(cfg2);
|
|
4526
|
+
if (!resolved.ok) return [{ id: "default", name: PLACEHOLDER_MODEL_NAME, isDefault: true }];
|
|
4527
|
+
const r = launch?.prefixArgs?.length && resolved.via !== "cmd-shim" ? { ...resolved, prefixArgs: [...resolved.prefixArgs, ...launch.prefixArgs] } : resolved;
|
|
4528
|
+
if (cfg2.fetchModels) return cfg2.fetchModels(r, launch?.args ?? []);
|
|
4529
|
+
return [{ id: "default", name: PLACEHOLDER_MODEL_NAME, isDefault: true }];
|
|
3733
4530
|
},
|
|
3734
4531
|
async createSession(o) {
|
|
3735
4532
|
return new AcpSession({
|
|
3736
4533
|
threadId: o.threadId,
|
|
4534
|
+
providerId: cfg2.id,
|
|
4535
|
+
providerName: cfg2.name,
|
|
3737
4536
|
cwd: o.cwd,
|
|
3738
4537
|
model: o.model,
|
|
3739
4538
|
mode: o.mode,
|
|
@@ -3741,7 +4540,8 @@ function createAcpProvider(cfg2) {
|
|
|
3741
4540
|
resumeId: o.resumeId,
|
|
3742
4541
|
binName: cfg2.binName,
|
|
3743
4542
|
binCandidates: cfg2.binCandidates ?? [cfg2.binName],
|
|
3744
|
-
acpArgs: cfg2.acpArgs,
|
|
4543
|
+
acpArgs: cfg2.argsFor ? await cfg2.argsFor(o.model) : cfg2.acpArgs,
|
|
4544
|
+
imagesFollowStartModel: cfg2.imagesFollowStartModel,
|
|
3745
4545
|
appVersion: cfg2.appVersion ?? "0.0.1",
|
|
3746
4546
|
flavor: cfg2.flavor ?? "generic",
|
|
3747
4547
|
modeMap: cfg2.modeMap,
|
|
@@ -3757,7 +4557,10 @@ var init_provider3 = __esm({
|
|
|
3757
4557
|
"daemon/src/providers/acp/provider.ts"() {
|
|
3758
4558
|
init_bin();
|
|
3759
4559
|
init_bin();
|
|
4560
|
+
init_launch();
|
|
4561
|
+
init_types();
|
|
3760
4562
|
init_session3();
|
|
4563
|
+
init_promptCaps();
|
|
3761
4564
|
acpCapabilities = {
|
|
3762
4565
|
resume: true,
|
|
3763
4566
|
interrupt: true,
|
|
@@ -3767,7 +4570,8 @@ var init_provider3 = __esm({
|
|
|
3767
4570
|
questions: false,
|
|
3768
4571
|
cost: false,
|
|
3769
4572
|
effort: [],
|
|
3770
|
-
modes: ["plan", "default", "acceptEdits", "bypassPermissions"]
|
|
4573
|
+
modes: ["plan", "default", "acceptEdits", "bypassPermissions"],
|
|
4574
|
+
deltasOnly: true
|
|
3771
4575
|
};
|
|
3772
4576
|
cursorAcpCapabilities = {
|
|
3773
4577
|
...acpCapabilities,
|
|
@@ -3777,8 +4581,53 @@ var init_provider3 = __esm({
|
|
|
3777
4581
|
}
|
|
3778
4582
|
});
|
|
3779
4583
|
|
|
3780
|
-
// daemon/src/providers/acp/
|
|
4584
|
+
// daemon/src/providers/acp/dsh.ts
|
|
4585
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4586
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
3781
4587
|
import { tmpdir } from "node:os";
|
|
4588
|
+
import { join as join10 } from "node:path";
|
|
4589
|
+
function dshRoute(model) {
|
|
4590
|
+
if (!model) return null;
|
|
4591
|
+
try {
|
|
4592
|
+
const v = JSON.parse(model);
|
|
4593
|
+
if (Array.isArray(v) && v.length === 2 && typeof v[0] === "string" && typeof v[1] === "string" && v[0] && v[1]) {
|
|
4594
|
+
return { provider: v[0], model: v[1] };
|
|
4595
|
+
}
|
|
4596
|
+
} catch {
|
|
4597
|
+
}
|
|
4598
|
+
return null;
|
|
4599
|
+
}
|
|
4600
|
+
function dshRoutePatch(route) {
|
|
4601
|
+
return [
|
|
4602
|
+
"# Agent Console: the route dsh's ACP starts on, so initialize advertises image input for it",
|
|
4603
|
+
"- id: acp",
|
|
4604
|
+
" config:",
|
|
4605
|
+
` provider: ${JSON.stringify(route.provider)}`,
|
|
4606
|
+
` model: ${JSON.stringify(route.model)}`,
|
|
4607
|
+
""
|
|
4608
|
+
].join("\n");
|
|
4609
|
+
}
|
|
4610
|
+
async function dshAcpArgs(model, dir = join10(tmpdir(), "agent-console-dsh")) {
|
|
4611
|
+
const route = dshRoute(model);
|
|
4612
|
+
if (!route) return [...DSH_ACP_ARGS];
|
|
4613
|
+
const body = dshRoutePatch(route);
|
|
4614
|
+
const file = join10(dir, `acp-route-${createHash2("sha256").update(body).digest("hex").slice(0, 16)}.yml`);
|
|
4615
|
+
await mkdir2(dir, { recursive: true });
|
|
4616
|
+
await writeFile3(file, body, { mode: 384 });
|
|
4617
|
+
return [...DSH_ACP_ARGS, "--patch", file];
|
|
4618
|
+
}
|
|
4619
|
+
function dshDefaultModel(models2, current) {
|
|
4620
|
+
return models2.find((m) => /magpie/i.test(m.id))?.id ?? current ?? models2[0]?.id ?? null;
|
|
4621
|
+
}
|
|
4622
|
+
var DSH_ACP_ARGS;
|
|
4623
|
+
var init_dsh = __esm({
|
|
4624
|
+
"daemon/src/providers/acp/dsh.ts"() {
|
|
4625
|
+
DSH_ACP_ARGS = ["--profile", "acp"];
|
|
4626
|
+
}
|
|
4627
|
+
});
|
|
4628
|
+
|
|
4629
|
+
// daemon/src/providers/acp/probe.ts
|
|
4630
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
3782
4631
|
function flatten(opts) {
|
|
3783
4632
|
const out = [];
|
|
3784
4633
|
for (const o of opts ?? []) {
|
|
@@ -3804,12 +4653,15 @@ function parseAcpSession(res) {
|
|
|
3804
4653
|
const currentThinking = typeof thoughtOpt?.currentValue === "string" ? thoughtOpt.currentValue : null;
|
|
3805
4654
|
return { models: models2, currentModel, thinking, currentThinking };
|
|
3806
4655
|
}
|
|
3807
|
-
function probeAcp(r, acpArgs, timeoutMs = 3e4) {
|
|
3808
|
-
return probeAcpRaw(r, acpArgs, timeoutMs).then((x) =>
|
|
4656
|
+
function probeAcp(r, acpArgs, timeoutMs = 3e4, providerId) {
|
|
4657
|
+
return probeAcpRaw(r, acpArgs, timeoutMs).then((x) => {
|
|
4658
|
+
rememberPromptCaps(providerId, x.promptCaps);
|
|
4659
|
+
return x.parsed;
|
|
4660
|
+
});
|
|
3809
4661
|
}
|
|
3810
4662
|
function probeAcpRaw(r, acpArgs, timeoutMs = 3e4, extMethod) {
|
|
3811
4663
|
return new Promise((resolvePromise, reject) => {
|
|
3812
|
-
const child = spawnResolved(r, acpArgs, { cwd:
|
|
4664
|
+
const child = spawnResolved(r, acpArgs, { cwd: tmpdir2() });
|
|
3813
4665
|
let buf = "";
|
|
3814
4666
|
let nextId = 0;
|
|
3815
4667
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -3825,9 +4677,9 @@ function probeAcpRaw(r, acpArgs, timeoutMs = 3e4, extMethod) {
|
|
|
3825
4677
|
};
|
|
3826
4678
|
const timer = setTimeout(() => finish(new Error("ACP \u63A2\u6D4B\u8D85\u65F6")), timeoutMs);
|
|
3827
4679
|
const send2 = (method, params) => new Promise((res, rej) => {
|
|
3828
|
-
const
|
|
3829
|
-
pending.set(
|
|
3830
|
-
child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
|
|
4680
|
+
const id2 = ++nextId;
|
|
4681
|
+
pending.set(id2, { res, rej });
|
|
4682
|
+
child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", id: id2, method, params }) + "\n");
|
|
3831
4683
|
});
|
|
3832
4684
|
child.on("error", (e) => finish(e));
|
|
3833
4685
|
child.on("close", () => finish(new Error("ACP \u8FDB\u7A0B\u63D0\u524D\u9000\u51FA")));
|
|
@@ -3858,11 +4710,11 @@ function probeAcpRaw(r, acpArgs, timeoutMs = 3e4, extMethod) {
|
|
|
3858
4710
|
});
|
|
3859
4711
|
void (async () => {
|
|
3860
4712
|
try {
|
|
3861
|
-
await send2("initialize", { protocolVersion: 1, clientCapabilities: {}, clientInfo: { name: "Agent Console", version: "probe" } });
|
|
3862
|
-
const res = await send2("session/new", { cwd:
|
|
4713
|
+
const init = await send2("initialize", { protocolVersion: 1, clientCapabilities: {}, clientInfo: { name: "Agent Console", version: "probe" } });
|
|
4714
|
+
const res = await send2("session/new", { cwd: tmpdir2(), mcpServers: [] });
|
|
3863
4715
|
let ext = null;
|
|
3864
4716
|
if (extMethod) ext = await send2(extMethod, {}).catch(() => null);
|
|
3865
|
-
finish(null, { parsed: parseAcpSession(res ?? {}), ext });
|
|
4717
|
+
finish(null, { parsed: parseAcpSession(res ?? {}), ext, promptCaps: promptCapsFromInitialize(init) });
|
|
3866
4718
|
} catch (e) {
|
|
3867
4719
|
finish(e instanceof Error ? e : new Error(String(e)));
|
|
3868
4720
|
}
|
|
@@ -3882,13 +4734,13 @@ function cursorCatalogToModels(catalog, currentModel) {
|
|
|
3882
4734
|
const out = [];
|
|
3883
4735
|
for (const m of catalog.models ?? []) {
|
|
3884
4736
|
if (typeof m.value !== "string" || !m.value) continue;
|
|
3885
|
-
const
|
|
4737
|
+
const id2 = m.value.replace(/\[.*\]$/u, "");
|
|
3886
4738
|
const thought = (m.configOptions ?? []).find((o) => o.category === "thought_level");
|
|
3887
4739
|
const levels = flatten(thought?.options);
|
|
3888
4740
|
out.push({
|
|
3889
|
-
id,
|
|
3890
|
-
name: typeof m.name === "string" && m.name ? m.name :
|
|
3891
|
-
isDefault:
|
|
4741
|
+
id: id2,
|
|
4742
|
+
name: typeof m.name === "string" && m.name ? m.name : id2,
|
|
4743
|
+
isDefault: id2 === currentBase,
|
|
3892
4744
|
...thought && levels.length ? {
|
|
3893
4745
|
thinkingOptions: levels.map((l) => ({ id: l.id, label: EFFORT_LABEL[l.id] ?? l.name })),
|
|
3894
4746
|
defaultThinking: typeof thought.currentValue === "string" ? thought.currentValue : void 0,
|
|
@@ -3898,10 +4750,11 @@ function cursorCatalogToModels(catalog, currentModel) {
|
|
|
3898
4750
|
}
|
|
3899
4751
|
return out;
|
|
3900
4752
|
}
|
|
3901
|
-
function cursorProbeModels(acpArgs) {
|
|
3902
|
-
return async (r) => {
|
|
4753
|
+
function cursorProbeModels(acpArgs, providerId) {
|
|
4754
|
+
return async (r, launchArgs = []) => {
|
|
3903
4755
|
try {
|
|
3904
|
-
const { parsed, ext } = await probeAcpRaw(r, acpArgs, 4e4, "cursor/list_available_models");
|
|
4756
|
+
const { parsed, ext, promptCaps } = await probeAcpRaw(r, [...acpArgs, ...launchArgs], 4e4, "cursor/list_available_models");
|
|
4757
|
+
rememberPromptCaps(providerId, promptCaps);
|
|
3905
4758
|
const fromCatalog = ext ? cursorCatalogToModels(ext, parsed.currentModel) : [];
|
|
3906
4759
|
return fromCatalog.length ? fromCatalog : probeToModels(parsed);
|
|
3907
4760
|
} catch (e) {
|
|
@@ -3910,10 +4763,27 @@ function cursorProbeModels(acpArgs) {
|
|
|
3910
4763
|
}
|
|
3911
4764
|
};
|
|
3912
4765
|
}
|
|
3913
|
-
function
|
|
3914
|
-
return async (r) => {
|
|
4766
|
+
function dshProbeModels(providerId) {
|
|
4767
|
+
return async (r, launchArgs = []) => {
|
|
4768
|
+
try {
|
|
4769
|
+
const first = await probeAcpRaw(r, [...DSH_ACP_ARGS, ...launchArgs], 3e4);
|
|
4770
|
+
let caps = first.promptCaps;
|
|
4771
|
+
const start = dshDefaultModel(first.parsed.models, first.parsed.currentModel);
|
|
4772
|
+
if (start && start !== first.parsed.currentModel && dshRoute(start)) {
|
|
4773
|
+
caps = await probeAcpRaw(r, [...await dshAcpArgs(start), ...launchArgs], 3e4).then((x) => x.promptCaps, () => caps);
|
|
4774
|
+
}
|
|
4775
|
+
rememberPromptCaps(providerId, caps);
|
|
4776
|
+
return probeToModels(first.parsed);
|
|
4777
|
+
} catch (e) {
|
|
4778
|
+
log("warn", "ACP \u63A2\u6D4B\u6A21\u578B\u5931\u8D25", { err: String(e) });
|
|
4779
|
+
return [];
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4782
|
+
}
|
|
4783
|
+
function acpProbeModels(acpArgs, providerId) {
|
|
4784
|
+
return async (r, launchArgs = []) => {
|
|
3915
4785
|
try {
|
|
3916
|
-
return probeToModels(await probeAcp(r, acpArgs));
|
|
4786
|
+
return probeToModels(await probeAcp(r, [...acpArgs, ...launchArgs], 3e4, providerId));
|
|
3917
4787
|
} catch (e) {
|
|
3918
4788
|
log("warn", "ACP \u63A2\u6D4B\u6A21\u578B\u5931\u8D25", { err: String(e) });
|
|
3919
4789
|
return [];
|
|
@@ -3925,6 +4795,8 @@ var init_probe = __esm({
|
|
|
3925
4795
|
"daemon/src/providers/acp/probe.ts"() {
|
|
3926
4796
|
init_spawn();
|
|
3927
4797
|
init_log();
|
|
4798
|
+
init_promptCaps();
|
|
4799
|
+
init_dsh();
|
|
3928
4800
|
EFFORT_LABEL = {
|
|
3929
4801
|
none: "None",
|
|
3930
4802
|
minimal: "Minimal",
|
|
@@ -3956,13 +4828,13 @@ function acpConfigForEntry(entry, appVersion) {
|
|
|
3956
4828
|
flavor: "cursor",
|
|
3957
4829
|
capabilities: cursorAcpCapabilities,
|
|
3958
4830
|
// Each model's own thinking levels come from cursor/list_available_models (as Paseo does)
|
|
3959
|
-
fetchModels: cursorProbeModels(["acp"])
|
|
4831
|
+
fetchModels: cursorProbeModels(["acp"], entry.id)
|
|
3960
4832
|
};
|
|
3961
4833
|
case "devin":
|
|
3962
4834
|
return {
|
|
3963
4835
|
...base,
|
|
3964
4836
|
acpArgs: ["acp"],
|
|
3965
|
-
fetchModels: acpProbeModels(["acp"]),
|
|
4837
|
+
fetchModels: acpProbeModels(["acp"], entry.id),
|
|
3966
4838
|
modeMap: { plan: "plan", default: "ask", acceptEdits: "accept-edits", bypassPermissions: "bypass" },
|
|
3967
4839
|
modeLabels: { plan: "Plan", default: "Ask", acceptEdits: "Code", bypassPermissions: "Bypass Permissions" }
|
|
3968
4840
|
};
|
|
@@ -3970,7 +4842,7 @@ function acpConfigForEntry(entry, appVersion) {
|
|
|
3970
4842
|
return {
|
|
3971
4843
|
...base,
|
|
3972
4844
|
acpArgs: ["acp"],
|
|
3973
|
-
fetchModels: acpProbeModels(["acp"]),
|
|
4845
|
+
fetchModels: acpProbeModels(["acp"], entry.id),
|
|
3974
4846
|
modeMap: { plan: "plan", default: "build" },
|
|
3975
4847
|
modeLabels: { plan: "Plan", default: "Build" }
|
|
3976
4848
|
};
|
|
@@ -3978,14 +4850,20 @@ function acpConfigForEntry(entry, appVersion) {
|
|
|
3978
4850
|
return {
|
|
3979
4851
|
...base,
|
|
3980
4852
|
acpArgs: ["--acp"],
|
|
3981
|
-
fetchModels: acpProbeModels(["--acp"]),
|
|
4853
|
+
fetchModels: acpProbeModels(["--acp"], entry.id),
|
|
3982
4854
|
modeMap: { plan: "plan", default: "agent", bypassPermissions: "allow-all" },
|
|
3983
4855
|
modeLabels: { plan: "Plan", default: "Agent", bypassPermissions: "Allow All" }
|
|
3984
4856
|
};
|
|
3985
4857
|
case "pi":
|
|
3986
|
-
return { ...base, acpArgs: [], fetchModels: acpProbeModels([]), modeMap: { default: "" } };
|
|
4858
|
+
return { ...base, acpArgs: [], fetchModels: acpProbeModels([], entry.id), modeMap: { default: "" } };
|
|
3987
4859
|
case "dsh":
|
|
3988
|
-
return {
|
|
4860
|
+
return {
|
|
4861
|
+
...base,
|
|
4862
|
+
acpArgs: DSH_ACP_ARGS,
|
|
4863
|
+
argsFor: (model) => dshAcpArgs(model),
|
|
4864
|
+
imagesFollowStartModel: true,
|
|
4865
|
+
fetchModels: dshProbeModels(entry.id)
|
|
4866
|
+
};
|
|
3989
4867
|
default:
|
|
3990
4868
|
return null;
|
|
3991
4869
|
}
|
|
@@ -3995,6 +4873,7 @@ var init_specs = __esm({
|
|
|
3995
4873
|
"daemon/src/providers/acp/specs.ts"() {
|
|
3996
4874
|
init_provider3();
|
|
3997
4875
|
init_probe();
|
|
4876
|
+
init_dsh();
|
|
3998
4877
|
ACP_IDS = /* @__PURE__ */ new Set(["cursor", "devin", "opencode", "dsh", "pi", "copilot"]);
|
|
3999
4878
|
}
|
|
4000
4879
|
});
|
|
@@ -4003,8 +4882,8 @@ var init_specs = __esm({
|
|
|
4003
4882
|
function registerProvider(p) {
|
|
4004
4883
|
providers.set(p.id, p);
|
|
4005
4884
|
}
|
|
4006
|
-
function getProvider(
|
|
4007
|
-
return providers.get(
|
|
4885
|
+
function getProvider(id2) {
|
|
4886
|
+
return providers.get(id2);
|
|
4008
4887
|
}
|
|
4009
4888
|
function listProviders() {
|
|
4010
4889
|
return [...providers.values()];
|
|
@@ -4037,10 +4916,10 @@ var init_registry = __esm({
|
|
|
4037
4916
|
});
|
|
4038
4917
|
|
|
4039
4918
|
// daemon/src/providers/enabled.ts
|
|
4040
|
-
function isProviderEnabled(settings,
|
|
4919
|
+
function isProviderEnabled(settings, id2) {
|
|
4041
4920
|
const m = settings.providerEnabled;
|
|
4042
|
-
if (m && typeof m === "object" && typeof m[
|
|
4043
|
-
const entry = catalogById(
|
|
4921
|
+
if (m && typeof m === "object" && typeof m[id2] === "boolean") return m[id2];
|
|
4922
|
+
const entry = catalogById(id2);
|
|
4044
4923
|
if (!entry) return true;
|
|
4045
4924
|
return entry.status === "verified";
|
|
4046
4925
|
}
|
|
@@ -4141,21 +5020,21 @@ var init_pickFolder = __esm({
|
|
|
4141
5020
|
});
|
|
4142
5021
|
|
|
4143
5022
|
// daemon/src/providers/magpieGateway.ts
|
|
4144
|
-
import { readFile as
|
|
4145
|
-
import { homedir as
|
|
4146
|
-
import { join as
|
|
5023
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
5024
|
+
import { homedir as homedir7 } from "node:os";
|
|
5025
|
+
import { join as join11 } from "node:path";
|
|
4147
5026
|
async function readJson(path3) {
|
|
4148
5027
|
try {
|
|
4149
|
-
return JSON.parse(await
|
|
5028
|
+
return JSON.parse(await readFile11(path3, "utf8"));
|
|
4150
5029
|
} catch {
|
|
4151
5030
|
return null;
|
|
4152
5031
|
}
|
|
4153
5032
|
}
|
|
4154
5033
|
async function readApplied() {
|
|
4155
|
-
return await readJson(
|
|
5034
|
+
return await readJson(join11(homedir7(), ".config", "magpie", "applied.json")) ?? {};
|
|
4156
5035
|
}
|
|
4157
5036
|
async function readClaudeEnv() {
|
|
4158
|
-
const s = await readJson(
|
|
5037
|
+
const s = await readJson(join11(homedir7(), ".claude", "settings.json"));
|
|
4159
5038
|
return s?.env ?? {};
|
|
4160
5039
|
}
|
|
4161
5040
|
async function gatewayBase() {
|
|
@@ -4169,7 +5048,7 @@ async function fetchGatewayModelIds() {
|
|
|
4169
5048
|
const res = await fetch(`${base}/v1/models`, { signal: AbortSignal.timeout(5e3) });
|
|
4170
5049
|
if (!res.ok) return [];
|
|
4171
5050
|
const j = await res.json();
|
|
4172
|
-
return (j.data ?? []).map((m) => m.id).filter((
|
|
5051
|
+
return (j.data ?? []).map((m) => m.id).filter((id2) => typeof id2 === "string" && !!id2);
|
|
4173
5052
|
} catch (e) {
|
|
4174
5053
|
log("warn", "\u8BFB\u53D6 magpie \u7F51\u5173\u6A21\u578B\u5217\u8868\u5931\u8D25", { err: String(e) });
|
|
4175
5054
|
return [];
|
|
@@ -4229,7 +5108,7 @@ async function withMagpie(providerId, own) {
|
|
|
4229
5108
|
return {
|
|
4230
5109
|
...m,
|
|
4231
5110
|
name,
|
|
4232
|
-
thinkingOptions: levels.map((
|
|
5111
|
+
thinkingOptions: levels.map((id2) => ({ id: id2, label: EFFORT_LABEL2[id2] ?? id2 })),
|
|
4233
5112
|
defaultThinking: m.isDefault ? def : m.defaultThinking ?? def
|
|
4234
5113
|
};
|
|
4235
5114
|
});
|
|
@@ -4262,46 +5141,53 @@ function withTimeout(p, ms, fb) {
|
|
|
4262
5141
|
function fresh(e, ttl) {
|
|
4263
5142
|
return !!e && Date.now() - e.at < ttl;
|
|
4264
5143
|
}
|
|
4265
|
-
|
|
4266
|
-
|
|
5144
|
+
function cacheKey(p, launch) {
|
|
5145
|
+
return launch ? `${p.id}\0${launchKey(launch)}` : p.id;
|
|
5146
|
+
}
|
|
5147
|
+
async function cachedDetect(p, launch) {
|
|
5148
|
+
const key = cacheKey(p, launch);
|
|
5149
|
+
const hit = detects.get(key);
|
|
4267
5150
|
if (fresh(hit, DETECT_MS)) return hit.value;
|
|
4268
|
-
let job = inflightDetect.get(
|
|
5151
|
+
let job = inflightDetect.get(key);
|
|
4269
5152
|
if (!job) {
|
|
5153
|
+
const timedOut = { available: false, reason: tr("provider.timeout") };
|
|
4270
5154
|
job = withTimeout(
|
|
4271
|
-
p.detect().catch((e) => ({ available: false, reason: String(e?.message ?? e) })),
|
|
5155
|
+
p.detect(launch).catch((e) => ({ available: false, reason: String(e?.message ?? e) })),
|
|
4272
5156
|
STEP_TIMEOUT_MS,
|
|
4273
|
-
|
|
5157
|
+
timedOut
|
|
4274
5158
|
).then((v) => {
|
|
4275
|
-
if (v
|
|
5159
|
+
if (v !== timedOut) detects.set(key, { at: Date.now(), value: v });
|
|
4276
5160
|
return v;
|
|
4277
|
-
}).finally(() => inflightDetect.delete(
|
|
4278
|
-
inflightDetect.set(
|
|
5161
|
+
}).finally(() => inflightDetect.delete(key));
|
|
5162
|
+
inflightDetect.set(key, job);
|
|
4279
5163
|
}
|
|
4280
5164
|
return job;
|
|
4281
5165
|
}
|
|
4282
|
-
async function cachedModels(p) {
|
|
4283
|
-
const
|
|
5166
|
+
async function cachedModels(p, launch) {
|
|
5167
|
+
const key = cacheKey(p, launch);
|
|
5168
|
+
const hit = models.get(key);
|
|
4284
5169
|
if (fresh(hit, MODELS_MS)) return hit.value;
|
|
4285
|
-
let job = inflightModels.get(
|
|
5170
|
+
let job = inflightModels.get(key);
|
|
4286
5171
|
if (!job) {
|
|
4287
|
-
job = withTimeout(p.listModels().catch(() => []), MODELS_TIMEOUT_MS, null).then(async (v) => {
|
|
4288
|
-
if (v === null) return models.get(
|
|
5172
|
+
job = withTimeout(p.listModels(launch).catch(() => []), MODELS_TIMEOUT_MS, null).then(async (v) => {
|
|
5173
|
+
if (v === null) return models.get(key)?.value ?? [];
|
|
4289
5174
|
const list = await withMagpie(p.id, dropPlaceholder(v)).catch(() => dropPlaceholder(v));
|
|
4290
|
-
models.set(
|
|
5175
|
+
models.set(key, { at: Date.now(), value: list });
|
|
4291
5176
|
return list;
|
|
4292
|
-
}).finally(() => inflightModels.delete(
|
|
4293
|
-
inflightModels.set(
|
|
5177
|
+
}).finally(() => inflightModels.delete(key));
|
|
5178
|
+
inflightModels.set(key, job);
|
|
4294
5179
|
}
|
|
4295
5180
|
return job;
|
|
4296
5181
|
}
|
|
4297
5182
|
function dropPlaceholder(list) {
|
|
4298
|
-
if (list.length === 1 && list[0].id === "default" && list[0].name === "\u9ED8\u8BA4") return [];
|
|
5183
|
+
if (list.length === 1 && list[0].id === "default" && (list[0].name === "\u9ED8\u8BA4" || list[0].name === PLACEHOLDER_MODEL_NAME)) return [];
|
|
4299
5184
|
return list;
|
|
4300
5185
|
}
|
|
4301
|
-
function clearProviderCache(
|
|
4302
|
-
if (
|
|
4303
|
-
detects
|
|
4304
|
-
|
|
5186
|
+
function clearProviderCache(id2) {
|
|
5187
|
+
if (id2) {
|
|
5188
|
+
for (const m of [detects, models]) {
|
|
5189
|
+
for (const k of [...m.keys()]) if (k === id2 || k.startsWith(id2 + "\0")) m.delete(k);
|
|
5190
|
+
}
|
|
4305
5191
|
} else {
|
|
4306
5192
|
detects.clear();
|
|
4307
5193
|
models.clear();
|
|
@@ -4310,7 +5196,10 @@ function clearProviderCache(id) {
|
|
|
4310
5196
|
var DETECT_MS, MODELS_MS, STEP_TIMEOUT_MS, MODELS_TIMEOUT_MS, detects, models, inflightDetect, inflightModels;
|
|
4311
5197
|
var init_infoCache = __esm({
|
|
4312
5198
|
"daemon/src/providers/infoCache.ts"() {
|
|
5199
|
+
init_types();
|
|
4313
5200
|
init_magpieGateway();
|
|
5201
|
+
init_launch();
|
|
5202
|
+
init_i18n();
|
|
4314
5203
|
DETECT_MS = 60 * 1e3;
|
|
4315
5204
|
MODELS_MS = 10 * 60 * 1e3;
|
|
4316
5205
|
STEP_TIMEOUT_MS = 15e3;
|
|
@@ -4322,18 +5211,29 @@ var init_infoCache = __esm({
|
|
|
4322
5211
|
}
|
|
4323
5212
|
});
|
|
4324
5213
|
|
|
5214
|
+
// daemon/src/providers/modes.ts
|
|
5215
|
+
function clampMode(modes, mode2) {
|
|
5216
|
+
if (mode2 && modes.includes(mode2)) return mode2;
|
|
5217
|
+
if (modes.includes("default")) return "default";
|
|
5218
|
+
return modes[0] ?? "default";
|
|
5219
|
+
}
|
|
5220
|
+
var init_modes = __esm({
|
|
5221
|
+
"daemon/src/providers/modes.ts"() {
|
|
5222
|
+
}
|
|
5223
|
+
});
|
|
5224
|
+
|
|
4325
5225
|
// daemon/src/ws/methods.ts
|
|
4326
5226
|
import { z } from "zod";
|
|
4327
5227
|
import { existsSync as existsSync8, statSync as statSync4 } from "node:fs";
|
|
4328
|
-
import { mkdir as
|
|
4329
|
-
import { homedir as
|
|
4330
|
-
import { dirname as dirname3, join as
|
|
5228
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
|
|
5229
|
+
import { homedir as homedir8 } from "node:os";
|
|
5230
|
+
import { dirname as dirname3, join as join12, win32 as win32Path } from "node:path";
|
|
4331
5231
|
import { execFile as execFile3 } from "node:child_process";
|
|
4332
5232
|
function wrap(schema, fn) {
|
|
4333
5233
|
return (params, ctx) => {
|
|
4334
5234
|
const r = schema.safeParse(params ?? {});
|
|
4335
5235
|
if (!r.success) {
|
|
4336
|
-
throw new ApiError("bad_params", "
|
|
5236
|
+
throw new ApiError("bad_params", tr("err.badParams", { detail: r.error.issues.map((i) => i.message).join("; ") }));
|
|
4337
5237
|
}
|
|
4338
5238
|
return fn(r.data, ctx);
|
|
4339
5239
|
};
|
|
@@ -4351,10 +5251,18 @@ function registerMethods(gw, svc) {
|
|
|
4351
5251
|
return { ok: true };
|
|
4352
5252
|
}));
|
|
4353
5253
|
gw.method("settings.get", wrap(zEmpty, () => svc.settings.get()));
|
|
4354
|
-
gw.method("settings.set", wrap(
|
|
4355
|
-
|
|
4356
|
-
(
|
|
4357
|
-
|
|
5254
|
+
gw.method("settings.set", wrap(z.object({ patch: z.record(z.string(), z.unknown()) }), async (p) => {
|
|
5255
|
+
const checked = parseSettingsPatch(p.patch);
|
|
5256
|
+
if (!checked.ok) throw new ApiError("bad_params", `Invalid setting: ${checked.error}`);
|
|
5257
|
+
const before = await svc.settings.get();
|
|
5258
|
+
const next = await svc.settings.set(checked.patch);
|
|
5259
|
+
for (const id2 of Object.keys(checked.patch.providerCommand ?? {})) {
|
|
5260
|
+
if (JSON.stringify(before.providerCommand?.[id2] ?? null) !== JSON.stringify(next.providerCommand?.[id2] ?? null)) {
|
|
5261
|
+
clearProviderCache(id2);
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
return next;
|
|
5265
|
+
}));
|
|
4358
5266
|
gw.method("sub.add", wrap(
|
|
4359
5267
|
z.object({ ch: z.string().min(1).max(120), afterSeq: z.number().int().nonnegative().optional() }),
|
|
4360
5268
|
async (p, ctx) => {
|
|
@@ -4383,9 +5291,10 @@ function registerMethods(gw, svc) {
|
|
|
4383
5291
|
listProviders().map(async (p2) => {
|
|
4384
5292
|
const entry = catalogById(p2.id);
|
|
4385
5293
|
const enabled = isProviderEnabled(settings, p2.id);
|
|
5294
|
+
const launch = launchFromSettings(settings, p2.id);
|
|
4386
5295
|
const skip = !enabled || entry?.status === "planned";
|
|
4387
|
-
const det = skip ? { available: false, reason: !enabled ? "
|
|
4388
|
-
const models2 = !skip && det.available ? await cachedModels(p2) : [];
|
|
5296
|
+
const det = skip ? { available: false, reason: !enabled ? tr("provider.disabled") : tr("provider.planned") } : await cachedDetect(p2, launch);
|
|
5297
|
+
const models2 = !skip && det.available ? await cachedModels(p2, launch) : [];
|
|
4389
5298
|
return {
|
|
4390
5299
|
id: p2.id,
|
|
4391
5300
|
name: p2.name,
|
|
@@ -4395,7 +5304,7 @@ function registerMethods(gw, svc) {
|
|
|
4395
5304
|
ready: entry?.status === "verified",
|
|
4396
5305
|
bin: det.bin,
|
|
4397
5306
|
version: det.version,
|
|
4398
|
-
reason: !enabled ? "
|
|
5307
|
+
reason: !enabled ? tr("provider.disabled") : det.reason,
|
|
4399
5308
|
installHint: det.installHint,
|
|
4400
5309
|
capabilities: p2.capabilities,
|
|
4401
5310
|
models: models2
|
|
@@ -4429,7 +5338,7 @@ function registerMethods(gw, svc) {
|
|
|
4429
5338
|
}));
|
|
4430
5339
|
gw.method("projects.remove", wrap(z.object({ id: z.string() }), async (p) => {
|
|
4431
5340
|
const proj = await svc.projects.get(p.id);
|
|
4432
|
-
if (!proj) throw new ApiError("not_found", "
|
|
5341
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
4433
5342
|
for (const t of await svc.threads.list()) {
|
|
4434
5343
|
if (t.projectId === p.id) {
|
|
4435
5344
|
await svc.threads.delete(t.id);
|
|
@@ -4442,8 +5351,8 @@ function registerMethods(gw, svc) {
|
|
|
4442
5351
|
}));
|
|
4443
5352
|
gw.method("projects.open", wrap(z.object({ id: z.string() }), async (p) => {
|
|
4444
5353
|
const proj = await svc.projects.get(p.id);
|
|
4445
|
-
if (!proj) throw new ApiError("not_found", "
|
|
4446
|
-
if (!existsSync8(proj.path)) throw new ApiError("bad_path", "
|
|
5354
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5355
|
+
if (!existsSync8(proj.path)) throw new ApiError("bad_path", tr("err.dirMissing"));
|
|
4447
5356
|
const cmd = process.platform === "win32" ? "explorer.exe" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
4448
5357
|
const target = process.platform === "win32" ? win32Path.normalize(proj.path) : proj.path;
|
|
4449
5358
|
execFile3(cmd, [target], { windowsHide: true }, () => {
|
|
@@ -4483,16 +5392,18 @@ function registerMethods(gw, svc) {
|
|
|
4483
5392
|
}),
|
|
4484
5393
|
async (p) => {
|
|
4485
5394
|
const proj = await svc.projects.get(p.projectId);
|
|
4486
|
-
if (!proj) throw new ApiError("not_found", "
|
|
4487
|
-
if (!existsSync8(proj.path)) throw new ApiError("bad_path", "
|
|
5395
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5396
|
+
if (!existsSync8(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
4488
5397
|
if (!p.providerSessionId && !isProviderEnabled(await svc.settings.get(), p.provider)) {
|
|
4489
|
-
throw new ApiError("bad_params", "
|
|
5398
|
+
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: catalogById(p.provider)?.name ?? p.provider }));
|
|
4490
5399
|
}
|
|
4491
5400
|
const cat = catalogById(p.provider);
|
|
4492
5401
|
if (!p.providerSessionId && cat && !providerCreatable(cat.status)) {
|
|
4493
|
-
throw new ApiError("bad_params",
|
|
5402
|
+
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: cat.name }));
|
|
4494
5403
|
}
|
|
4495
|
-
const
|
|
5404
|
+
const provider = getProvider(p.provider);
|
|
5405
|
+
const mode2 = clampMode(provider?.capabilities.modes ?? [], p.mode);
|
|
5406
|
+
const meta = await svc.threads.create({ ...p, mode: mode2 });
|
|
4496
5407
|
gw.publish("sidebar", { type: "thread.upsert", thread: meta });
|
|
4497
5408
|
return meta;
|
|
4498
5409
|
}
|
|
@@ -4501,7 +5412,7 @@ function registerMethods(gw, svc) {
|
|
|
4501
5412
|
z.object({ id: z.string(), afterSeq: z.number().int().nonnegative().optional() }),
|
|
4502
5413
|
async (p) => {
|
|
4503
5414
|
const meta = await svc.threads.get(p.id);
|
|
4504
|
-
if (!meta) throw new ApiError("not_found", "
|
|
5415
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
4505
5416
|
const events = await svc.threads.readEvents(p.id, p.afterSeq ?? 0, 5e3);
|
|
4506
5417
|
const rt = svc.runtime?.(p.id) ?? null;
|
|
4507
5418
|
return {
|
|
@@ -4522,7 +5433,7 @@ function registerMethods(gw, svc) {
|
|
|
4522
5433
|
}),
|
|
4523
5434
|
async (p) => {
|
|
4524
5435
|
const meta = await svc.threads.get(p.id);
|
|
4525
|
-
if (!meta) throw new ApiError("not_found", "
|
|
5436
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
4526
5437
|
const patch = {};
|
|
4527
5438
|
if (p.title !== void 0) {
|
|
4528
5439
|
patch.title = p.title.trim() || meta.title;
|
|
@@ -4537,7 +5448,7 @@ function registerMethods(gw, svc) {
|
|
|
4537
5448
|
));
|
|
4538
5449
|
gw.method("threads.delete", wrap(z.object({ id: z.string() }), async (p) => {
|
|
4539
5450
|
const meta = await svc.threads.get(p.id);
|
|
4540
|
-
if (!meta) throw new ApiError("not_found", "
|
|
5451
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
4541
5452
|
if (svc.manager) await svc.manager.deleteThread(p.id);
|
|
4542
5453
|
else await svc.threads.delete(p.id);
|
|
4543
5454
|
gw.publish("sidebar", { type: "thread.remove", id: p.id });
|
|
@@ -4545,8 +5456,8 @@ function registerMethods(gw, svc) {
|
|
|
4545
5456
|
}));
|
|
4546
5457
|
const projectPath = async (projectId) => {
|
|
4547
5458
|
const proj = await svc.projects.get(projectId);
|
|
4548
|
-
if (!proj) throw new ApiError("not_found", "
|
|
4549
|
-
if (!existsSync8(proj.path)) throw new ApiError("bad_path", "
|
|
5459
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
5460
|
+
if (!existsSync8(proj.path)) throw new ApiError("bad_path", tr("err.projectDirGone"));
|
|
4550
5461
|
return proj.path;
|
|
4551
5462
|
};
|
|
4552
5463
|
gw.method("git.status", wrap(
|
|
@@ -4571,7 +5482,7 @@ function registerMethods(gw, svc) {
|
|
|
4571
5482
|
));
|
|
4572
5483
|
gw.method("history.list", wrap(z.object({ projectId: z.string() }), async (p) => {
|
|
4573
5484
|
const proj = await svc.projects.get(p.projectId);
|
|
4574
|
-
if (!proj) throw new ApiError("not_found", "
|
|
5485
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
4575
5486
|
const imported = new Set(
|
|
4576
5487
|
(await svc.threads.list()).filter((t) => t.projectId === p.projectId && t.providerSessionId).map((t) => t.providerSessionId)
|
|
4577
5488
|
);
|
|
@@ -4590,7 +5501,7 @@ function registerMethods(gw, svc) {
|
|
|
4590
5501
|
}),
|
|
4591
5502
|
async (p) => {
|
|
4592
5503
|
const proj = await svc.projects.get(p.projectId);
|
|
4593
|
-
if (!proj) throw new ApiError("not_found", "
|
|
5504
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
4594
5505
|
const metas = [];
|
|
4595
5506
|
for (const item of p.items) {
|
|
4596
5507
|
const provider = getProvider(item.provider);
|
|
@@ -4635,7 +5546,7 @@ function registerMethods(gw, svc) {
|
|
|
4635
5546
|
));
|
|
4636
5547
|
gw.method("terminal.attach", wrap(z.object({ id: z.string() }), async (p, ctx) => {
|
|
4637
5548
|
const r = svc.terminals.attach(p.id);
|
|
4638
|
-
if (r.error || r.index == null) throw new ApiError("not_found", r.error ?? "
|
|
5549
|
+
if (r.error || r.index == null) throw new ApiError("not_found", r.error ?? tr("err.terminalMissing"));
|
|
4639
5550
|
svc.gateway.subscribe(ctx.conn, `terminal:${r.index}`);
|
|
4640
5551
|
return r;
|
|
4641
5552
|
}));
|
|
@@ -4645,7 +5556,7 @@ function registerMethods(gw, svc) {
|
|
|
4645
5556
|
));
|
|
4646
5557
|
gw.method("terminal.kill", wrap(z.object({ id: z.string() }), (p) => svc.terminals.kill(p.id)));
|
|
4647
5558
|
const needManager = () => {
|
|
4648
|
-
if (!svc.manager) throw new ApiError("unavailable", "
|
|
5559
|
+
if (!svc.manager) throw new ApiError("unavailable", tr("err.noManager"));
|
|
4649
5560
|
return svc.manager;
|
|
4650
5561
|
};
|
|
4651
5562
|
gw.method("thread.send", wrap(
|
|
@@ -4656,10 +5567,13 @@ function registerMethods(gw, svc) {
|
|
|
4656
5567
|
behavior: z.enum(["queue", "steer"]).optional()
|
|
4657
5568
|
}),
|
|
4658
5569
|
async (p) => {
|
|
4659
|
-
await needManager().send(p.id, { text: p.text, attachments: p.attachments }, p.behavior ?? "queue");
|
|
4660
|
-
return { ok: true };
|
|
5570
|
+
const delivered = await needManager().send(p.id, { text: p.text, attachments: p.attachments }, p.behavior ?? "queue");
|
|
5571
|
+
return { ok: true, delivered: delivered ?? "started" };
|
|
4661
5572
|
}
|
|
4662
5573
|
));
|
|
5574
|
+
gw.method("thread.queueRemove", wrap(z.object({ id: z.string(), queueId: z.string() }), async (p) => {
|
|
5575
|
+
return { ok: needManager().removeQueued(p.id, p.queueId) };
|
|
5576
|
+
}));
|
|
4663
5577
|
gw.method("thread.interrupt", wrap(z.object({ id: z.string() }), async (p) => {
|
|
4664
5578
|
await needManager().interrupt(p.id);
|
|
4665
5579
|
return { ok: true };
|
|
@@ -4717,15 +5631,15 @@ function registerMethods(gw, svc) {
|
|
|
4717
5631
|
}));
|
|
4718
5632
|
gw.method("thread.exportMarkdown", wrap(z.object({ id: z.string() }), async (p) => {
|
|
4719
5633
|
const meta = await svc.threads.get(p.id);
|
|
4720
|
-
if (!meta) throw new ApiError("not_found", "
|
|
5634
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
4721
5635
|
const events = await svc.threads.readEvents(p.id, 0, 2e4);
|
|
4722
5636
|
const L = [];
|
|
4723
5637
|
L.push(`# ${meta.title}`, "");
|
|
4724
|
-
L.push(`> ${meta.provider} \xB7 ${meta.model ?? "
|
|
5638
|
+
L.push(`> ${meta.provider} \xB7 ${meta.model ?? tr("export.defaultModel")} \xB7 ${meta.mode} \xB7 ${new Date(meta.createdAt).toLocaleString(intlLocale())}`, "");
|
|
4725
5639
|
for (const e of events) {
|
|
4726
5640
|
switch (e.type) {
|
|
4727
5641
|
case "user_message":
|
|
4728
|
-
L.push(
|
|
5642
|
+
L.push(`**${tr("export.me")}**${e.steered ? tr("export.steered") : ""}:
|
|
4729
5643
|
|
|
4730
5644
|
${e.text}`, "");
|
|
4731
5645
|
break;
|
|
@@ -4733,7 +5647,7 @@ ${e.text}`, "");
|
|
|
4733
5647
|
L.push(e.text, "");
|
|
4734
5648
|
break;
|
|
4735
5649
|
case "reasoning":
|
|
4736
|
-
L.push(`<details><summary
|
|
5650
|
+
L.push(`<details><summary>${tr("export.reasoning")}</summary>
|
|
4737
5651
|
|
|
4738
5652
|
${e.text}
|
|
4739
5653
|
</details>`, "");
|
|
@@ -4747,10 +5661,14 @@ ${e.text}
|
|
|
4747
5661
|
break;
|
|
4748
5662
|
}
|
|
4749
5663
|
case "plan":
|
|
4750
|
-
L.push(
|
|
5664
|
+
L.push(`### ${tr("export.plan")}
|
|
5665
|
+
|
|
5666
|
+
` + e.markdown, "");
|
|
4751
5667
|
break;
|
|
4752
5668
|
case "todo":
|
|
4753
|
-
L.push(
|
|
5669
|
+
L.push(`### ${tr("export.todo")}
|
|
5670
|
+
|
|
5671
|
+
` + e.items.map((i) => `- [${i.status === "done" || i.status === "completed" ? "x" : " "}] ${i.text}`).join("\n"), "");
|
|
4754
5672
|
break;
|
|
4755
5673
|
case "notice":
|
|
4756
5674
|
L.push(`_${e.message}_`, "");
|
|
@@ -4765,14 +5683,19 @@ ${e.text}
|
|
|
4765
5683
|
const markdown = L.join("\n");
|
|
4766
5684
|
const safe = meta.title.replace(/[\\/:*?"<>|\s]+/g, "_").slice(0, 60) || "thread";
|
|
4767
5685
|
const filename = `${safe}.md`;
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
5686
|
+
let dir = join12(homedir8(), "Downloads");
|
|
5687
|
+
try {
|
|
5688
|
+
await mkdir3(dir, { recursive: true });
|
|
5689
|
+
} catch {
|
|
5690
|
+
dir = join12(svc.cfg.dataDir, "exports");
|
|
5691
|
+
await mkdir3(dir, { recursive: true });
|
|
5692
|
+
}
|
|
5693
|
+
let dest = join12(dir, filename);
|
|
4771
5694
|
if (existsSync8(dest)) {
|
|
4772
5695
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4773
|
-
dest =
|
|
5696
|
+
dest = join12(dir, `${safe}-${stamp2}.md`);
|
|
4774
5697
|
}
|
|
4775
|
-
await
|
|
5698
|
+
await writeFile4(dest, markdown, "utf8");
|
|
4776
5699
|
revealInFileManager(dest);
|
|
4777
5700
|
return { markdown, filename, path: dest };
|
|
4778
5701
|
}));
|
|
@@ -4797,12 +5720,16 @@ function revealInFileManager(filePath) {
|
|
|
4797
5720
|
var threadModeSchema, ApiError;
|
|
4798
5721
|
var init_methods = __esm({
|
|
4799
5722
|
"daemon/src/ws/methods.ts"() {
|
|
5723
|
+
init_settings();
|
|
4800
5724
|
init_registry();
|
|
4801
5725
|
init_catalog();
|
|
4802
5726
|
init_enabled();
|
|
4803
5727
|
init_pickFolder();
|
|
4804
5728
|
init_bin();
|
|
4805
5729
|
init_infoCache();
|
|
5730
|
+
init_launch();
|
|
5731
|
+
init_modes();
|
|
5732
|
+
init_i18n();
|
|
4806
5733
|
threadModeSchema = z.enum(["plan", "default", "acceptEdits", "bypassPermissions", "ask"]);
|
|
4807
5734
|
ApiError = class extends Error {
|
|
4808
5735
|
code;
|
|
@@ -4815,13 +5742,13 @@ var init_methods = __esm({
|
|
|
4815
5742
|
});
|
|
4816
5743
|
|
|
4817
5744
|
// daemon/src/util/atomic.ts
|
|
4818
|
-
import { writeFile as
|
|
5745
|
+
import { writeFile as writeFile5, rename, mkdir as mkdir4 } from "node:fs/promises";
|
|
4819
5746
|
import { dirname as dirname4 } from "node:path";
|
|
4820
5747
|
async function writeJsonAtomic(file, obj) {
|
|
4821
5748
|
try {
|
|
4822
|
-
await
|
|
5749
|
+
await mkdir4(dirname4(file), { recursive: true });
|
|
4823
5750
|
const tmp = `${file}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4824
|
-
await
|
|
5751
|
+
await writeFile5(tmp, JSON.stringify(obj, null, 2), "utf8");
|
|
4825
5752
|
await rename(tmp, file);
|
|
4826
5753
|
} catch (e) {
|
|
4827
5754
|
if (e instanceof ApiError) throw e;
|
|
@@ -4834,13 +5761,118 @@ var init_atomic = __esm({
|
|
|
4834
5761
|
}
|
|
4835
5762
|
});
|
|
4836
5763
|
|
|
5764
|
+
// daemon/src/store/settings.ts
|
|
5765
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
5766
|
+
import { join as join13 } from "node:path";
|
|
5767
|
+
import { z as z2 } from "zod";
|
|
5768
|
+
function parseSettingsPatch(patch) {
|
|
5769
|
+
const r = settingsPatchSchema.safeParse(patch);
|
|
5770
|
+
if (r.success) return { ok: true, patch: r.data };
|
|
5771
|
+
return {
|
|
5772
|
+
ok: false,
|
|
5773
|
+
error: r.error.issues.map((i) => `${i.path.join(".") || "(patch)"}: ${i.message}`).join("; ")
|
|
5774
|
+
};
|
|
5775
|
+
}
|
|
5776
|
+
function sanitizeStored(raw) {
|
|
5777
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
5778
|
+
const shape = settingsPatchSchema.shape;
|
|
5779
|
+
const out = {};
|
|
5780
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
5781
|
+
const field = shape[k];
|
|
5782
|
+
if (field && k !== "idleCloseMinutes" && !field.safeParse(v).success) continue;
|
|
5783
|
+
if (k === "idleCloseMinutes") {
|
|
5784
|
+
const n = Number(v);
|
|
5785
|
+
if (!Number.isFinite(n)) continue;
|
|
5786
|
+
out[k] = Math.min(IDLE_CLOSE_MAX, Math.max(IDLE_CLOSE_MIN, Math.round(n)));
|
|
5787
|
+
continue;
|
|
5788
|
+
}
|
|
5789
|
+
out[k] = v;
|
|
5790
|
+
}
|
|
5791
|
+
return out;
|
|
5792
|
+
}
|
|
5793
|
+
var DEFAULT_SETTINGS, IDLE_CLOSE_MIN, IDLE_CLOSE_MAX, mode, id, settingsPatchSchema, SettingsStore;
|
|
5794
|
+
var init_settings = __esm({
|
|
5795
|
+
"daemon/src/store/settings.ts"() {
|
|
5796
|
+
init_atomic();
|
|
5797
|
+
DEFAULT_SETTINGS = {
|
|
5798
|
+
defaultProvider: "claude",
|
|
5799
|
+
defaultMode: "default",
|
|
5800
|
+
sendKey: "enter",
|
|
5801
|
+
runningSend: "queue",
|
|
5802
|
+
showThinking: true,
|
|
5803
|
+
theme: "auto",
|
|
5804
|
+
idleCloseMinutes: 30,
|
|
5805
|
+
collapsedProjects: [],
|
|
5806
|
+
panel: { open: false, tab: "changes", width: 440 },
|
|
5807
|
+
sidebarWidth: 248,
|
|
5808
|
+
lastProjectId: null,
|
|
5809
|
+
lastThreadId: null,
|
|
5810
|
+
recentDirs: []
|
|
5811
|
+
};
|
|
5812
|
+
IDLE_CLOSE_MIN = 5;
|
|
5813
|
+
IDLE_CLOSE_MAX = 240;
|
|
5814
|
+
mode = z2.enum(["plan", "default", "acceptEdits", "bypassPermissions", "ask"]);
|
|
5815
|
+
id = z2.string().min(1).max(200);
|
|
5816
|
+
settingsPatchSchema = z2.object({
|
|
5817
|
+
defaultProvider: z2.string().min(1).max(64),
|
|
5818
|
+
defaultMode: mode,
|
|
5819
|
+
sendKey: z2.enum(["enter", "ctrlEnter"]),
|
|
5820
|
+
runningSend: z2.enum(["queue", "steer"]),
|
|
5821
|
+
showThinking: z2.boolean(),
|
|
5822
|
+
// auto = follow the host: Magpie's light/dark inside Magpie, the OS outside it
|
|
5823
|
+
theme: z2.enum(["auto", "light", "dark"]),
|
|
5824
|
+
idleCloseMinutes: z2.number().int().min(IDLE_CLOSE_MIN).max(IDLE_CLOSE_MAX),
|
|
5825
|
+
collapsedProjects: z2.array(id).max(1e3),
|
|
5826
|
+
panel: z2.object({ open: z2.boolean(), tab: z2.string().max(40), width: z2.number().min(0).max(5e3) }).partial(),
|
|
5827
|
+
sidebarWidth: z2.number().min(0).max(5e3),
|
|
5828
|
+
lastProjectId: id.nullable(),
|
|
5829
|
+
lastThreadId: id.nullable(),
|
|
5830
|
+
recentDirs: z2.array(z2.string().max(4096)).max(50),
|
|
5831
|
+
providerEnabled: z2.record(z2.string().max(64), z2.boolean()),
|
|
5832
|
+
providerCommand: z2.record(
|
|
5833
|
+
z2.string().max(64),
|
|
5834
|
+
z2.object({ executable: z2.string().max(4096).optional(), args: z2.array(z2.string().max(4096)).max(64).optional() }).strict()
|
|
5835
|
+
),
|
|
5836
|
+
v1Migrated: z2.boolean()
|
|
5837
|
+
}).partial().strict();
|
|
5838
|
+
SettingsStore = class {
|
|
5839
|
+
file;
|
|
5840
|
+
cache = null;
|
|
5841
|
+
writing = Promise.resolve();
|
|
5842
|
+
constructor(dataDir) {
|
|
5843
|
+
this.file = join13(dataDir, "settings.json");
|
|
5844
|
+
}
|
|
5845
|
+
async get() {
|
|
5846
|
+
if (this.cache) return this.cache;
|
|
5847
|
+
let raw = {};
|
|
5848
|
+
try {
|
|
5849
|
+
raw = JSON.parse(await readFile12(this.file, "utf8"));
|
|
5850
|
+
} catch {
|
|
5851
|
+
}
|
|
5852
|
+
raw = sanitizeStored(raw);
|
|
5853
|
+
this.cache = { ...DEFAULT_SETTINGS, ...raw, panel: { ...DEFAULT_SETTINGS.panel, ...raw.panel } };
|
|
5854
|
+
return this.cache;
|
|
5855
|
+
}
|
|
5856
|
+
async set(patch) {
|
|
5857
|
+
const cur = await this.get();
|
|
5858
|
+
const next = { ...cur, ...patch, panel: { ...cur.panel, ...patch.panel } };
|
|
5859
|
+
this.cache = next;
|
|
5860
|
+
const snap = next;
|
|
5861
|
+
this.writing = this.writing.then(() => writeJsonAtomic(this.file, snap));
|
|
5862
|
+
await this.writing;
|
|
5863
|
+
return next;
|
|
5864
|
+
}
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
});
|
|
5868
|
+
|
|
4837
5869
|
// daemon/src/util/jsonl.ts
|
|
4838
|
-
import { appendFile, readFile as
|
|
5870
|
+
import { appendFile, readFile as readFile14, mkdir as mkdir5, rename as rename2, writeFile as writeFile6 } from "node:fs/promises";
|
|
4839
5871
|
import { dirname as dirname5 } from "node:path";
|
|
4840
5872
|
async function readJsonl(file) {
|
|
4841
5873
|
let text = "";
|
|
4842
5874
|
try {
|
|
4843
|
-
text = await
|
|
5875
|
+
text = await readFile14(file, "utf8");
|
|
4844
5876
|
} catch {
|
|
4845
5877
|
return [];
|
|
4846
5878
|
}
|
|
@@ -4859,7 +5891,7 @@ function appendJsonl(file, obj) {
|
|
|
4859
5891
|
const prev = writers.get(file) ?? Promise.resolve();
|
|
4860
5892
|
const next = prev.then(async () => {
|
|
4861
5893
|
try {
|
|
4862
|
-
await
|
|
5894
|
+
await mkdir5(dirname5(file), { recursive: true });
|
|
4863
5895
|
await appendFile(file, JSON.stringify(obj) + "\n", "utf8");
|
|
4864
5896
|
} catch (e) {
|
|
4865
5897
|
if (e instanceof ApiError) throw e;
|
|
@@ -4886,9 +5918,9 @@ var legacy_exports = {};
|
|
|
4886
5918
|
__export(legacy_exports, {
|
|
4887
5919
|
migrateLegacy: () => migrateLegacy
|
|
4888
5920
|
});
|
|
4889
|
-
import { cp as cp2, mkdir as
|
|
5921
|
+
import { cp as cp2, mkdir as mkdir8, readdir as readdir6, readFile as readFile18 } from "node:fs/promises";
|
|
4890
5922
|
import { existsSync as existsSync12 } from "node:fs";
|
|
4891
|
-
import { join as
|
|
5923
|
+
import { join as join19 } from "node:path";
|
|
4892
5924
|
function stamp() {
|
|
4893
5925
|
const d = /* @__PURE__ */ new Date();
|
|
4894
5926
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -4897,19 +5929,19 @@ function stamp() {
|
|
|
4897
5929
|
async function migrateLegacy(dataDir, stores) {
|
|
4898
5930
|
const settings = await stores.settings.get();
|
|
4899
5931
|
if (settings.v1Migrated) return { migrated: 0, skipped: true };
|
|
4900
|
-
const oldDir =
|
|
4901
|
-
const oldSettingsPath =
|
|
5932
|
+
const oldDir = join19(dataDir, "sessions");
|
|
5933
|
+
const oldSettingsPath = join19(dataDir, "settings.json");
|
|
4902
5934
|
if (!existsSync12(oldDir) && !existsSync12(oldSettingsPath)) {
|
|
4903
5935
|
await stores.settings.set({ v1Migrated: true });
|
|
4904
5936
|
return { migrated: 0, skipped: true };
|
|
4905
5937
|
}
|
|
4906
|
-
const backup =
|
|
4907
|
-
await
|
|
4908
|
-
if (existsSync12(oldDir)) await cp2(oldDir,
|
|
5938
|
+
const backup = join19(dataDir, "trash", `legacy-${stamp()}`);
|
|
5939
|
+
await mkdir8(backup, { recursive: true });
|
|
5940
|
+
if (existsSync12(oldDir)) await cp2(oldDir, join19(backup, "sessions"), { recursive: true }).catch(() => {
|
|
4909
5941
|
});
|
|
4910
|
-
if (existsSync12(oldSettingsPath)) await cp2(oldSettingsPath,
|
|
5942
|
+
if (existsSync12(oldSettingsPath)) await cp2(oldSettingsPath, join19(backup, "settings.json")).catch(() => {
|
|
4911
5943
|
});
|
|
4912
|
-
const oldSettings = existsSync12(oldSettingsPath) ? JSON.parse(await
|
|
5944
|
+
const oldSettings = existsSync12(oldSettingsPath) ? JSON.parse(await readFile18(oldSettingsPath, "utf8").catch(() => "{}")) : {};
|
|
4913
5945
|
const projectIdByPath = /* @__PURE__ */ new Map();
|
|
4914
5946
|
const lc = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
4915
5947
|
for (const p of oldSettings.projects ?? []) {
|
|
@@ -4923,13 +5955,13 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
4923
5955
|
}
|
|
4924
5956
|
let sessionIds = [];
|
|
4925
5957
|
try {
|
|
4926
|
-
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) && existsSync12(
|
|
5958
|
+
sessionIds = (await readdir6(oldDir)).filter((d) => validId(d) && existsSync12(join19(oldDir, d, "meta.json")));
|
|
4927
5959
|
} catch {
|
|
4928
5960
|
}
|
|
4929
5961
|
const metas = [];
|
|
4930
|
-
for (const
|
|
5962
|
+
for (const id2 of sessionIds) {
|
|
4931
5963
|
try {
|
|
4932
|
-
const m = JSON.parse(await
|
|
5964
|
+
const m = JSON.parse(await readFile18(join19(oldDir, id2, "meta.json"), "utf8"));
|
|
4933
5965
|
if (m?.cwd) metas.push(m);
|
|
4934
5966
|
} catch {
|
|
4935
5967
|
}
|
|
@@ -4956,13 +5988,13 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
4956
5988
|
const projectOf = (cwd) => {
|
|
4957
5989
|
const l = lc(cwd);
|
|
4958
5990
|
let best = null;
|
|
4959
|
-
for (const [pp,
|
|
5991
|
+
for (const [pp, id2] of projectIdByPath) {
|
|
4960
5992
|
if (belongsTo(l, pp) && (best == null || pp.length > best.length)) best = pp;
|
|
4961
5993
|
}
|
|
4962
5994
|
return best ? projectIdByPath.get(best) : null;
|
|
4963
5995
|
};
|
|
4964
5996
|
let migrated = 0;
|
|
4965
|
-
const threadsDir =
|
|
5997
|
+
const threadsDir = join19(dataDir, "threads");
|
|
4966
5998
|
for (const m of metas) {
|
|
4967
5999
|
try {
|
|
4968
6000
|
const projectId = projectOf(m.cwd);
|
|
@@ -4999,10 +6031,10 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
4999
6031
|
importedFrom: m.cliSessionId ? { provider, sessionId: m.cliSessionId } : void 0,
|
|
5000
6032
|
seq: 0
|
|
5001
6033
|
};
|
|
5002
|
-
const tdir =
|
|
6034
|
+
const tdir = join19(threadsDir, meta.id);
|
|
5003
6035
|
if (existsSync12(tdir)) meta.id = `t_${m.id}_v1`.slice(0, 80);
|
|
5004
|
-
await
|
|
5005
|
-
const oldEvts = (await
|
|
6036
|
+
await mkdir8(join19(threadsDir, meta.id), { recursive: true });
|
|
6037
|
+
const oldEvts = (await readFile18(join19(oldDir, m.id, "events.jsonl"), "utf8").catch(() => "")).split("\n").filter((l) => l.trim()).map((l) => {
|
|
5006
6038
|
try {
|
|
5007
6039
|
return JSON.parse(l);
|
|
5008
6040
|
} catch {
|
|
@@ -5081,9 +6113,9 @@ async function migrateLegacy(dataDir, stores) {
|
|
|
5081
6113
|
seq++;
|
|
5082
6114
|
lines.push({ type: "tool_call", itemId: key, kind: "other", title: o.title, status: "failed", seq, ts: Date.now(), threadId: meta.id });
|
|
5083
6115
|
}
|
|
5084
|
-
for (const ev of lines) await appendJsonl(
|
|
6116
|
+
for (const ev of lines) await appendJsonl(join19(threadsDir, meta.id, "timeline.jsonl"), ev);
|
|
5085
6117
|
meta.seq = seq;
|
|
5086
|
-
await writeJsonAtomic(
|
|
6118
|
+
await writeJsonAtomic(join19(threadsDir, meta.id, "meta.json"), meta);
|
|
5087
6119
|
migrated++;
|
|
5088
6120
|
} catch (e) {
|
|
5089
6121
|
log("warn", "\u8FC1\u79FB\uFF1A\u4F1A\u8BDD\u5931\u8D25", { id: m.id, err: String(e) });
|
|
@@ -5148,7 +6180,7 @@ var init_warm = __esm({
|
|
|
5148
6180
|
|
|
5149
6181
|
// daemon/src/main.ts
|
|
5150
6182
|
import { mkdirSync as mkdirSync2, rmSync as rmSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
5151
|
-
import { join as
|
|
6183
|
+
import { join as join20, dirname as dirname7 } from "node:path";
|
|
5152
6184
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5153
6185
|
|
|
5154
6186
|
// daemon/src/config.ts
|
|
@@ -5265,6 +6297,7 @@ function tokenEqual(a, b) {
|
|
|
5265
6297
|
|
|
5266
6298
|
// daemon/src/http.ts
|
|
5267
6299
|
init_log();
|
|
6300
|
+
init_i18n();
|
|
5268
6301
|
var MIME = {
|
|
5269
6302
|
".html": "text/html; charset=utf-8",
|
|
5270
6303
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -5306,7 +6339,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5306
6339
|
await route(req, res);
|
|
5307
6340
|
} catch (e) {
|
|
5308
6341
|
log("error", "HTTP \u5904\u7406\u5F02\u5E38", { url: req.url, err: String(e) });
|
|
5309
|
-
if (!res.headersSent) sendJson(res, 500, { ok: false, e: { code: "internal", message: "
|
|
6342
|
+
if (!res.headersSent) sendJson(res, 500, { ok: false, e: { code: "internal", message: tr("http.internal") } });
|
|
5310
6343
|
else res.end();
|
|
5311
6344
|
}
|
|
5312
6345
|
});
|
|
@@ -5318,7 +6351,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5318
6351
|
}
|
|
5319
6352
|
const origin = classifyOrigin(req.headers.origin, cfg2.port);
|
|
5320
6353
|
if (origin === "foreign") {
|
|
5321
|
-
sendJson(res, 403, { ok: false, e: { code: "bad_origin", message: "
|
|
6354
|
+
sendJson(res, 403, { ok: false, e: { code: "bad_origin", message: tr("http.badOrigin") } });
|
|
5322
6355
|
return;
|
|
5323
6356
|
}
|
|
5324
6357
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${cfg2.port}`);
|
|
@@ -5327,7 +6360,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5327
6360
|
const isAttachment = pathname.startsWith("/attachments/");
|
|
5328
6361
|
if (req.method === "OPTIONS") {
|
|
5329
6362
|
if (pathname !== "/api/upload" || origin === "none") {
|
|
5330
|
-
sendJson(res, 403, { ok: false, e: { code: "bad_preflight", message: "
|
|
6363
|
+
sendJson(res, 403, { ok: false, e: { code: "bad_preflight", message: tr("http.badPreflight") } });
|
|
5331
6364
|
return;
|
|
5332
6365
|
}
|
|
5333
6366
|
send(res, 204, "", {
|
|
@@ -5344,7 +6377,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5344
6377
|
const queryToken = isAttachment ? url.searchParams.get("t") : null;
|
|
5345
6378
|
const validToken = typeof headerToken === "string" && tokenEqual(headerToken, cfg2.token) || typeof queryToken === "string" && tokenEqual(queryToken, cfg2.token);
|
|
5346
6379
|
if (!validToken) {
|
|
5347
|
-
sendJson(res, 403, { ok: false, e: { code: "bad_token", message: "
|
|
6380
|
+
sendJson(res, 403, { ok: false, e: { code: "bad_token", message: tr("http.badToken") } });
|
|
5348
6381
|
return;
|
|
5349
6382
|
}
|
|
5350
6383
|
if (origin === "opaque") {
|
|
@@ -5355,10 +6388,10 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5355
6388
|
await routeApi(req, res, pathname, url);
|
|
5356
6389
|
return;
|
|
5357
6390
|
}
|
|
5358
|
-
const
|
|
5359
|
-
const a =
|
|
6391
|
+
const id2 = safeDecode(pathname.slice("/attachments/".length));
|
|
6392
|
+
const a = id2 === null ? null : await attachments.get(id2);
|
|
5360
6393
|
if (!a) {
|
|
5361
|
-
sendJson(res, 404, { ok: false, e: { code: "not_found", message: "
|
|
6394
|
+
sendJson(res, 404, { ok: false, e: { code: "not_found", message: tr("http.noAttachment") } });
|
|
5362
6395
|
return;
|
|
5363
6396
|
}
|
|
5364
6397
|
const data2 = await attachments.read(a.id);
|
|
@@ -5366,11 +6399,11 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5366
6399
|
return;
|
|
5367
6400
|
}
|
|
5368
6401
|
if (origin === "opaque") {
|
|
5369
|
-
sendJson(res, 403, { ok: false, e: { code: "bad_origin", message: "
|
|
6402
|
+
sendJson(res, 403, { ok: false, e: { code: "bad_origin", message: tr("http.badOrigin") } });
|
|
5370
6403
|
return;
|
|
5371
6404
|
}
|
|
5372
6405
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
5373
|
-
sendJson(res, 405, { ok: false, e: { code: "method", message: "
|
|
6406
|
+
sendJson(res, 405, { ok: false, e: { code: "method", message: tr("http.method") } });
|
|
5374
6407
|
return;
|
|
5375
6408
|
}
|
|
5376
6409
|
if (pathname === "/" || pathname === "/index.html") {
|
|
@@ -5378,23 +6411,23 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5378
6411
|
const html = await readFile(join4(appDir, "index.html"), "utf8");
|
|
5379
6412
|
send(res, 200, html, { ...STATIC_HEADERS, "content-type": "text/html; charset=utf-8" });
|
|
5380
6413
|
} catch {
|
|
5381
|
-
send(res, 503, "
|
|
6414
|
+
send(res, 503, tr("http.noApp"), { ...STATIC_HEADERS, "content-type": "text/plain; charset=utf-8" });
|
|
5382
6415
|
}
|
|
5383
6416
|
return;
|
|
5384
6417
|
}
|
|
5385
6418
|
const decoded = safeDecode(pathname);
|
|
5386
6419
|
if (decoded === null) {
|
|
5387
|
-
sendJson(res, 400, { ok: false, e: { code: "bad_path", message: "
|
|
6420
|
+
sendJson(res, 400, { ok: false, e: { code: "bad_path", message: tr("http.badPath") } });
|
|
5388
6421
|
return;
|
|
5389
6422
|
}
|
|
5390
6423
|
const rel = normalize(decoded).replace(/^([\\/])+/, "");
|
|
5391
6424
|
const full = normalize(join4(appDir, rel));
|
|
5392
6425
|
if (!full.startsWith(normalize(appDir)) || rel.includes("..")) {
|
|
5393
|
-
sendJson(res, 403, { ok: false, e: { code: "bad_path", message: "
|
|
6426
|
+
sendJson(res, 403, { ok: false, e: { code: "bad_path", message: tr("http.pathDenied") } });
|
|
5394
6427
|
return;
|
|
5395
6428
|
}
|
|
5396
6429
|
if (!existsSync2(full) || !(await stat(full)).isFile()) {
|
|
5397
|
-
sendJson(res, 404, { ok: false, e: { code: "not_found", message: "
|
|
6430
|
+
sendJson(res, 404, { ok: false, e: { code: "not_found", message: tr("http.noFile") } });
|
|
5398
6431
|
return;
|
|
5399
6432
|
}
|
|
5400
6433
|
const data = await readFile(full);
|
|
@@ -5408,13 +6441,13 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5408
6441
|
if (pathname === "/api/upload" && req.method === "POST") {
|
|
5409
6442
|
const name = url.searchParams.get("name") ?? "";
|
|
5410
6443
|
if (!attachments.allowedExt(name)) {
|
|
5411
|
-
sendJson(res, 400, { ok: false, e: { code: "bad_type", message: "
|
|
6444
|
+
sendJson(res, 400, { ok: false, e: { code: "bad_type", message: tr("http.badType") } });
|
|
5412
6445
|
return;
|
|
5413
6446
|
}
|
|
5414
6447
|
const declared = Number(req.headers["content-length"] ?? 0);
|
|
5415
6448
|
if (declared > MAX_UPLOAD) {
|
|
5416
6449
|
res.setHeader("connection", "close");
|
|
5417
|
-
sendJson(res, 413, { ok: false, e: { code: "too_large", message: "
|
|
6450
|
+
sendJson(res, 413, { ok: false, e: { code: "too_large", message: tr("http.tooLarge") } });
|
|
5418
6451
|
req.resume();
|
|
5419
6452
|
return;
|
|
5420
6453
|
}
|
|
@@ -5425,7 +6458,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5425
6458
|
size += b.length;
|
|
5426
6459
|
if (size > MAX_UPLOAD) {
|
|
5427
6460
|
res.setHeader("connection", "close");
|
|
5428
|
-
sendJson(res, 413, { ok: false, e: { code: "too_large", message: "
|
|
6461
|
+
sendJson(res, 413, { ok: false, e: { code: "too_large", message: tr("http.tooLarge") } });
|
|
5429
6462
|
return;
|
|
5430
6463
|
}
|
|
5431
6464
|
chunks.push(b);
|
|
@@ -5434,7 +6467,7 @@ function createHttpServer(cfg2, appDir, attachments, hooks = {}) {
|
|
|
5434
6467
|
sendJson(res, 200, { ok: true, id: a.id, name: a.name, mime: a.mime, size: a.size });
|
|
5435
6468
|
return;
|
|
5436
6469
|
}
|
|
5437
|
-
sendJson(res, 404, { ok: false, e: { code: "not_found", message: "
|
|
6470
|
+
sendJson(res, 404, { ok: false, e: { code: "not_found", message: tr("http.noRoute") } });
|
|
5438
6471
|
}
|
|
5439
6472
|
if (hooks.onUpgrade) {
|
|
5440
6473
|
server.on("upgrade", hooks.onUpgrade);
|
|
@@ -5476,105 +6509,58 @@ var AttachmentStore = class {
|
|
|
5476
6509
|
async save(name, data) {
|
|
5477
6510
|
const ext = this.safeExt(name);
|
|
5478
6511
|
if (!ext) throw new Error("\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B");
|
|
5479
|
-
const
|
|
6512
|
+
const id2 = "a_" + Date.now().toString(36) + randomBytes2(3).toString("hex");
|
|
5480
6513
|
await mkdir(this.dir, { recursive: true });
|
|
5481
|
-
const file = join5(this.dir,
|
|
6514
|
+
const file = join5(this.dir, id2 + ext);
|
|
5482
6515
|
await writeFile(file, data);
|
|
5483
|
-
this.files.set(
|
|
6516
|
+
this.files.set(id2, id2 + ext);
|
|
5484
6517
|
const mime = MIME2[ext] ?? TEXT_MIME;
|
|
5485
|
-
return { id, name: name || "\u672A\u547D\u540D" + ext, mime, size: data.length, path: file };
|
|
6518
|
+
return { id: id2, name: name || "\u672A\u547D\u540D" + ext, mime, size: data.length, path: file };
|
|
5486
6519
|
}
|
|
5487
|
-
async get(
|
|
5488
|
-
if (!validId(
|
|
5489
|
-
const known = this.files.get(
|
|
6520
|
+
async get(id2) {
|
|
6521
|
+
if (!validId(id2)) return null;
|
|
6522
|
+
const known = this.files.get(id2);
|
|
5490
6523
|
if (known) {
|
|
5491
|
-
const hit = await this.describe(
|
|
6524
|
+
const hit = await this.describe(id2, known);
|
|
5492
6525
|
if (hit) return hit;
|
|
5493
|
-
this.files.delete(
|
|
6526
|
+
this.files.delete(id2);
|
|
5494
6527
|
}
|
|
5495
6528
|
const { readdir: readdir7 } = await import("node:fs/promises");
|
|
5496
6529
|
try {
|
|
5497
6530
|
for (const f of await readdir7(this.dir)) {
|
|
5498
|
-
if (f.startsWith(
|
|
5499
|
-
this.files.set(
|
|
5500
|
-
return await this.describe(
|
|
6531
|
+
if (f.startsWith(id2 + ".")) {
|
|
6532
|
+
this.files.set(id2, f);
|
|
6533
|
+
return await this.describe(id2, f);
|
|
5501
6534
|
}
|
|
5502
6535
|
}
|
|
5503
6536
|
} catch {
|
|
5504
6537
|
}
|
|
5505
6538
|
return null;
|
|
5506
6539
|
}
|
|
5507
|
-
async describe(
|
|
6540
|
+
async describe(id2, f) {
|
|
5508
6541
|
const p = join5(this.dir, f);
|
|
5509
6542
|
const s = await stat2(p).catch(() => null);
|
|
5510
6543
|
if (!s?.isFile()) return null;
|
|
5511
6544
|
const ext = extname2(f).toLowerCase();
|
|
5512
|
-
return { id, name: f.slice(
|
|
6545
|
+
return { id: id2, name: f.slice(id2.length + 1) || f, mime: MIME2[ext] ?? TEXT_MIME, size: s.size, path: p };
|
|
5513
6546
|
}
|
|
5514
|
-
async read(
|
|
5515
|
-
const a = await this.get(
|
|
6547
|
+
async read(id2) {
|
|
6548
|
+
const a = await this.get(id2);
|
|
5516
6549
|
if (!a) return null;
|
|
5517
6550
|
return readFile2(a.path);
|
|
5518
6551
|
}
|
|
5519
6552
|
};
|
|
5520
6553
|
|
|
5521
|
-
// daemon/src/
|
|
5522
|
-
|
|
5523
|
-
import { readFile as readFile10 } from "node:fs/promises";
|
|
5524
|
-
import { join as join11 } from "node:path";
|
|
5525
|
-
var DEFAULT_SETTINGS = {
|
|
5526
|
-
defaultProvider: "claude",
|
|
5527
|
-
defaultMode: "default",
|
|
5528
|
-
defaultEffort: null,
|
|
5529
|
-
sendKey: "enter",
|
|
5530
|
-
runningSend: "queue",
|
|
5531
|
-
showThinking: true,
|
|
5532
|
-
density: "comfortable",
|
|
5533
|
-
theme: "auto",
|
|
5534
|
-
idleCloseMinutes: 30,
|
|
5535
|
-
idleExitMinutes: 60,
|
|
5536
|
-
collapsedProjects: [],
|
|
5537
|
-
panel: { open: false, tab: "changes", width: 440 },
|
|
5538
|
-
sidebarWidth: 248,
|
|
5539
|
-
lastProjectId: null,
|
|
5540
|
-
lastThreadId: null,
|
|
5541
|
-
recentDirs: []
|
|
5542
|
-
};
|
|
5543
|
-
var SettingsStore = class {
|
|
5544
|
-
file;
|
|
5545
|
-
cache = null;
|
|
5546
|
-
writing = Promise.resolve();
|
|
5547
|
-
constructor(dataDir) {
|
|
5548
|
-
this.file = join11(dataDir, "settings.json");
|
|
5549
|
-
}
|
|
5550
|
-
async get() {
|
|
5551
|
-
if (this.cache) return this.cache;
|
|
5552
|
-
let raw = {};
|
|
5553
|
-
try {
|
|
5554
|
-
raw = JSON.parse(await readFile10(this.file, "utf8"));
|
|
5555
|
-
} catch {
|
|
5556
|
-
}
|
|
5557
|
-
this.cache = { ...DEFAULT_SETTINGS, ...raw, panel: { ...DEFAULT_SETTINGS.panel, ...raw.panel } };
|
|
5558
|
-
return this.cache;
|
|
5559
|
-
}
|
|
5560
|
-
async set(patch) {
|
|
5561
|
-
const cur = await this.get();
|
|
5562
|
-
const next = { ...cur, ...patch, panel: { ...cur.panel, ...patch.panel } };
|
|
5563
|
-
this.cache = next;
|
|
5564
|
-
const snap = next;
|
|
5565
|
-
this.writing = this.writing.then(() => writeJsonAtomic(this.file, snap));
|
|
5566
|
-
await this.writing;
|
|
5567
|
-
return next;
|
|
5568
|
-
}
|
|
5569
|
-
};
|
|
6554
|
+
// daemon/src/main.ts
|
|
6555
|
+
init_settings();
|
|
5570
6556
|
|
|
5571
6557
|
// daemon/src/store/projects.ts
|
|
5572
6558
|
init_atomic();
|
|
5573
6559
|
init_paths();
|
|
5574
6560
|
init_methods();
|
|
5575
|
-
import { readFile as
|
|
6561
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
5576
6562
|
import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
|
|
5577
|
-
import { join as
|
|
6563
|
+
import { join as join14 } from "node:path";
|
|
5578
6564
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
5579
6565
|
function newId() {
|
|
5580
6566
|
return "p_" + Date.now().toString(36) + randomBytes3(3).toString("hex");
|
|
@@ -5584,21 +6570,21 @@ var ProjectsStore = class {
|
|
|
5584
6570
|
cache = null;
|
|
5585
6571
|
writing = Promise.resolve();
|
|
5586
6572
|
constructor(dataDir) {
|
|
5587
|
-
this.file =
|
|
6573
|
+
this.file = join14(dataDir, "projects.json");
|
|
5588
6574
|
}
|
|
5589
6575
|
async list() {
|
|
5590
6576
|
if (this.cache) return this.cache;
|
|
5591
6577
|
try {
|
|
5592
|
-
this.cache = JSON.parse(await
|
|
6578
|
+
this.cache = JSON.parse(await readFile13(this.file, "utf8"));
|
|
5593
6579
|
if (!Array.isArray(this.cache)) this.cache = [];
|
|
5594
6580
|
} catch {
|
|
5595
6581
|
this.cache = [];
|
|
5596
6582
|
}
|
|
5597
6583
|
return this.cache;
|
|
5598
6584
|
}
|
|
5599
|
-
async get(
|
|
5600
|
-
if (!validId(
|
|
5601
|
-
return (await this.list()).find((p) => p.id ===
|
|
6585
|
+
async get(id2) {
|
|
6586
|
+
if (!validId(id2)) return null;
|
|
6587
|
+
return (await this.list()).find((p) => p.id === id2) ?? null;
|
|
5602
6588
|
}
|
|
5603
6589
|
async save(list) {
|
|
5604
6590
|
this.cache = list;
|
|
@@ -5625,17 +6611,17 @@ var ProjectsStore = class {
|
|
|
5625
6611
|
await this.save(list);
|
|
5626
6612
|
return proj;
|
|
5627
6613
|
}
|
|
5628
|
-
async rename(
|
|
6614
|
+
async rename(id2, name) {
|
|
5629
6615
|
const list = await this.list();
|
|
5630
|
-
const p = list.find((x) => x.id ===
|
|
6616
|
+
const p = list.find((x) => x.id === id2);
|
|
5631
6617
|
if (!p) throw new ApiError("not_found", "\u9879\u76EE\u4E0D\u5B58\u5728");
|
|
5632
6618
|
p.name = name.trim() || p.name;
|
|
5633
6619
|
await this.save(list);
|
|
5634
6620
|
return p;
|
|
5635
6621
|
}
|
|
5636
|
-
async remove(
|
|
6622
|
+
async remove(id2) {
|
|
5637
6623
|
const list = await this.list();
|
|
5638
|
-
const idx = list.findIndex((x) => x.id ===
|
|
6624
|
+
const idx = list.findIndex((x) => x.id === id2);
|
|
5639
6625
|
if (idx < 0) return;
|
|
5640
6626
|
list.splice(idx, 1);
|
|
5641
6627
|
await this.save(list);
|
|
@@ -5648,8 +6634,8 @@ init_jsonl();
|
|
|
5648
6634
|
init_paths();
|
|
5649
6635
|
init_methods();
|
|
5650
6636
|
init_i18n();
|
|
5651
|
-
import { mkdir as
|
|
5652
|
-
import { join as
|
|
6637
|
+
import { mkdir as mkdir6, rename as rename3, readdir as readdir4, readFile as readFile15 } from "node:fs/promises";
|
|
6638
|
+
import { join as join15 } from "node:path";
|
|
5653
6639
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
5654
6640
|
function newId2() {
|
|
5655
6641
|
return "t_" + Date.now().toString(36) + randomBytes4(3).toString("hex");
|
|
@@ -5668,17 +6654,17 @@ var ThreadsStore = class {
|
|
|
5668
6654
|
return p;
|
|
5669
6655
|
}
|
|
5670
6656
|
constructor(dataDir) {
|
|
5671
|
-
this.dir =
|
|
5672
|
-
this.trashDir =
|
|
6657
|
+
this.dir = join15(dataDir, "threads");
|
|
6658
|
+
this.trashDir = join15(dataDir, "trash", "threads");
|
|
5673
6659
|
}
|
|
5674
|
-
tdir(
|
|
5675
|
-
return
|
|
6660
|
+
tdir(id2) {
|
|
6661
|
+
return join15(this.dir, id2);
|
|
5676
6662
|
}
|
|
5677
|
-
metaPath(
|
|
5678
|
-
return
|
|
6663
|
+
metaPath(id2) {
|
|
6664
|
+
return join15(this.tdir(id2), "meta.json");
|
|
5679
6665
|
}
|
|
5680
|
-
timelinePath(
|
|
5681
|
-
return
|
|
6666
|
+
timelinePath(id2) {
|
|
6667
|
+
return join15(this.tdir(id2), "timeline.jsonl");
|
|
5682
6668
|
}
|
|
5683
6669
|
async loadAll() {
|
|
5684
6670
|
if (this.loaded) return;
|
|
@@ -5689,9 +6675,9 @@ var ThreadsStore = class {
|
|
|
5689
6675
|
} catch {
|
|
5690
6676
|
return;
|
|
5691
6677
|
}
|
|
5692
|
-
for (const
|
|
6678
|
+
for (const id2 of ids) {
|
|
5693
6679
|
try {
|
|
5694
|
-
const m = JSON.parse(await
|
|
6680
|
+
const m = JSON.parse(await readFile15(this.metaPath(id2), "utf8"));
|
|
5695
6681
|
if (m && typeof m.id === "string") this.metas.set(m.id, m);
|
|
5696
6682
|
} catch {
|
|
5697
6683
|
}
|
|
@@ -5701,10 +6687,10 @@ var ThreadsStore = class {
|
|
|
5701
6687
|
await this.loadAll();
|
|
5702
6688
|
return [...this.metas.values()].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
5703
6689
|
}
|
|
5704
|
-
async get(
|
|
5705
|
-
if (!validId(
|
|
6690
|
+
async get(id2) {
|
|
6691
|
+
if (!validId(id2)) return null;
|
|
5706
6692
|
await this.loadAll();
|
|
5707
|
-
return this.metas.get(
|
|
6693
|
+
return this.metas.get(id2) ?? null;
|
|
5708
6694
|
}
|
|
5709
6695
|
async saveMeta(meta) {
|
|
5710
6696
|
this.metas.set(meta.id, meta);
|
|
@@ -5733,16 +6719,16 @@ var ThreadsStore = class {
|
|
|
5733
6719
|
seq: 0
|
|
5734
6720
|
};
|
|
5735
6721
|
try {
|
|
5736
|
-
await
|
|
6722
|
+
await mkdir6(this.tdir(meta.id), { recursive: true });
|
|
5737
6723
|
} catch (e) {
|
|
5738
6724
|
throw new ApiError("io_error", `\u521B\u5EFA\u5BF9\u8BDD\u76EE\u5F55\u5931\u8D25\uFF1A${e?.message ?? e}`);
|
|
5739
6725
|
}
|
|
5740
6726
|
await this.saveMeta(meta);
|
|
5741
6727
|
return meta;
|
|
5742
6728
|
}
|
|
5743
|
-
async patch(
|
|
6729
|
+
async patch(id2, patch) {
|
|
5744
6730
|
return this.serial(async () => {
|
|
5745
|
-
const meta = await this.get(
|
|
6731
|
+
const meta = await this.get(id2);
|
|
5746
6732
|
if (!meta) throw new ApiError("not_found", "\u5BF9\u8BDD\u4E0D\u5B58\u5728");
|
|
5747
6733
|
if (patch.title !== void 0 && patch.titleAuto === void 0) patch = { ...patch, titleAuto: false };
|
|
5748
6734
|
const next = { ...meta, ...patch, id: meta.id, updatedAt: Date.now() };
|
|
@@ -5752,37 +6738,37 @@ var ThreadsStore = class {
|
|
|
5752
6738
|
}
|
|
5753
6739
|
// Append an event: inside the serialized section take meta.seq+1 as seq, write the jsonl and update meta.
|
|
5754
6740
|
// A passed-in seq is ignored (keeps it strictly increasing); ts/threadId are filled in when missing.
|
|
5755
|
-
async appendEvent(
|
|
6741
|
+
async appendEvent(id2, ev) {
|
|
5756
6742
|
return this.serial(async () => {
|
|
5757
|
-
const meta = await this.get(
|
|
6743
|
+
const meta = await this.get(id2);
|
|
5758
6744
|
if (!meta) throw new ApiError("not_found", "\u5BF9\u8BDD\u4E0D\u5B58\u5728");
|
|
5759
6745
|
const full = {
|
|
5760
6746
|
...ev,
|
|
5761
6747
|
seq: meta.seq + 1,
|
|
5762
6748
|
ts: ev.ts ?? Date.now(),
|
|
5763
|
-
threadId:
|
|
6749
|
+
threadId: id2
|
|
5764
6750
|
};
|
|
5765
|
-
await appendJsonl(this.timelinePath(
|
|
6751
|
+
await appendJsonl(this.timelinePath(id2), full);
|
|
5766
6752
|
meta.seq = full.seq;
|
|
5767
6753
|
meta.updatedAt = Date.now();
|
|
5768
|
-
this.metas.set(
|
|
5769
|
-
await writeJsonAtomic(this.metaPath(
|
|
6754
|
+
this.metas.set(id2, meta);
|
|
6755
|
+
await writeJsonAtomic(this.metaPath(id2), meta);
|
|
5770
6756
|
return full;
|
|
5771
6757
|
});
|
|
5772
6758
|
}
|
|
5773
|
-
async readEvents(
|
|
5774
|
-
const rows = await readJsonl(this.timelinePath(
|
|
6759
|
+
async readEvents(id2, afterSeq = 0, limit = 5e3) {
|
|
6760
|
+
const rows = await readJsonl(this.timelinePath(id2));
|
|
5775
6761
|
const out = rows.filter((e) => typeof e.seq === "number" && e.seq > afterSeq);
|
|
5776
6762
|
return out.slice(-limit);
|
|
5777
6763
|
}
|
|
5778
|
-
async delete(
|
|
5779
|
-
const meta = await this.get(
|
|
6764
|
+
async delete(id2) {
|
|
6765
|
+
const meta = await this.get(id2);
|
|
5780
6766
|
if (!meta) return;
|
|
5781
6767
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
5782
|
-
await
|
|
6768
|
+
await mkdir6(this.trashDir, { recursive: true });
|
|
5783
6769
|
for (let attempt = 0; ; attempt++) {
|
|
5784
6770
|
try {
|
|
5785
|
-
await rename3(this.tdir(
|
|
6771
|
+
await rename3(this.tdir(id2), join15(this.trashDir, `${id2}-${stamp2}`));
|
|
5786
6772
|
break;
|
|
5787
6773
|
} catch (e) {
|
|
5788
6774
|
const code = e?.code;
|
|
@@ -5793,7 +6779,7 @@ var ThreadsStore = class {
|
|
|
5793
6779
|
throw new ApiError("internal", "\u79FB\u5165\u56DE\u6536\u76EE\u5F55\u5931\u8D25\uFF1A" + String(e));
|
|
5794
6780
|
}
|
|
5795
6781
|
}
|
|
5796
|
-
this.metas.delete(
|
|
6782
|
+
this.metas.delete(id2);
|
|
5797
6783
|
}
|
|
5798
6784
|
};
|
|
5799
6785
|
|
|
@@ -6000,6 +6986,8 @@ init_atomic();
|
|
|
6000
6986
|
init_registry();
|
|
6001
6987
|
|
|
6002
6988
|
// daemon/src/agents/manager.ts
|
|
6989
|
+
init_launch();
|
|
6990
|
+
init_modes();
|
|
6003
6991
|
init_attachmentRefs();
|
|
6004
6992
|
init_registry();
|
|
6005
6993
|
init_catalog();
|
|
@@ -6041,7 +7029,7 @@ var Coalescer = class {
|
|
|
6041
7029
|
this.flush(itemId);
|
|
6042
7030
|
}
|
|
6043
7031
|
dispose() {
|
|
6044
|
-
for (const
|
|
7032
|
+
for (const id2 of [...this.acc.keys()]) this.flush(id2);
|
|
6045
7033
|
}
|
|
6046
7034
|
};
|
|
6047
7035
|
|
|
@@ -6051,6 +7039,7 @@ init_log();
|
|
|
6051
7039
|
init_i18n();
|
|
6052
7040
|
init_commands();
|
|
6053
7041
|
var SLASH_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
7042
|
+
var MAX_WARM_IDLE = 3;
|
|
6054
7043
|
var DELTA_TYPES = ["assistant_delta", "reasoning_delta", "tool_output_delta"];
|
|
6055
7044
|
var deltaKey = (type, itemId) => `${type}\0${itemId}`;
|
|
6056
7045
|
var AgentManager = class {
|
|
@@ -6129,6 +7118,16 @@ var AgentManager = class {
|
|
|
6129
7118
|
}
|
|
6130
7119
|
return false;
|
|
6131
7120
|
}
|
|
7121
|
+
/** Drop a queued message that hasn't been sent yet (the × on the composer's queue list) */
|
|
7122
|
+
removeQueued(threadId, queueId) {
|
|
7123
|
+
const rt = this.runtimes.get(threadId);
|
|
7124
|
+
if (!rt) return false;
|
|
7125
|
+
const before = rt.queue.length;
|
|
7126
|
+
rt.queue = rt.queue.filter((q) => q.id !== queueId);
|
|
7127
|
+
if (rt.queue.length === before) return false;
|
|
7128
|
+
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
7129
|
+
return true;
|
|
7130
|
+
}
|
|
6132
7131
|
runtimeInfo(threadId) {
|
|
6133
7132
|
const rt = this.runtimes.get(threadId);
|
|
6134
7133
|
if (!rt) return null;
|
|
@@ -6159,7 +7158,9 @@ var AgentManager = class {
|
|
|
6159
7158
|
endedTurnIds: /* @__PURE__ */ new Set(),
|
|
6160
7159
|
starting: false,
|
|
6161
7160
|
commands: [],
|
|
6162
|
-
sessionCreating: null
|
|
7161
|
+
sessionCreating: null,
|
|
7162
|
+
pendingText: /* @__PURE__ */ new Map(),
|
|
7163
|
+
deltasOnly: false
|
|
6163
7164
|
};
|
|
6164
7165
|
this.runtimes.set(threadId, r);
|
|
6165
7166
|
}
|
|
@@ -6208,17 +7209,18 @@ var AgentManager = class {
|
|
|
6208
7209
|
steered: true,
|
|
6209
7210
|
attachments: this.attachmentLabels(attachmentRefs)
|
|
6210
7211
|
});
|
|
6211
|
-
return;
|
|
7212
|
+
return "steered";
|
|
6212
7213
|
} catch (e) {
|
|
6213
7214
|
log("info", `thread ${threadId} \u63D2\u8BDD\u5931\u8D25\uFF0C\u6539\u4E3A\u6392\u961F\uFF1A${String(e)}`);
|
|
6214
7215
|
}
|
|
6215
7216
|
}
|
|
6216
|
-
const q = { id: "q" + Date.now().toString(36), text: input.text, attachments: attachmentRefs };
|
|
7217
|
+
const q = { id: "q" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), text: input.text, attachments: attachmentRefs };
|
|
6217
7218
|
rt.queue.push(q);
|
|
6218
7219
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
6219
|
-
return;
|
|
7220
|
+
return "queued";
|
|
6220
7221
|
}
|
|
6221
7222
|
await this.startTurn(threadId, input.text, attachmentRefs);
|
|
7223
|
+
return "started";
|
|
6222
7224
|
}
|
|
6223
7225
|
async startTurn(threadId, text, attachmentRefs) {
|
|
6224
7226
|
const rt = this.rt(threadId);
|
|
@@ -6313,12 +7315,21 @@ var AgentManager = class {
|
|
|
6313
7315
|
return;
|
|
6314
7316
|
}
|
|
6315
7317
|
if (ev.type === "assistant_delta" || ev.type === "reasoning_delta" || ev.type === "tool_output_delta") {
|
|
7318
|
+
if (rt.deltasOnly && ev.type !== "tool_output_delta") {
|
|
7319
|
+
const key = `${ev.type === "assistant_delta" ? "assistant" : "reasoning"}\0${ev.itemId}`;
|
|
7320
|
+
const prior = rt.pendingText.get(key);
|
|
7321
|
+
rt.pendingText.set(key, { type: ev.type === "assistant_delta" ? "assistant" : "reasoning", itemId: ev.itemId, text: (prior?.text ?? "") + ev.text });
|
|
7322
|
+
}
|
|
6316
7323
|
rt.coalescer.push(deltaKey(ev.type, ev.itemId), ev.text);
|
|
6317
7324
|
return;
|
|
6318
7325
|
}
|
|
6319
7326
|
const evItemId = ev.itemId;
|
|
6320
7327
|
if (typeof evItemId === "string") this.flushDeltas(rt, evItemId);
|
|
7328
|
+
if (rt.pendingText.size && ev.type !== "usage" && ev.type !== "assistant_message" && ev.type !== "reasoning") {
|
|
7329
|
+
await this.persistPendingText(threadId, rt);
|
|
7330
|
+
}
|
|
6321
7331
|
if (ev.type === "assistant_message" || ev.type === "reasoning") {
|
|
7332
|
+
rt.pendingText.delete(`${ev.type === "assistant_message" ? "assistant" : "reasoning"}\0${ev.itemId}`);
|
|
6322
7333
|
await this.appendRaw(threadId, ev);
|
|
6323
7334
|
return;
|
|
6324
7335
|
}
|
|
@@ -6375,6 +7386,8 @@ var AgentManager = class {
|
|
|
6375
7386
|
if (ev.status === "failed" && rt.session && !this.alive(rt.session)) rt.session = null;
|
|
6376
7387
|
await this.setStatus(threadId, "idle");
|
|
6377
7388
|
const next = rt.queue.shift();
|
|
7389
|
+
if (!next) void this.recycleIdle().catch(() => {
|
|
7390
|
+
});
|
|
6378
7391
|
if (next) {
|
|
6379
7392
|
this.gw.publish(`thread:${threadId}`, { type: "queue", queue: rt.queue });
|
|
6380
7393
|
await this.startTurn(threadId, next.text, next.attachments).catch((e) => {
|
|
@@ -6385,6 +7398,15 @@ var AgentManager = class {
|
|
|
6385
7398
|
}
|
|
6386
7399
|
await this.appendRaw(threadId, ev);
|
|
6387
7400
|
}
|
|
7401
|
+
async persistPendingText(threadId, rt) {
|
|
7402
|
+
const pending = [...rt.pendingText.values()];
|
|
7403
|
+
rt.pendingText.clear();
|
|
7404
|
+
for (const p of pending) {
|
|
7405
|
+
if (!p.text) continue;
|
|
7406
|
+
this.flushDeltas(rt, p.itemId);
|
|
7407
|
+
await this.appendRaw(threadId, { type: p.type === "assistant" ? "assistant_message" : "reasoning", itemId: p.itemId, text: p.text });
|
|
7408
|
+
}
|
|
7409
|
+
}
|
|
6388
7410
|
alive(_s) {
|
|
6389
7411
|
return true;
|
|
6390
7412
|
}
|
|
@@ -6392,10 +7414,10 @@ var AgentManager = class {
|
|
|
6392
7414
|
async respond(threadId, requestId, answer) {
|
|
6393
7415
|
const rt = this.rt(threadId);
|
|
6394
7416
|
const pending = rt.pending.get(requestId);
|
|
6395
|
-
if (!pending) throw new ApiError("not_found", "
|
|
7417
|
+
if (!pending) throw new ApiError("not_found", tr("err.requestGone"));
|
|
6396
7418
|
if (pending.kind === "permission" && answer.optionId) {
|
|
6397
7419
|
const valid = pending.options.some((o) => o.id === answer.optionId);
|
|
6398
|
-
if (!valid) throw new ApiError("bad_params",
|
|
7420
|
+
if (!valid) throw new ApiError("bad_params", tr("err.badOption", { id: String(answer.optionId) }));
|
|
6399
7421
|
}
|
|
6400
7422
|
rt.pending.delete(requestId);
|
|
6401
7423
|
if (pending.kind === "permission") {
|
|
@@ -6423,8 +7445,8 @@ var AgentManager = class {
|
|
|
6423
7445
|
async interrupt(threadId) {
|
|
6424
7446
|
const rt = this.rt(threadId);
|
|
6425
7447
|
if (!rt.session || rt.status !== "running") return;
|
|
6426
|
-
for (const [
|
|
6427
|
-
await this.appendRaw(threadId, { type: "permission_resolved", requestId:
|
|
7448
|
+
for (const [id2, p] of rt.pending) {
|
|
7449
|
+
await this.appendRaw(threadId, { type: "permission_resolved", requestId: id2, cancelled: true });
|
|
6428
7450
|
p.resolve({ cancelled: true });
|
|
6429
7451
|
}
|
|
6430
7452
|
rt.pending.clear();
|
|
@@ -6462,10 +7484,10 @@ var AgentManager = class {
|
|
|
6462
7484
|
if (rt.session) await rt.session.setModel(model);
|
|
6463
7485
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
6464
7486
|
}
|
|
6465
|
-
async setMode(threadId,
|
|
6466
|
-
await this.threads.patch(threadId, { mode });
|
|
7487
|
+
async setMode(threadId, mode2) {
|
|
7488
|
+
await this.threads.patch(threadId, { mode: mode2 });
|
|
6467
7489
|
const rt = this.rt(threadId);
|
|
6468
|
-
if (rt.session) await rt.session.setMode(
|
|
7490
|
+
if (rt.session) await rt.session.setMode(mode2);
|
|
6469
7491
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
6470
7492
|
}
|
|
6471
7493
|
async setEffort(threadId, effort) {
|
|
@@ -6484,7 +7506,7 @@ var AgentManager = class {
|
|
|
6484
7506
|
}
|
|
6485
7507
|
async setCursorOpts(threadId, patch) {
|
|
6486
7508
|
const meta = await this.mustThread(threadId);
|
|
6487
|
-
if (meta.provider !== "cursor") throw new ApiError("bad_params", "
|
|
7509
|
+
if (meta.provider !== "cursor") throw new ApiError("bad_params", tr("err.cursorOnly"));
|
|
6488
7510
|
const next = await this.threads.patch(threadId, patch);
|
|
6489
7511
|
const rt = this.rt(threadId);
|
|
6490
7512
|
const sess = rt.session;
|
|
@@ -6497,14 +7519,14 @@ var AgentManager = class {
|
|
|
6497
7519
|
async setProvider(threadId, providerId) {
|
|
6498
7520
|
await this.mustThread(threadId);
|
|
6499
7521
|
const meta = await this.threads.get(threadId);
|
|
6500
|
-
if (!meta) throw new ApiError("not_found", "
|
|
7522
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
6501
7523
|
const rt = this.rt(threadId);
|
|
6502
7524
|
if (rt.status === "running" || rt.status === "initializing" || rt.starting) {
|
|
6503
|
-
throw new ApiError("busy", "
|
|
7525
|
+
throw new ApiError("busy", tr("err.busySwitch"));
|
|
6504
7526
|
}
|
|
6505
7527
|
const events = await this.threads.readEvents(threadId, 0, 5e3);
|
|
6506
7528
|
if (events.some((e) => e.type === "user_message")) {
|
|
6507
|
-
throw new ApiError("bad_params", "
|
|
7529
|
+
throw new ApiError("bad_params", tr("err.hasMessages"));
|
|
6508
7530
|
}
|
|
6509
7531
|
const p = await this.assertProviderUsable(providerId);
|
|
6510
7532
|
if (rt.session) {
|
|
@@ -6519,7 +7541,7 @@ var AgentManager = class {
|
|
|
6519
7541
|
await this.threads.patch(threadId, {
|
|
6520
7542
|
provider: providerId,
|
|
6521
7543
|
model: null,
|
|
6522
|
-
mode: p.capabilities.modes
|
|
7544
|
+
mode: clampMode(p.capabilities.modes, meta.mode),
|
|
6523
7545
|
effort: null,
|
|
6524
7546
|
providerSessionId: null,
|
|
6525
7547
|
status: "idle"
|
|
@@ -6622,7 +7644,7 @@ var AgentManager = class {
|
|
|
6622
7644
|
if (rt.session) return rt.session;
|
|
6623
7645
|
const provider = await this.assertProviderUsable(meta.provider, { allowDisabled: !!meta.providerSessionId });
|
|
6624
7646
|
const proj = await this.projects.get(meta.projectId);
|
|
6625
|
-
if (!proj) throw new ApiError("not_found", "
|
|
7647
|
+
if (!proj) throw new ApiError("not_found", tr("err.projectMissing"));
|
|
6626
7648
|
let model = meta.model;
|
|
6627
7649
|
if (meta.provider === "dsh" && model) {
|
|
6628
7650
|
const available = await cachedModels(provider).catch(() => []);
|
|
@@ -6631,16 +7653,20 @@ var AgentManager = class {
|
|
|
6631
7653
|
await this.threads.patch(threadId, { model });
|
|
6632
7654
|
this.gw.publish("sidebar", { type: "thread.upsert", thread: await this.threads.get(threadId) });
|
|
6633
7655
|
}
|
|
7656
|
+
} else if (meta.provider === "dsh" && !model) {
|
|
7657
|
+
const available = await cachedModels(provider).catch(() => []);
|
|
7658
|
+
const magpie = available.filter((m) => /magpie/i.test(m.id));
|
|
7659
|
+
model = (magpie.find((m) => m.isDefault) ?? magpie[0] ?? available.find((m) => m.isDefault))?.id ?? null;
|
|
6634
7660
|
}
|
|
6635
7661
|
if (opts.updateStatus) await this.setStatus(threadId, "initializing");
|
|
6636
7662
|
const settings = await this.settings.get();
|
|
6637
|
-
const
|
|
6638
|
-
|
|
7663
|
+
const launch = launchFromSettings(settings, meta.provider);
|
|
7664
|
+
rt.deltasOnly = !!provider.capabilities.deltasOnly;
|
|
6639
7665
|
rt.session = await provider.createSession({
|
|
6640
7666
|
threadId,
|
|
6641
7667
|
cwd: proj.path,
|
|
6642
7668
|
model,
|
|
6643
|
-
mode: meta.mode,
|
|
7669
|
+
mode: clampMode(provider.capabilities.modes, meta.mode),
|
|
6644
7670
|
effort: meta.effort,
|
|
6645
7671
|
resumeId: meta.providerSessionId,
|
|
6646
7672
|
cursorFast: meta.cursorFast,
|
|
@@ -6658,7 +7684,7 @@ var AgentManager = class {
|
|
|
6658
7684
|
// ---- internals ----
|
|
6659
7685
|
async mustThread(threadId) {
|
|
6660
7686
|
const meta = await this.threads.get(threadId);
|
|
6661
|
-
if (!meta) throw new ApiError("not_found", "
|
|
7687
|
+
if (!meta) throw new ApiError("not_found", tr("err.threadMissing"));
|
|
6662
7688
|
return meta;
|
|
6663
7689
|
}
|
|
6664
7690
|
providerOf(meta) {
|
|
@@ -6666,17 +7692,17 @@ var AgentManager = class {
|
|
|
6666
7692
|
}
|
|
6667
7693
|
async assertProviderUsable(providerId, opts = {}) {
|
|
6668
7694
|
const p = getProvider(providerId);
|
|
6669
|
-
if (!p) throw new ApiError("bad_params",
|
|
7695
|
+
if (!p) throw new ApiError("bad_params", tr("provider.unknown", { id: providerId }));
|
|
6670
7696
|
const s = await this.settings.get();
|
|
6671
7697
|
if (!opts.allowDisabled && !isProviderEnabled(s, providerId)) {
|
|
6672
|
-
throw new ApiError("bad_params",
|
|
7698
|
+
throw new ApiError("bad_params", tr("provider.disabledNamed", { name: p.name }));
|
|
6673
7699
|
}
|
|
6674
7700
|
const entry = catalogById(providerId);
|
|
6675
7701
|
if (entry && !providerCreatable(entry.status)) {
|
|
6676
|
-
throw new ApiError("bad_params",
|
|
7702
|
+
throw new ApiError("bad_params", tr("provider.plannedNamed", { name: p.name }));
|
|
6677
7703
|
}
|
|
6678
|
-
const det = await cachedDetect(p);
|
|
6679
|
-
if (!det.available) throw new ApiError("bad_params", det.reason ??
|
|
7704
|
+
const det = await cachedDetect(p, launchFromSettings(s, providerId));
|
|
7705
|
+
if (!det.available) throw new ApiError("bad_params", det.reason ?? tr("provider.unavailable", { name: p.name }));
|
|
6680
7706
|
return p;
|
|
6681
7707
|
}
|
|
6682
7708
|
async setStatus(threadId, status) {
|
|
@@ -6698,23 +7724,41 @@ var AgentManager = class {
|
|
|
6698
7724
|
const full = { ...ev, seq: -1, ts: Date.now(), threadId };
|
|
6699
7725
|
this.gw.publish(`thread:${threadId}`, { type: "event", event: full });
|
|
6700
7726
|
}
|
|
7727
|
+
/** B17: idle Agent processes are closed after idleCloseMinutes, and at most MAX_WARM_IDLE of them stay warm
|
|
7728
|
+
* (least recently used go first). A running turn, one starting, or one waiting on an approval is never touched. */
|
|
7729
|
+
recycling = false;
|
|
6701
7730
|
async recycleIdle() {
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
7731
|
+
if (this.recycling) return;
|
|
7732
|
+
this.recycling = true;
|
|
7733
|
+
try {
|
|
7734
|
+
const s = await this.settings.get();
|
|
7735
|
+
const minutes = Number(s.idleCloseMinutes);
|
|
7736
|
+
const limit = (Number.isFinite(minutes) && minutes > 0 ? minutes : 30) * 6e4;
|
|
7737
|
+
const idle = [...this.runtimes.values()].filter((rt) => rt.session && rt.status === "idle" && !rt.starting && !rt.sessionCreating && rt.pending.size === 0 && rt.queue.length === 0).sort((a, b) => b.lastActivity - a.lastActivity);
|
|
7738
|
+
const now = Date.now();
|
|
7739
|
+
for (const [i, rt] of idle.entries()) {
|
|
7740
|
+
const expired = now - rt.lastActivity > limit;
|
|
7741
|
+
if (!expired && i < MAX_WARM_IDLE) continue;
|
|
7742
|
+
log("info", `idle cleanup: closing thread ${rt.threadId} (${expired ? "idle timeout" : "over the warm limit"})`);
|
|
7743
|
+
try {
|
|
7744
|
+
await this.close(rt.threadId);
|
|
7745
|
+
} catch (e) {
|
|
7746
|
+
log("info", `idle cleanup: thread ${rt.threadId}: ${String(e?.message ?? e)}`);
|
|
7747
|
+
this.runtimes.delete(rt.threadId);
|
|
7748
|
+
}
|
|
6708
7749
|
}
|
|
7750
|
+
} finally {
|
|
7751
|
+
this.recycling = false;
|
|
6709
7752
|
}
|
|
6710
7753
|
}
|
|
6711
7754
|
};
|
|
6712
7755
|
|
|
6713
7756
|
// daemon/src/git/service.ts
|
|
7757
|
+
init_i18n();
|
|
6714
7758
|
import { execFile as execFile4 } from "node:child_process";
|
|
6715
7759
|
import { existsSync as existsSync10 } from "node:fs";
|
|
6716
|
-
import { cp, mkdir as
|
|
6717
|
-
import { join as
|
|
7760
|
+
import { cp, mkdir as mkdir7, readFile as readFile16, rename as rename4, rm, stat as stat9 } from "node:fs/promises";
|
|
7761
|
+
import { join as join16, normalize as normalize2, sep as sep2 } from "node:path";
|
|
6718
7762
|
var TIMEOUT2 = 15e3;
|
|
6719
7763
|
var DIFF_LIMIT = 1024 * 1024;
|
|
6720
7764
|
function runGit(cwd, args) {
|
|
@@ -6722,13 +7766,13 @@ function runGit(cwd, args) {
|
|
|
6722
7766
|
execFile4("git", ["-c", "core.quotepath=false", ...args], { cwd, timeout: TIMEOUT2, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (e, stdout, stderr) => {
|
|
6723
7767
|
const code = e && typeof e.code === "number" ? e.code : e ? 1 : 0;
|
|
6724
7768
|
const missing = e?.code === "ENOENT";
|
|
6725
|
-
res({ code, out: String(stdout ?? ""), err: missing ? "
|
|
7769
|
+
res({ code, out: String(stdout ?? ""), err: missing ? tr("git.missing") : String(stderr || (e?.message ?? "")) });
|
|
6726
7770
|
});
|
|
6727
7771
|
});
|
|
6728
7772
|
}
|
|
6729
7773
|
function insidePath(projectPath, p) {
|
|
6730
7774
|
const rel = normalize2(p).replace(/\\/g, "/");
|
|
6731
|
-
const abs = normalize2(
|
|
7775
|
+
const abs = normalize2(join16(projectPath, rel));
|
|
6732
7776
|
const proj = normalize2(projectPath);
|
|
6733
7777
|
if (abs !== proj && !abs.startsWith(proj + sep2)) return null;
|
|
6734
7778
|
return rel.replace(/\\/g, "/");
|
|
@@ -6774,16 +7818,16 @@ var GitService = class {
|
|
|
6774
7818
|
// Unified diff text; {tooLarge:true} when the diff is > 1MB
|
|
6775
7819
|
async diff(projectPath, p) {
|
|
6776
7820
|
const rel = insidePath(projectPath, p);
|
|
6777
|
-
if (!rel) return { error: "
|
|
7821
|
+
if (!rel) return { error: tr("fs.pathDenied") };
|
|
6778
7822
|
const ls = await runGit(projectPath, ["ls-files", "--error-unmatch", "--", rel]);
|
|
6779
7823
|
if (ls.code !== 0) {
|
|
6780
|
-
const full =
|
|
6781
|
-
if (!existsSync10(full)) return { error: "
|
|
6782
|
-
const s = await
|
|
6783
|
-
if (!s.isFile()) return { error: "
|
|
7824
|
+
const full = join16(projectPath, rel);
|
|
7825
|
+
if (!existsSync10(full)) return { error: tr("fs.fileMissing") };
|
|
7826
|
+
const s = await stat9(full);
|
|
7827
|
+
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
6784
7828
|
if (s.size > DIFF_LIMIT) return { tooLarge: true };
|
|
6785
|
-
const text = await
|
|
6786
|
-
if (text === null) return { error: "
|
|
7829
|
+
const text = await readFile16(full, "utf8").catch(() => null);
|
|
7830
|
+
if (text === null) return { error: tr("git.binary") };
|
|
6787
7831
|
const lines = text.split("\n").map((l) => "+" + l).join("\n");
|
|
6788
7832
|
return { untracked: true, diff: `--- /dev/null
|
|
6789
7833
|
+++ b/${rel}
|
|
@@ -6802,8 +7846,8 @@ ${lines}` };
|
|
|
6802
7846
|
// Tracked → git restore; untracked → move to trash/discarded/<ts>/<relpath>
|
|
6803
7847
|
async discard(projectPath, p, dataDir) {
|
|
6804
7848
|
const rel = insidePath(projectPath, p);
|
|
6805
|
-
if (!rel) return { ok: false, error: "
|
|
6806
|
-
if (rel === "." || rel === "" || rel === "./") return { ok: false, error: "
|
|
7849
|
+
if (!rel) return { ok: false, error: tr("fs.pathDenied") };
|
|
7850
|
+
if (rel === "." || rel === "" || rel === "./") return { ok: false, error: tr("fs.pathDenied") };
|
|
6807
7851
|
const ls = await runGit(projectPath, ["ls-files", "--error-unmatch", "--", rel]);
|
|
6808
7852
|
if (ls.code === 0) {
|
|
6809
7853
|
let r = await runGit(projectPath, ["restore", "--staged", "--worktree", "--", rel]);
|
|
@@ -6811,11 +7855,11 @@ ${lines}` };
|
|
|
6811
7855
|
if (r.code !== 0) return { ok: false, error: r.err.trim() || "\u653E\u5F03\u5931\u8D25" };
|
|
6812
7856
|
return { ok: true };
|
|
6813
7857
|
}
|
|
6814
|
-
const full =
|
|
6815
|
-
if (!existsSync10(full)) return { ok: false, error: "
|
|
7858
|
+
const full = join16(projectPath, rel);
|
|
7859
|
+
if (!existsSync10(full)) return { ok: false, error: tr("fs.fileMissing") };
|
|
6816
7860
|
const stamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
6817
|
-
const dest =
|
|
6818
|
-
await
|
|
7861
|
+
const dest = join16(dataDir, "trash", "discarded", stamp2, rel);
|
|
7862
|
+
await mkdir7(join16(dest, ".."), { recursive: true });
|
|
6819
7863
|
try {
|
|
6820
7864
|
await moveAcrossDevices(full, dest);
|
|
6821
7865
|
} catch (e) {
|
|
@@ -6826,8 +7870,9 @@ ${lines}` };
|
|
|
6826
7870
|
};
|
|
6827
7871
|
|
|
6828
7872
|
// daemon/src/files/service.ts
|
|
6829
|
-
|
|
6830
|
-
import {
|
|
7873
|
+
init_i18n();
|
|
7874
|
+
import { readdir as readdir5, readFile as readFile17, stat as stat10 } from "node:fs/promises";
|
|
7875
|
+
import { join as join17, normalize as normalize3, sep as sep3, extname as extname3 } from "node:path";
|
|
6831
7876
|
import ignore from "ignore";
|
|
6832
7877
|
function decodeText(buf) {
|
|
6833
7878
|
try {
|
|
@@ -6890,7 +7935,7 @@ var LANG = {
|
|
|
6890
7935
|
};
|
|
6891
7936
|
function insidePath2(projectPath, p) {
|
|
6892
7937
|
const rel = normalize3(p || ".").replace(/\\/g, "/");
|
|
6893
|
-
const abs = normalize3(
|
|
7938
|
+
const abs = normalize3(join17(projectPath, rel));
|
|
6894
7939
|
const proj = normalize3(projectPath);
|
|
6895
7940
|
if (abs !== proj && !abs.startsWith(proj + sep3)) return null;
|
|
6896
7941
|
return rel;
|
|
@@ -6900,15 +7945,15 @@ var FileService = class {
|
|
|
6900
7945
|
// Invalidate by .gitignore mtime: an edited .gitignore takes effect the next time a directory is expanded
|
|
6901
7946
|
igCache = /* @__PURE__ */ new Map();
|
|
6902
7947
|
async igFor(projectPath) {
|
|
6903
|
-
const gi =
|
|
6904
|
-
const st = await
|
|
7948
|
+
const gi = join17(projectPath, ".gitignore");
|
|
7949
|
+
const st = await stat10(gi).catch(() => null);
|
|
6905
7950
|
const mtime = st?.isFile() ? st.mtimeMs : -1;
|
|
6906
7951
|
const hit = this.igCache.get(projectPath);
|
|
6907
7952
|
if (hit && hit.mtime === mtime) return hit.ig;
|
|
6908
7953
|
let ig = null;
|
|
6909
7954
|
if (mtime >= 0) {
|
|
6910
7955
|
try {
|
|
6911
|
-
ig = ignore().add(await
|
|
7956
|
+
ig = ignore().add(await readFile17(gi, "utf8"));
|
|
6912
7957
|
} catch {
|
|
6913
7958
|
ig = null;
|
|
6914
7959
|
}
|
|
@@ -6918,13 +7963,13 @@ var FileService = class {
|
|
|
6918
7963
|
}
|
|
6919
7964
|
async tree(projectPath, dir) {
|
|
6920
7965
|
const rel = insidePath2(projectPath, dir);
|
|
6921
|
-
if (rel === null) return { error: "
|
|
6922
|
-
const abs =
|
|
6923
|
-
const s = await
|
|
6924
|
-
if (!s?.isDirectory()) return { error: "
|
|
7966
|
+
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7967
|
+
const abs = join17(projectPath, rel);
|
|
7968
|
+
const s = await stat10(abs).catch(() => null);
|
|
7969
|
+
if (!s?.isDirectory()) return { error: tr("fs.dirMissing") };
|
|
6925
7970
|
const ig = await this.igFor(projectPath);
|
|
6926
7971
|
const dirents = await readdir5(abs, { withFileTypes: true }).catch(() => null);
|
|
6927
|
-
if (!dirents) return { error: "
|
|
7972
|
+
if (!dirents) return { error: tr("fs.dirUnreadable") };
|
|
6928
7973
|
const out = [];
|
|
6929
7974
|
for (const d of dirents) {
|
|
6930
7975
|
if (out.length >= MAX_PER_LEVEL) break;
|
|
@@ -6933,7 +7978,7 @@ var FileService = class {
|
|
|
6933
7978
|
const childRel = rel === "." ? name : `${rel}/${name}`;
|
|
6934
7979
|
let isDir2 = d.isDirectory();
|
|
6935
7980
|
if (d.isSymbolicLink()) {
|
|
6936
|
-
const st = await
|
|
7981
|
+
const st = await stat10(join17(projectPath, childRel)).catch(() => null);
|
|
6937
7982
|
if (!st) continue;
|
|
6938
7983
|
isDir2 = st.isDirectory();
|
|
6939
7984
|
} else if (!d.isFile() && !isDir2) {
|
|
@@ -6949,14 +7994,14 @@ var FileService = class {
|
|
|
6949
7994
|
}
|
|
6950
7995
|
async read(projectPath, p) {
|
|
6951
7996
|
const rel = insidePath2(projectPath, p);
|
|
6952
|
-
if (rel === null) return { error: "
|
|
6953
|
-
const abs =
|
|
6954
|
-
const s = await
|
|
6955
|
-
if (!s) return { error: "
|
|
6956
|
-
if (!s.isFile()) return { error: "
|
|
7997
|
+
if (rel === null) return { error: tr("fs.pathDenied") };
|
|
7998
|
+
const abs = join17(projectPath, rel);
|
|
7999
|
+
const s = await stat10(abs).catch(() => null);
|
|
8000
|
+
if (!s) return { error: tr("fs.fileMissing") };
|
|
8001
|
+
if (!s.isFile()) return { error: tr("fs.notFile") };
|
|
6957
8002
|
if (s.size > MAX_READ) return { tooLarge: true };
|
|
6958
|
-
const buf = await
|
|
6959
|
-
if (!buf) return { error: "
|
|
8003
|
+
const buf = await readFile17(abs).catch(() => null);
|
|
8004
|
+
if (!buf) return { error: tr("fs.unreadable") };
|
|
6960
8005
|
const head = buf.subarray(0, 8192);
|
|
6961
8006
|
if (head.includes(0)) return { binary: true };
|
|
6962
8007
|
const { text, encoding } = decodeText(buf);
|
|
@@ -6967,11 +8012,12 @@ var FileService = class {
|
|
|
6967
8012
|
// daemon/src/terminal/service.ts
|
|
6968
8013
|
init_pathscan();
|
|
6969
8014
|
init_log();
|
|
8015
|
+
init_i18n();
|
|
6970
8016
|
import { spawn as spawn3, execFileSync as execFileSync2 } from "node:child_process";
|
|
6971
8017
|
import { accessSync as accessSync2, chmodSync, constants as fsc, existsSync as existsSync11, statSync as statSync6 } from "node:fs";
|
|
6972
8018
|
import { createRequire as moduleRequire } from "node:module";
|
|
6973
8019
|
import { release } from "node:os";
|
|
6974
|
-
import { dirname as dirname6, join as
|
|
8020
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
6975
8021
|
import { StringDecoder } from "node:string_decoder";
|
|
6976
8022
|
var MAX_TERMS_PER_PROJECT = 8;
|
|
6977
8023
|
var BUF_MAX = 2 * 1024 * 1024;
|
|
@@ -7017,7 +8063,7 @@ function fixSpawnHelper() {
|
|
|
7017
8063
|
const req = moduleRequire(import.meta.url);
|
|
7018
8064
|
const root = dirname6(req.resolve("node-pty/package.json"));
|
|
7019
8065
|
for (const sub of [`prebuilds/darwin-${process.arch}`, "build/Release"]) {
|
|
7020
|
-
const f =
|
|
8066
|
+
const f = join18(root, sub, "spawn-helper");
|
|
7021
8067
|
if (existsSync11(f) && !isExecFile(f)) {
|
|
7022
8068
|
chmodSync(f, 493);
|
|
7023
8069
|
log("info", "\u5DF2\u4FEE\u590D node-pty spawn-helper \u7684\u53EF\u6267\u884C\u4F4D", { file: f });
|
|
@@ -7060,14 +8106,14 @@ var TerminalService = class {
|
|
|
7060
8106
|
}
|
|
7061
8107
|
async create(projectId, cwd, cols, rows) {
|
|
7062
8108
|
const alive = [...this.terms.values()].filter((t) => t.projectId === projectId && t.alive);
|
|
7063
|
-
if (alive.length >= MAX_TERMS_PER_PROJECT) return { error:
|
|
8109
|
+
if (alive.length >= MAX_TERMS_PER_PROJECT) return { error: tr("term.max", { n: MAX_TERMS_PER_PROJECT }) };
|
|
7064
8110
|
const shell = pickShell();
|
|
7065
8111
|
const term = {
|
|
7066
8112
|
id: rid5(),
|
|
7067
8113
|
index: nextIndex++,
|
|
7068
8114
|
projectId,
|
|
7069
8115
|
cwd,
|
|
7070
|
-
title:
|
|
8116
|
+
title: tr("term.title", { n: alive.length + 1 }),
|
|
7071
8117
|
alive: true,
|
|
7072
8118
|
simple: false,
|
|
7073
8119
|
pty: null,
|
|
@@ -7115,25 +8161,25 @@ var TerminalService = class {
|
|
|
7115
8161
|
this.byIndex.set(term.index, term);
|
|
7116
8162
|
return { id: term.id, index: term.index, simple: term.simple };
|
|
7117
8163
|
}
|
|
7118
|
-
attach(
|
|
7119
|
-
const t = this.terms.get(
|
|
7120
|
-
if (!t) return { error: "
|
|
8164
|
+
attach(id2) {
|
|
8165
|
+
const t = this.terms.get(id2);
|
|
8166
|
+
if (!t) return { error: tr("err.terminalMissing") };
|
|
7121
8167
|
const lines = t.buf.split("\n");
|
|
7122
8168
|
const snapshot = lines.length > SNAP_LINES ? lines.slice(-SNAP_LINES).join("\n") : t.buf;
|
|
7123
8169
|
const windowsPty = process.platform === "win32" && t.pty ? conptyInfo() : void 0;
|
|
7124
8170
|
return { index: t.index, snapshot, simple: t.simple, ...windowsPty ? { windowsPty } : {} };
|
|
7125
8171
|
}
|
|
7126
|
-
indexOf(
|
|
7127
|
-
return this.terms.get(
|
|
8172
|
+
indexOf(id2) {
|
|
8173
|
+
return this.terms.get(id2)?.index ?? null;
|
|
7128
8174
|
}
|
|
7129
|
-
resize(
|
|
7130
|
-
const t = this.terms.get(
|
|
8175
|
+
resize(id2, cols, rows) {
|
|
8176
|
+
const t = this.terms.get(id2);
|
|
7131
8177
|
if (!t || !t.alive) return { ok: false };
|
|
7132
8178
|
t.pty?.resize(Math.max(20, cols), Math.max(5, rows));
|
|
7133
8179
|
return { ok: true };
|
|
7134
8180
|
}
|
|
7135
|
-
kill(
|
|
7136
|
-
const t = this.terms.get(
|
|
8181
|
+
kill(id2) {
|
|
8182
|
+
const t = this.terms.get(id2);
|
|
7137
8183
|
if (!t) return { ok: false };
|
|
7138
8184
|
this.killTerm(t);
|
|
7139
8185
|
return { ok: true };
|
|
@@ -7273,6 +8319,7 @@ if (nodeMajor < 20) {
|
|
|
7273
8319
|
process.env.PATH = augmentPath(process.env, dirname7(process.execPath));
|
|
7274
8320
|
var cfg = parseConfig(process.argv.slice(2));
|
|
7275
8321
|
process.on("uncaughtException", (e) => {
|
|
8322
|
+
if (e?.code === "EPIPE") return;
|
|
7276
8323
|
log("error", "uncaughtException", { err: String(e?.stack ?? e) });
|
|
7277
8324
|
});
|
|
7278
8325
|
process.on("unhandledRejection", (e) => {
|
|
@@ -7281,10 +8328,10 @@ process.on("unhandledRejection", (e) => {
|
|
|
7281
8328
|
async function main() {
|
|
7282
8329
|
initLog(cfg.dataDir);
|
|
7283
8330
|
for (const d of ["threads", "attachments", "trash", "logs"]) {
|
|
7284
|
-
mkdirSync2(
|
|
8331
|
+
mkdirSync2(join20(cfg.dataDir, d), { recursive: true });
|
|
7285
8332
|
}
|
|
7286
|
-
const appDir =
|
|
7287
|
-
const attachments = new AttachmentStore(
|
|
8333
|
+
const appDir = join20(dirname7(fileURLToPath3(import.meta.url)), "..", "..", "app");
|
|
8334
|
+
const attachments = new AttachmentStore(join20(cfg.dataDir, "attachments"));
|
|
7288
8335
|
const { bindAttachmentStore: bindAttachmentStore2 } = await Promise.resolve().then(() => (init_attachmentRefs(), attachmentRefs_exports));
|
|
7289
8336
|
bindAttachmentStore2(attachments);
|
|
7290
8337
|
const settings = new SettingsStore(cfg.dataDir);
|
|
@@ -7311,7 +8358,7 @@ async function main() {
|
|
|
7311
8358
|
git: new GitService(),
|
|
7312
8359
|
files: new FileService(),
|
|
7313
8360
|
terminals,
|
|
7314
|
-
runtime: (
|
|
8361
|
+
runtime: (id2) => manager.runtimeInfo(id2)
|
|
7315
8362
|
});
|
|
7316
8363
|
const server = createHttpServer(cfg, appDir, attachments, {
|
|
7317
8364
|
onUpgrade: (req, socket, head) => gateway.handleUpgrade(req, socket, head)
|
|
@@ -7343,7 +8390,7 @@ async function main() {
|
|
|
7343
8390
|
const { warmProviderCache: warmProviderCache2 } = await Promise.resolve().then(() => (init_warm(), warm_exports));
|
|
7344
8391
|
await warmProviderCache2(await settings.get());
|
|
7345
8392
|
})().catch((e) => log("warn", "Provider \u9884\u70ED\u5931\u8D25", { err: String(e) }));
|
|
7346
|
-
const daemonJsonPath =
|
|
8393
|
+
const daemonJsonPath = join20(cfg.dataDir, "daemon.json");
|
|
7347
8394
|
await writeJsonAtomic(daemonJsonPath, {
|
|
7348
8395
|
pid: process.pid,
|
|
7349
8396
|
port: cfg.port,
|
|
@@ -7354,7 +8401,9 @@ async function main() {
|
|
|
7354
8401
|
});
|
|
7355
8402
|
chmodSync2(daemonJsonPath, 384);
|
|
7356
8403
|
log("info", `daemon \u5DF2\u542F\u52A8 \u7AEF\u53E3 ${cfg.port} \u6570\u636E\u76EE\u5F55 ${cfg.dataDir} \u7248\u672C ${cfg.version} pid ${process.pid}`);
|
|
7357
|
-
if (!cfg.parentPid)
|
|
8404
|
+
if (!cfg.parentPid) {
|
|
8405
|
+
console.log(process.stdout.isTTY ? `[agent-console] open http://127.0.0.1:${cfg.port}/#t=${cfg.token}` : `[agent-console] listening on http://127.0.0.1:${cfg.port}/ (token not printed: stdout is not a terminal)`);
|
|
8406
|
+
}
|
|
7358
8407
|
let idleSince = Date.now();
|
|
7359
8408
|
setInterval(() => {
|
|
7360
8409
|
const busy = gateway.connectionCount > 0 || manager.anyBusy() || terminals.count > 0;
|
|
@@ -7391,9 +8440,16 @@ async function main() {
|
|
|
7391
8440
|
}
|
|
7392
8441
|
}, 2e3);
|
|
7393
8442
|
parentWatch.unref();
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
8443
|
+
let lifelineGone = false;
|
|
8444
|
+
const lifelineClosed = () => {
|
|
8445
|
+
if (lifelineGone) return;
|
|
8446
|
+
lifelineGone = true;
|
|
8447
|
+
log("info", "plugin host closed the lifeline (stdin); daemon exiting with it");
|
|
8448
|
+
shutdown(0);
|
|
8449
|
+
};
|
|
8450
|
+
process.stdin.on("end", lifelineClosed);
|
|
8451
|
+
process.stdin.on("close", lifelineClosed);
|
|
8452
|
+
process.stdin.on("error", lifelineClosed);
|
|
7397
8453
|
process.stdin.resume();
|
|
7398
8454
|
}
|
|
7399
8455
|
}
|