pi-web-ui 0.92.0 → 0.93.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/CHANGELOG.md +58 -2
- package/README.md +69 -14
- package/README.zh-CN.md +36 -10
- package/bin/pi-web-ui.mjs +18 -6
- package/dist/server/agent-service.js +497 -120
- package/dist/server/auth-cookie.js +26 -0
- package/dist/server/claim-files-tool.js +6 -7
- package/dist/server/client-state.js +65 -6
- package/dist/server/conversation-read-tool.js +2 -1
- package/dist/server/delegate-task.js +10 -3
- package/dist/server/dsh/dsh-agent-service.js +43 -1
- package/dist/server/files-service.js +78 -26
- package/dist/server/http-proxy.js +72 -0
- package/dist/server/index.js +62 -7
- package/dist/server/patch-node-pty.js +11 -0
- package/dist/server/plugins.js +1 -0
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/read-tool.js +37 -3
- package/dist/server/resolve-global-sdk.js +72 -0
- package/dist/server/sdk-origin.js +64 -0
- package/dist/server/serialize.js +59 -21
- package/dist/server/settings-service.js +12 -1
- package/dist/server/skill-tool.js +2 -1
- package/dist/server/subagents.js +30 -13
- package/dist/server/terminals.js +38 -18
- package/dist/server/themes.js +15 -0
- package/dist/server/tool-info.js +89 -0
- package/dist/server/tool-manager.js +107 -14
- package/package.json +2 -2
- package/plugins/catalog.json +10 -0
- package/themes/ayu-light.css +136 -0
- package/themes/catppuccin-latte.css +4 -0
- package/themes/catppuccin.css +4 -0
- package/themes/codex.css +136 -0
- package/themes/cyberpunk.css +4 -0
- package/themes/dazzle.css +4 -0
- package/themes/everforest-light.css +136 -0
- package/themes/geist.css +136 -0
- package/themes/gruvbox-light.css +136 -0
- package/themes/kanagawa-lotus.css +136 -0
- package/themes/md-preview.css +4 -0
- package/themes/mist.css +4 -0
- package/themes/nord.css +4 -0
- package/themes/one-dark.css +4 -0
- package/themes/paper.css +4 -0
- package/themes/rose-pine-dawn.css +136 -0
- package/themes/sakura.css +4 -0
- package/themes/solarized-light.css +4 -0
- package/themes/tokyo-night.css +4 -0
- package/themes/white.css +4 -0
- package/web/dist/assets/{TerminalPanel-RACpk6AX.js → TerminalPanel-C1A23aI-.js} +1 -1
- package/web/dist/assets/index-B48QWf0I.js +364 -0
- package/web/dist/assets/index-DGL5OInG.css +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BgrnP9Jz.js +0 -364
- package/web/dist/assets/index-Cy8dSTZt.css +0 -1
|
@@ -21,6 +21,7 @@ import { statSync, readFileSync } from "node:fs";
|
|
|
21
21
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
import { Type } from "typebox";
|
|
23
23
|
import { bilingual, pick } from "./i18n.js";
|
|
24
|
+
import { SKILL_TOOL_NAME } from "./tool-manager.js";
|
|
24
25
|
import { decodeText } from "./text-sniff.js";
|
|
25
26
|
/** 单文件正文上限(与 agent-service fillSkillContents 同口径:8KB)。 */
|
|
26
27
|
export const SKILL_TOOL_FILE_CAP = 8192;
|
|
@@ -58,7 +59,7 @@ export function makeSkillTool(host, lang) {
|
|
|
58
59
|
details,
|
|
59
60
|
});
|
|
60
61
|
return defineTool({
|
|
61
|
-
name:
|
|
62
|
+
name: SKILL_TOOL_NAME,
|
|
62
63
|
label: "Load a skill",
|
|
63
64
|
description: bilingual("Load a skill's full text by its exact name (see the <available_skills> catalog in the system prompt). " +
|
|
64
65
|
"Prefer this over reading the skill file with the read tool — no path guessing needed. " +
|
package/dist/server/subagents.js
CHANGED
|
@@ -61,7 +61,7 @@ export function subagentTitle(prompt) {
|
|
|
61
61
|
export function withSubagentOwner(host, ownerId) {
|
|
62
62
|
return {
|
|
63
63
|
...host,
|
|
64
|
-
spawnSubagent: (prompt, type, cwd, templateName, model) => host.spawnSubagent(prompt, type, cwd, templateName, model, ownerId),
|
|
64
|
+
spawnSubagent: (prompt, type, cwd, templateName, model, _parentId, persist) => host.spawnSubagent(prompt, type, cwd, templateName, model, ownerId, persist),
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
@@ -125,6 +125,11 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
125
125
|
cwd: Type.Optional(Type.String({
|
|
126
126
|
description: bilingual("Subagent working directory (relative/absolute). Defaults to the main session's cwd.", "子代理工作目录(相对/绝对)。默认继承主会话工作目录。"),
|
|
127
127
|
})),
|
|
128
|
+
persist: Type.Optional(Type.Boolean({
|
|
129
|
+
description: bilingual("Optional: persist this conversation to disk as a regular session (saved in history, resumable). " +
|
|
130
|
+
"Default false (lightweight in-memory subagent). Use true for tasks that need long-term retention or human follow-up.", "可选:是否将该对话持久化落盘为普通对话(保存在历史会话中,可随时回顾与继续)。" +
|
|
131
|
+
"默认 false(轻量内存子代理)。需要长期留存或后续人工跟进的任务建议设为 true。"),
|
|
132
|
+
})),
|
|
128
133
|
}),
|
|
129
134
|
execute: async (_id, p, _signal, _onUpdate, ctx) => {
|
|
130
135
|
if (p.template && !host.isTemplateUsable(p.template)) {
|
|
@@ -134,7 +139,7 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
134
139
|
// host 抛错):转成返回文本而不是直接抛,让 AI 能读到原因并调整重试。
|
|
135
140
|
let convId;
|
|
136
141
|
try {
|
|
137
|
-
convId = await host.spawnSubagent(p.prompt, p.type ?? "general", p.cwd ?? ctx.cwd, p.template, p.model);
|
|
142
|
+
convId = await host.spawnSubagent(p.prompt, p.type ?? "general", p.cwd ?? ctx.cwd, p.template, p.model, undefined, p.persist);
|
|
138
143
|
}
|
|
139
144
|
catch (err) {
|
|
140
145
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -151,8 +156,10 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
151
156
|
const templateLineEn = p.template ? `\nTemplate: ${p.template}` : "";
|
|
152
157
|
const modelLineZh = p.model ? `\n模型:${p.model}` : "";
|
|
153
158
|
const modelLineEn = p.model ? `\nModel: ${p.model}` : "";
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
const kindLabelZh = p.persist ? "普通持久化对话" : "子代理";
|
|
160
|
+
const kindLabelEn = p.persist ? "Persistent conversation" : "Subagent";
|
|
161
|
+
return text(pick(getLang(), `${kindLabelZh}已启动(运行列表可见):${convId}\n类型:${subagentType} · 标题:${subagentTitleText}${templateLineZh}${modelLineZh}` +
|
|
162
|
+
`\n用 subagent_wait_all 一次等全部完成(不用轮询),subagent_get_result 取单个结果,subagent_list 看运行态,subagent_steer 改向,subagent_stop 停止。`, `${kindLabelEn} started (visible in the running list): ${convId}\nType: ${subagentType} · Title: ${subagentTitleText}${templateLineEn}${modelLineEn}` +
|
|
156
163
|
`\nUse subagent_wait_all to wait for all at once (no polling), subagent_get_result for a single result, subagent_list for live status, subagent_steer to redirect, subagent_stop to stop.`, "subagents.spawn.started", {
|
|
157
164
|
convId: convId,
|
|
158
165
|
subagentType: subagentType,
|
|
@@ -163,7 +170,7 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
163
170
|
templateLineEn: templateLineEn,
|
|
164
171
|
modelLineZh: modelLineZh,
|
|
165
172
|
modelLineEn: modelLineEn,
|
|
166
|
-
}), { convId, template: p.template, model: p.model });
|
|
173
|
+
}), { convId, template: p.template, model: p.model, persisted: !!p.persist });
|
|
167
174
|
},
|
|
168
175
|
}),
|
|
169
176
|
defineTool({
|
|
@@ -220,16 +227,24 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
220
227
|
defineTool({
|
|
221
228
|
name: "subagent_list",
|
|
222
229
|
label: "List subagents",
|
|
223
|
-
description: bilingual("List all subagents and their live status: convId, type, state, title, message count (errors/aborts are marked in the state).", "
|
|
230
|
+
description: bilingual("List all subagents and managed conversations with their live status: convId, type, state, title, message count (errors/aborts are marked in the state).", "列出全部子代理及受控对话的运行态:convId、类型、状态、标题、消息数(报错/中止的会在状态里标出)。"),
|
|
224
231
|
promptSnippet: "list all subagents and their live status",
|
|
225
|
-
parameters: Type.Object({
|
|
226
|
-
|
|
227
|
-
|
|
232
|
+
parameters: Type.Object({
|
|
233
|
+
kind: Type.Optional(Type.String({
|
|
234
|
+
enum: ["all", "subagent", "persistent"],
|
|
235
|
+
description: bilingual("Filter: all (default) = all managed tasks; subagent = only ephemeral in-memory subagents; persistent = only persistent conversations.", "过滤类型:all(默认)= 全部受控对话;subagent = 仅临时内存子代理;persistent = 仅持久化普通对话。"),
|
|
236
|
+
})),
|
|
237
|
+
}),
|
|
238
|
+
execute: async (_id, p) => {
|
|
239
|
+
const list = host.listSubagents(p.kind);
|
|
228
240
|
if (list.length === 0)
|
|
229
|
-
return text(pick(getLang(), "
|
|
241
|
+
return text(pick(getLang(), "当前没有运行中的对话/子代理。", "No managed conversations or subagents running.", "subagents.list.empty"));
|
|
230
242
|
const tLang = getLang();
|
|
231
|
-
const lines = list.map((r) =>
|
|
232
|
-
(tLang === "zh" ?
|
|
243
|
+
const lines = list.map((r) => {
|
|
244
|
+
const tag = r.persisted ? (tLang === "zh" ? "持久化" : "persistent") : tLang === "zh" ? "子代理" : "subagent";
|
|
245
|
+
return (`- ${r.convId} · ${r.type} · ${subagentVerdict(r, tLang)} · ${r.title}` +
|
|
246
|
+
(tLang === "zh" ? `(${tag} · msg: ${r.messageCount})` : ` (${tag} · msg: ${r.messageCount})`));
|
|
247
|
+
});
|
|
233
248
|
return text(lines.join("\n"));
|
|
234
249
|
},
|
|
235
250
|
}),
|
|
@@ -374,7 +389,9 @@ export function makeSubagentTools(host, lang, selfConvId) {
|
|
|
374
389
|
const emptyLang = getLang();
|
|
375
390
|
return text(pick(emptyLang, "没有需要等待的子代理(调用者自身与祖先不计入等待;子代理无参时只等自己的后代)。", "No subagents to wait for (the calling session itself and its ancestors are never waited on; a subagent without runIds only waits for its own descendants).", "subagents.wait.empty"));
|
|
376
391
|
}
|
|
377
|
-
const
|
|
392
|
+
const currentWatchdogMs = host.getWatchdogTimeoutMs?.() ?? WAIT_CAP_MS;
|
|
393
|
+
const currentWaitCapMs = currentWatchdogMs > 0 ? Math.max(60_000, Math.floor(currentWatchdogMs * 0.8)) : 3600_000;
|
|
394
|
+
const timeoutMs = Math.min(Math.max(p.timeoutSeconds ?? 600, 1), Math.floor(currentWaitCapMs / 1000)) * 1000;
|
|
378
395
|
const waitStart = Date.now();
|
|
379
396
|
const deadline = waitStart + timeoutMs;
|
|
380
397
|
// 已到终态的、(或已被移出找不到的)直接归位;剩下的阻塞轮询到
|
package/dist/server/terminals.js
CHANGED
|
@@ -1295,10 +1295,16 @@ export class TerminalManager {
|
|
|
1295
1295
|
w.cb(null);
|
|
1296
1296
|
entry.exitCode = exitCode;
|
|
1297
1297
|
this.terms.delete(id);
|
|
1298
|
+
// 进程已退出,立即释放底层的 PTY 句柄(Windows ConPTY / HPCON / sockets 等),
|
|
1299
|
+
// 避免已退出的终端长期占用 ConPTY 与 MSYS2 控制台资源(issue #269)。
|
|
1300
|
+
this.killNative(entry);
|
|
1298
1301
|
while (this.history.size >= MAX_TERMINAL_HISTORY) {
|
|
1299
1302
|
const oldest = this.history.keys().next().value;
|
|
1300
1303
|
if (typeof oldest !== "string")
|
|
1301
1304
|
break;
|
|
1305
|
+
const oldEntry = this.history.get(oldest);
|
|
1306
|
+
if (oldEntry)
|
|
1307
|
+
this.killNative(oldEntry);
|
|
1302
1308
|
this.history.delete(oldest);
|
|
1303
1309
|
}
|
|
1304
1310
|
this.history.set(id, entry);
|
|
@@ -1339,8 +1345,13 @@ export class TerminalManager {
|
|
|
1339
1345
|
this.emitList();
|
|
1340
1346
|
return;
|
|
1341
1347
|
}
|
|
1342
|
-
|
|
1348
|
+
const histEntry = this.history.get(id);
|
|
1349
|
+
if (histEntry) {
|
|
1350
|
+
this.killNative(histEntry);
|
|
1351
|
+
this.history.delete(id);
|
|
1343
1352
|
this.emitList();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1344
1355
|
}
|
|
1345
1356
|
/** Rename a terminal tab (live or retained history). Empty names ignored. */
|
|
1346
1357
|
rename(id, title) {
|
|
@@ -1354,27 +1365,33 @@ export class TerminalManager {
|
|
|
1354
1365
|
this.emitList();
|
|
1355
1366
|
}
|
|
1356
1367
|
/**
|
|
1357
|
-
* Kill the native PTY (issue #215:Windows ConPTY
|
|
1368
|
+
* Kill the native PTY (issue #215:Windows ConPTY 关机死锁;issue #269:MSYS2 控制台耗尽死锁)。
|
|
1358
1369
|
*
|
|
1359
|
-
* Windows
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1362
|
-
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
* 非 Windows 原样直调 `pty.kill()`。
|
|
1370
|
+
* Windows 下:
|
|
1371
|
+
* 1. 若进程已经退出(entry.exited):绝不可再调用 Node 的 process.kill(pid),
|
|
1372
|
+
* 直接调 pty.kill() 释放 HPCON 句柄(子进程已死、管道见 EOF,绝不会死锁)。
|
|
1373
|
+
* 2. 若进程尚未退出(!entry.exited):
|
|
1374
|
+
* 先尝试优雅关闭(向 PTY 写入 \x03exit\r),让 bash 正常触发清理钩子;
|
|
1375
|
+
* 若仍未退出才以 process.kill(pid)(TerminateProcess)强制兜底。
|
|
1376
|
+
* 3. 关机(shutdown=true):跳过 pty.kill(),由 OS 回收。
|
|
1377
|
+
* 非 Windows 原样直调 pty.kill()。
|
|
1368
1378
|
*/
|
|
1369
1379
|
killNative(entry, shutdown = false) {
|
|
1370
1380
|
if (process.platform === "win32") {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1381
|
+
if (!entry.exited) {
|
|
1382
|
+
const pid = entry.pty.pid;
|
|
1383
|
+
if (typeof pid === "number") {
|
|
1384
|
+
try {
|
|
1385
|
+
// 优雅关闭:先发 Ctrl+C,再发 exit\r
|
|
1386
|
+
entry.pty.write("\x03exit\r");
|
|
1387
|
+
}
|
|
1388
|
+
catch { }
|
|
1389
|
+
try {
|
|
1390
|
+
process.kill(pid);
|
|
1391
|
+
}
|
|
1392
|
+
catch {
|
|
1393
|
+
// already dead
|
|
1394
|
+
}
|
|
1378
1395
|
}
|
|
1379
1396
|
}
|
|
1380
1397
|
if (shutdown)
|
|
@@ -1410,6 +1427,9 @@ export class TerminalManager {
|
|
|
1410
1427
|
w.cb(null);
|
|
1411
1428
|
entry.watches = [];
|
|
1412
1429
|
}
|
|
1430
|
+
for (const entry of this.history.values()) {
|
|
1431
|
+
this.killNative(entry, shutdown);
|
|
1432
|
+
}
|
|
1413
1433
|
this.terms.clear();
|
|
1414
1434
|
this.history.clear();
|
|
1415
1435
|
this.emitList();
|
package/dist/server/themes.js
CHANGED
|
@@ -23,6 +23,20 @@ const ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
|
23
23
|
const THEME_NAME_RE = /\/\*\s*theme-name:\s*(.+?)\s*\*\//;
|
|
24
24
|
const THEME_NAME_EN_RE = /\/\*\s*theme-name-en:\s*(.+?)\s*\*\//;
|
|
25
25
|
const THEME_GROUP_RE = /\/\*\s*theme-group:\s*(.+?)\s*\*\//;
|
|
26
|
+
/** 从主题 CSS 头部读取 color-scheme(浅/暗),无法识别时返回 undefined。 */
|
|
27
|
+
const THEME_SCHEME_RE = /color-scheme:\s*(dark|light)/i;
|
|
28
|
+
function readScheme(path) {
|
|
29
|
+
try {
|
|
30
|
+
const head = readFileSync(path, "utf8").slice(0, 800);
|
|
31
|
+
const m = head.match(THEME_SCHEME_RE);
|
|
32
|
+
if (m && (m[1] === "dark" || m[1] === "light"))
|
|
33
|
+
return m[1];
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// ignore
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
26
40
|
function readDisplayName(path, fallback) {
|
|
27
41
|
try {
|
|
28
42
|
const head = readFileSync(path, "utf8").slice(0, 400);
|
|
@@ -72,6 +86,7 @@ export function listThemes(builtinDir, userDir) {
|
|
|
72
86
|
name,
|
|
73
87
|
builtin,
|
|
74
88
|
group,
|
|
89
|
+
scheme: readScheme(path),
|
|
75
90
|
...(nameEn ? { nameEn } : {}),
|
|
76
91
|
};
|
|
77
92
|
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** parameters(JSON Schema)的体积上限:超过就整丢。 */
|
|
2
|
+
export const TOOL_PARAMETERS_CAP = 64_000;
|
|
3
|
+
/** description 的字数上限(SDK 里的工具描述很少超过几百字,这里防的是扩展写疯)。 */
|
|
4
|
+
export const TOOL_DESCRIPTION_CAP = 8_000;
|
|
5
|
+
/** promptGuidelines 合计字数上限(进系统提示词的要点,正常都很短)。 */
|
|
6
|
+
export const TOOL_GUIDELINES_CAP = 8_000;
|
|
7
|
+
function str(v) {
|
|
8
|
+
if (typeof v !== "string")
|
|
9
|
+
return undefined;
|
|
10
|
+
const s = v.trim();
|
|
11
|
+
return s ? s : undefined;
|
|
12
|
+
}
|
|
13
|
+
/** 超长截断(`…` 结尾,与前端 truncateText 同款标记)。 */
|
|
14
|
+
function cut(s, cap) {
|
|
15
|
+
return s.length > cap ? `${s.slice(0, cap)}…` : s;
|
|
16
|
+
}
|
|
17
|
+
/** 参数 schema 是否可下发(能序列化且不超上限)。 */
|
|
18
|
+
function parametersWithinCap(params) {
|
|
19
|
+
if (params === undefined || params === null)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const text = JSON.stringify(params);
|
|
23
|
+
return typeof text === "string" && text.length <= TOOL_PARAMETERS_CAP;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// 循环引用 / BigInt 等序列化不了的值:当成「没有 schema」。
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 归一化一条工具定义 → wire 载荷。
|
|
32
|
+
*
|
|
33
|
+
* @param name 请求里的工具名(**永远以它为准**:定义里的 name 缺失时前端仍要能显示标题)。
|
|
34
|
+
* @param raw 引擎给的定义;undefined/null = 没找到。
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeToolInfo(name, raw) {
|
|
37
|
+
const toolName = str(name) ?? "";
|
|
38
|
+
if (!raw || typeof raw !== "object")
|
|
39
|
+
return { name: toolName, found: false };
|
|
40
|
+
const payload = { name: toolName, found: true };
|
|
41
|
+
const label = str(raw.label);
|
|
42
|
+
if (label)
|
|
43
|
+
payload.label = cut(label, 200);
|
|
44
|
+
const description = str(raw.description);
|
|
45
|
+
if (description)
|
|
46
|
+
payload.description = cut(description, TOOL_DESCRIPTION_CAP);
|
|
47
|
+
const snippet = str(raw.promptSnippet);
|
|
48
|
+
if (snippet)
|
|
49
|
+
payload.promptSnippet = cut(snippet, TOOL_DESCRIPTION_CAP);
|
|
50
|
+
if (Array.isArray(raw.promptGuidelines)) {
|
|
51
|
+
const guidelines = [];
|
|
52
|
+
let total = 0;
|
|
53
|
+
for (const g of raw.promptGuidelines) {
|
|
54
|
+
const text = str(g);
|
|
55
|
+
if (!text)
|
|
56
|
+
continue;
|
|
57
|
+
if (total + text.length > TOOL_GUIDELINES_CAP)
|
|
58
|
+
break;
|
|
59
|
+
guidelines.push(text);
|
|
60
|
+
total += text.length;
|
|
61
|
+
}
|
|
62
|
+
if (guidelines.length > 0)
|
|
63
|
+
payload.promptGuidelines = guidelines;
|
|
64
|
+
}
|
|
65
|
+
if (raw.parameters !== undefined && raw.parameters !== null) {
|
|
66
|
+
if (parametersWithinCap(raw.parameters))
|
|
67
|
+
payload.parameters = raw.parameters;
|
|
68
|
+
else
|
|
69
|
+
payload.parametersDropped = true;
|
|
70
|
+
}
|
|
71
|
+
if (raw.active === true)
|
|
72
|
+
payload.active = true;
|
|
73
|
+
else if (raw.active === false)
|
|
74
|
+
payload.active = false;
|
|
75
|
+
const info = raw.sourceInfo;
|
|
76
|
+
if (info && typeof info === "object") {
|
|
77
|
+
const source = str(info.source);
|
|
78
|
+
if (source)
|
|
79
|
+
payload.source = source;
|
|
80
|
+
const scope = str(info.scope);
|
|
81
|
+
if (scope)
|
|
82
|
+
payload.scope = scope;
|
|
83
|
+
}
|
|
84
|
+
return payload;
|
|
85
|
+
}
|
|
86
|
+
/** 当前引擎不支持枚举工具定义时的应答(前端据此显示「当前引擎不支持」而不是「未找到」)。 */
|
|
87
|
+
export function unsupportedToolInfo(name) {
|
|
88
|
+
return { name: str(name) ?? "", found: false, unsupported: true };
|
|
89
|
+
}
|
|
@@ -61,9 +61,12 @@ export const SCHEDULE_CANCEL_TOOL_NAME = "schedule_cancel";
|
|
|
61
61
|
/** 展示文件工具(定义见 present-files-tool.ts):把图片/视频/文本作为预览卡片
|
|
62
62
|
* 推到对话里,卡片带预览/本地打开/在文件夹中显示/下载/复制路径。 */
|
|
63
63
|
export const PRESENT_FILES_TOOL_NAME = "present_files";
|
|
64
|
+
/** 文件认领工具(定义见 claim-files-tool.ts):声明要改哪些文件,让同项目的
|
|
65
|
+
* 并行对话绕行(纯建议,不拦编辑)。 */
|
|
66
|
+
export const CLAIM_FILES_TOOL_NAME = "claim_files";
|
|
64
67
|
/** 旧工具名(持久化迁移用;新代码一律用 MARKERS_LIST_TOOL_NAME)。 */
|
|
65
68
|
export const LEGACY_MARKERS_LIST_TOOL_NAME = "markers_list";
|
|
66
|
-
/** 可开关的 Agent 工具总目录(共
|
|
69
|
+
/** 可开关的 Agent 工具总目录(共 26 个;bash 本体与 SDK 内置 edit/read
|
|
67
70
|
* 不进目录——关了 agent 就残了,不给关)。 */
|
|
68
71
|
export const AGENT_TOOL_CATALOG = [
|
|
69
72
|
...TERMINAL_TOOL_NAMES.map((name) => ({
|
|
@@ -78,26 +81,116 @@ export const AGENT_TOOL_CATALOG = [
|
|
|
78
81
|
defaultOn: true,
|
|
79
82
|
dshVisible: false,
|
|
80
83
|
})),
|
|
81
|
-
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
{
|
|
85
|
+
name: EDIT_SOFT_TOOL_NAME,
|
|
86
|
+
group: "other",
|
|
87
|
+
defaultOn: false,
|
|
88
|
+
dshVisible: false,
|
|
89
|
+
descKey: "editSoftEnabledDesc",
|
|
90
|
+
offHintKey: "editSoftOffHint",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: DELEGATE_TASK_TOOL_NAME,
|
|
94
|
+
group: "other",
|
|
95
|
+
defaultOn: true,
|
|
96
|
+
dshVisible: false,
|
|
97
|
+
descKey: "delegateTaskEnabledDesc",
|
|
98
|
+
offHintKey: "delegateTaskOffHint",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: ASK_USER_QUESTION_TOOL_NAME,
|
|
102
|
+
group: "other",
|
|
103
|
+
defaultOn: true,
|
|
104
|
+
dshVisible: true,
|
|
105
|
+
descKey: "questionnaireEnabledDesc",
|
|
106
|
+
offHintKey: "questionnaireOffHint",
|
|
107
|
+
},
|
|
85
108
|
// 默认关(AI 动用户浏览器,opt-in 才开)且 dshVisible=false:DSH 引擎没有页面桥
|
|
86
109
|
// (page_request 由 pi 引擎的 customTool 发出),列在那里只会让用户关一个不存在的工具。
|
|
87
|
-
{
|
|
110
|
+
{
|
|
111
|
+
name: BROWSER_PAGE_TOOL_NAME,
|
|
112
|
+
group: "other",
|
|
113
|
+
defaultOn: false,
|
|
114
|
+
dshVisible: false,
|
|
115
|
+
descKey: "browserPageEnabledDesc",
|
|
116
|
+
offHintKey: "browserPageOffHint",
|
|
117
|
+
},
|
|
88
118
|
// 只读别的对话(含子代理实时消息与历史转录),默认开;DSH 引擎没有该 customTool。
|
|
89
|
-
{
|
|
119
|
+
{
|
|
120
|
+
name: CONVERSATION_READ_TOOL_NAME,
|
|
121
|
+
group: "other",
|
|
122
|
+
defaultOn: true,
|
|
123
|
+
dshVisible: false,
|
|
124
|
+
descKey: "conversationReadEnabledDesc",
|
|
125
|
+
offHintKey: "conversationReadOffHint",
|
|
126
|
+
},
|
|
127
|
+
// 文件认领(事前打招呼,纯 advisory):默认开,不打开 AI 不知道能认领;
|
|
128
|
+
// 关掉只少一路提醒(事后触碰集照常工作);DSH 引擎没有该 customTool
|
|
129
|
+
// (走 goal-rpc,无 customTool 注册面),提醒是服务端算的、DSH 照样能看到。
|
|
130
|
+
{
|
|
131
|
+
name: CLAIM_FILES_TOOL_NAME,
|
|
132
|
+
group: "other",
|
|
133
|
+
defaultOn: true,
|
|
134
|
+
dshVisible: false,
|
|
135
|
+
descKey: "claimFilesEnabledDesc",
|
|
136
|
+
offHintKey: "claimFilesOffHint",
|
|
137
|
+
},
|
|
138
|
+
// 展示文件给用户(图片/视频内联、文本开预览弹窗、本地打开按钮):默认开,
|
|
139
|
+
// 不打开模型根本不知道能“给用户看”;DSH 引擎没有该 customTool(走 shipped preset)。
|
|
140
|
+
{
|
|
141
|
+
name: PRESENT_FILES_TOOL_NAME,
|
|
142
|
+
group: "other",
|
|
143
|
+
defaultOn: true,
|
|
144
|
+
dshVisible: false,
|
|
145
|
+
descKey: "presentFilesEnabledDesc",
|
|
146
|
+
offHintKey: "presentFilesOffHint",
|
|
147
|
+
},
|
|
90
148
|
// 技能全文按名加载(名录仍在 {{skills}} 段),默认开;DSH 引擎没有该 customTool
|
|
91
149
|
// (走 goal-rpc,无 customTool 注册面)。
|
|
92
|
-
{
|
|
150
|
+
{
|
|
151
|
+
name: SKILL_TOOL_NAME,
|
|
152
|
+
group: "other",
|
|
153
|
+
defaultOn: true,
|
|
154
|
+
dshVisible: false,
|
|
155
|
+
descKey: "skillEnabledDesc",
|
|
156
|
+
offHintKey: "skillOffHint",
|
|
157
|
+
},
|
|
93
158
|
// 定时/延时唤醒:默认开(不打开 AI 根本不知道能定时;60s 间隔底线+面板可随时取消),
|
|
94
159
|
// DSH 引擎没有该 customTool(走 goal-rpc,无 customTool 注册面)。
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
160
|
+
{
|
|
161
|
+
name: SCHEDULE_TASK_TOOL_NAME,
|
|
162
|
+
group: "other",
|
|
163
|
+
defaultOn: true,
|
|
164
|
+
dshVisible: false,
|
|
165
|
+
descKey: "scheduleTaskEnabledDesc",
|
|
166
|
+
offHintKey: "scheduleTaskOffHint",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: SCHEDULE_LIST_TOOL_NAME,
|
|
170
|
+
group: "other",
|
|
171
|
+
defaultOn: true,
|
|
172
|
+
dshVisible: false,
|
|
173
|
+
descKey: "scheduleTaskEnabledDesc",
|
|
174
|
+
offHintKey: "scheduleTaskOffHint",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: SCHEDULE_CANCEL_TOOL_NAME,
|
|
178
|
+
group: "other",
|
|
179
|
+
defaultOn: true,
|
|
180
|
+
dshVisible: false,
|
|
181
|
+
descKey: "scheduleTaskEnabledDesc",
|
|
182
|
+
offHintKey: "scheduleTaskOffHint",
|
|
183
|
+
},
|
|
184
|
+
// todo_list 唯一例外:行不在「其他」组,固定在上面的 markers 分区(设置页循环
|
|
185
|
+
// 跳过它,见 OTHER_AGENT_TOOLS;文案 key 照给,万一哪天搬家不用补)。
|
|
186
|
+
{
|
|
187
|
+
name: MARKERS_LIST_TOOL_NAME,
|
|
188
|
+
group: "other",
|
|
189
|
+
defaultOn: true,
|
|
190
|
+
dshVisible: true,
|
|
191
|
+
descKey: "todoListEnabledDesc",
|
|
192
|
+
offHintKey: "todoListOffHint",
|
|
193
|
+
},
|
|
101
194
|
];
|
|
102
195
|
const KNOWN_NAMES = new Set(AGENT_TOOL_CATALOG.map((t) => t.name));
|
|
103
196
|
/** 是否为本表登记的可开关工具(未知名一律 false,不抛错)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"description": "Just open your browser to get all your work done — browser cockpit for AI coding agents (chat, code, files, terminal, Git in one tab)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@deepseek-ai/dsh-sdk-jsonrpc-server": "^0.1.1-rc.2",
|
|
86
86
|
"@deepseek-ai/dsh-sdk-protocol": "^0.1.1-rc.2",
|
|
87
|
-
"@earendil-works/pi-coding-agent": "
|
|
87
|
+
"@earendil-works/pi-coding-agent": ">=0.85.1 <0.87.0",
|
|
88
88
|
"@xterm/addon-fit": "^0.11.0",
|
|
89
89
|
"@xterm/xterm": "^6.0.0",
|
|
90
90
|
"compression": "^1.8.2",
|
package/plugins/catalog.json
CHANGED
|
@@ -108,5 +108,15 @@
|
|
|
108
108
|
"descriptionEn": "Notes, todos and scheduled reminders in one place: a freely draggable always-on-top panel (position, size and settings all live in it, no separate view tab); reminders fire server-side (survive restarts, delivered on next open), with 5 AI tools and /note /todo /remind quick capture.",
|
|
109
109
|
"source": "xing-shuyin/pi-web-ui/plugins/notes",
|
|
110
110
|
"homepage": "https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/notes"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"id": "pi-web-multigit",
|
|
114
|
+
"name": "multi-git",
|
|
115
|
+
"icon": "🌿",
|
|
116
|
+
"iconSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"18\" r=\"3\"/><circle cx=\"6\" cy=\"6\" r=\"3\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><path d=\"M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9\"/><path d=\"M12 12v3\"/></svg>",
|
|
117
|
+
"description": "多仓库 Git 总览:工作区内每个仓库的变更、差异、历史、分支、贮藏、跨仓搜索与时间线,另有工作区分支聚合、Fetch/Pull 与内置终端(其余只读)。",
|
|
118
|
+
"descriptionEn": "Multi-repo Git overview: changes, diffs, history, branches, stashes, cross-repo search and a timeline for every repository in the workspace, plus a workspace-wide branch view, Fetch/Pull and an optional terminal.",
|
|
119
|
+
"source": "EinErste/pi-web-multigit",
|
|
120
|
+
"homepage": "https://github.com/EinErste/pi-web-multigit"
|
|
111
121
|
}
|
|
112
122
|
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* theme-name: 浅阑秋 */
|
|
2
|
+
/* theme-name-en: Ayu Light */
|
|
3
|
+
/* theme-group: classic */
|
|
4
|
+
:root {
|
|
5
|
+
color-scheme: light;
|
|
6
|
+
--bg: #fcfcfc;
|
|
7
|
+
--bg-elev: #f8f9fa;
|
|
8
|
+
--bg-elev2: #f0f1f3;
|
|
9
|
+
--border: #e0e1e3;
|
|
10
|
+
--border-soft: #ecedef;
|
|
11
|
+
--text: #5c6166;
|
|
12
|
+
--text-dim: #66727f;
|
|
13
|
+
--text-faint: #6a7582;
|
|
14
|
+
--accent: #f29718;
|
|
15
|
+
--accent-soft: rgba(242, 151, 24, 0.14);
|
|
16
|
+
--green: #86b300;
|
|
17
|
+
--green-soft: rgba(134, 179, 0, 0.12);
|
|
18
|
+
--red: #f07171;
|
|
19
|
+
--red-soft: rgba(240, 113, 113, 0.1);
|
|
20
|
+
--amber: #e59645;
|
|
21
|
+
--blue: #3b82f6;
|
|
22
|
+
--blue-soft: rgba(59, 130, 246, 0.12);
|
|
23
|
+
--term-bg: #fcfcfc;
|
|
24
|
+
--term-fg: #5c6166;
|
|
25
|
+
--term-cursor: #f29718;
|
|
26
|
+
--term-cursor-accent: #fcfcfc;
|
|
27
|
+
--term-selection: rgba(242, 151, 24, 0.24);
|
|
28
|
+
--term-black: #5c6166;
|
|
29
|
+
--term-red: #f07171;
|
|
30
|
+
--term-green: #86b300;
|
|
31
|
+
--term-yellow: #eba400;
|
|
32
|
+
--term-blue: #22a4e6;
|
|
33
|
+
--term-magenta: #a37acc;
|
|
34
|
+
--term-cyan: #4cbf99;
|
|
35
|
+
--term-white: #f8f9fa;
|
|
36
|
+
--term-bright-black: #828e9f;
|
|
37
|
+
--term-bright-red: #f07171;
|
|
38
|
+
--term-bright-green: #86b300;
|
|
39
|
+
--term-bright-yellow: #eba400;
|
|
40
|
+
--term-bright-blue: #22a4e6;
|
|
41
|
+
--term-bright-magenta: #a37acc;
|
|
42
|
+
--term-bright-cyan: #4cbf99;
|
|
43
|
+
--term-bright-white: #ffffff;
|
|
44
|
+
--mono: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
|
|
45
|
+
--mermaid-font-size: 12px;
|
|
46
|
+
--tooltip-bg: #f8f9fa;
|
|
47
|
+
--code-bg: #f0f1f3;
|
|
48
|
+
--code-text: #5c6166;
|
|
49
|
+
--code-border: var(--border-soft);
|
|
50
|
+
--code-pad: 8px 10px;
|
|
51
|
+
--card-body-pad: 4px 14px 12px;
|
|
52
|
+
--err-text: #e55a5a;
|
|
53
|
+
--red-text: #e55a5a;
|
|
54
|
+
--amber-text: #c77e24;
|
|
55
|
+
--info-blue: #22a4e6;
|
|
56
|
+
--link: #22a4e6;
|
|
57
|
+
--link-hover: #1a8fc4;
|
|
58
|
+
--link-soft: #22a4e6;
|
|
59
|
+
--md-strong: #5c6166;
|
|
60
|
+
--skill-blue: #22a4e6;
|
|
61
|
+
--plugin-purple: #b58cff;
|
|
62
|
+
--auth-green: #18a97c;
|
|
63
|
+
--scroll-thumb: #d8dade;
|
|
64
|
+
--scroll-thumb-hover: #b9bcc2;
|
|
65
|
+
--notice-err-bg: #401d23;
|
|
66
|
+
--notice-warn-bg: #38251f;
|
|
67
|
+
--notice-info-bg: #1b2544;
|
|
68
|
+
--notice-err-border: #ef4444;
|
|
69
|
+
--notice-warn-border: #f59e0b;
|
|
70
|
+
--notice-info-border: #3b82f6;
|
|
71
|
+
--send-blue: #f29718;
|
|
72
|
+
--send-blue-hover: #e08a0d;
|
|
73
|
+
--stop-red: #ef4444;
|
|
74
|
+
--stop-red-hover: #dc2626;
|
|
75
|
+
--brand-grad-a: #f29718;
|
|
76
|
+
--brand-grad-b: #f07171;
|
|
77
|
+
--on-amber: #1a1a1a;
|
|
78
|
+
--search-active-fg: #1a1d26;
|
|
79
|
+
--switch-knob: #ffffff;
|
|
80
|
+
--bg-elev3: rgba(255, 255, 255, 0.06);
|
|
81
|
+
--sunken-bg: rgba(0, 0, 0, 0.15);
|
|
82
|
+
--glow-015: rgba(255, 255, 255, 0.015);
|
|
83
|
+
--glow-025: rgba(255, 255, 255, 0.025);
|
|
84
|
+
--glow-03: rgba(255, 255, 255, 0.03);
|
|
85
|
+
--glow-04: rgba(255, 255, 255, 0.04);
|
|
86
|
+
--glow-05: rgba(255, 255, 255, 0.05);
|
|
87
|
+
--glow-12: rgba(255, 255, 255, 0.12);
|
|
88
|
+
--glow-18: rgba(255, 255, 255, 0.18);
|
|
89
|
+
--glow-22: rgba(255, 255, 255, 0.22);
|
|
90
|
+
--glow-38: rgba(255, 255, 255, 0.38);
|
|
91
|
+
--control-fg: #6b7d8f;
|
|
92
|
+
--control-bg: #f0f1f3;
|
|
93
|
+
--control-border: #e0e1e3;
|
|
94
|
+
--bg-image: none;
|
|
95
|
+
--bg-image-dim: 0.78;
|
|
96
|
+
--bg-image-blur: 0px;
|
|
97
|
+
--wallpaper-panel-alpha: 100%;
|
|
98
|
+
--topbar-bg: #f8f9fa;
|
|
99
|
+
--statusbar-bg: #fcfcfc;
|
|
100
|
+
--panel-bg: #f8f9fa;
|
|
101
|
+
--card-bg: #f8f9fa;
|
|
102
|
+
--chip-bg: #f0f1f3;
|
|
103
|
+
--msgs-bg: #fcfcfc;
|
|
104
|
+
--inputbox-bg: #f0f1f3;
|
|
105
|
+
--menu-bg: var(--bg-elev2);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* ---- syntax highlighting (Ayu Light) ---- */
|
|
109
|
+
.hljs {
|
|
110
|
+
color: #5c6166;
|
|
111
|
+
background: #f0f1f3;
|
|
112
|
+
}
|
|
113
|
+
.hljs-keyword,
|
|
114
|
+
.hljs-meta .hljs-keyword {
|
|
115
|
+
color: #fa8532;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
}
|
|
118
|
+
.hljs-title,
|
|
119
|
+
.hljs-title.function_ {
|
|
120
|
+
color: #eba400;
|
|
121
|
+
}
|
|
122
|
+
.hljs-number,
|
|
123
|
+
.hljs-literal {
|
|
124
|
+
color: #a37acc;
|
|
125
|
+
}
|
|
126
|
+
.hljs-string {
|
|
127
|
+
color: #86b300;
|
|
128
|
+
}
|
|
129
|
+
.hljs-built_in,
|
|
130
|
+
.hljs-type {
|
|
131
|
+
color: #55b4d4;
|
|
132
|
+
}
|
|
133
|
+
.hljs-comment {
|
|
134
|
+
color: #6a7582;
|
|
135
|
+
font-style: italic;
|
|
136
|
+
}
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
--red: #d20f39;
|
|
19
19
|
--red-soft: rgba(210, 15, 57, 0.1);
|
|
20
20
|
--amber: #df8e1d;
|
|
21
|
+
--blue: #3b82f6;
|
|
22
|
+
--blue-soft: rgba(59, 130, 246, 0.12);
|
|
21
23
|
--term-bg: #eff1f5;
|
|
22
24
|
--term-fg: #4c4f69;
|
|
23
25
|
--term-cursor: #dc8a78;
|
|
@@ -68,6 +70,8 @@
|
|
|
68
70
|
--notice-info-border: #3b82f6;
|
|
69
71
|
--send-blue: #1e66f5;
|
|
70
72
|
--send-blue-hover: #7287fd;
|
|
73
|
+
--stop-red: #ef4444;
|
|
74
|
+
--stop-red-hover: #dc2626;
|
|
71
75
|
--brand-grad-a: #1e66f5;
|
|
72
76
|
--brand-grad-b: #8839ef;
|
|
73
77
|
--on-amber: #1a1a1a;
|