pi-web-ui 0.96.1 → 0.97.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 +48 -2
- package/README.md +26 -0
- package/README.zh-CN.md +25 -0
- package/bin/pi-web-ui.mjs +11 -2
- package/dist/server/agent-service.js +1195 -120
- package/dist/server/client-state.js +89 -1
- package/dist/server/compacted-history.js +146 -0
- package/dist/server/delegate-mode.js +149 -0
- package/dist/server/delegate-task.js +10 -10
- package/dist/server/dsh/dsh-agent-service.js +35 -2
- package/dist/server/dsh/dsh-client.js +3 -1
- package/dist/server/eval-tool.js +3 -1
- package/dist/server/goal-review-gate.js +263 -0
- package/dist/server/goal-service.js +1225 -445
- package/dist/server/index.js +35 -1
- package/dist/server/lsp-tool.js +13 -10
- package/dist/server/markers/builtins/todo.js +0 -2
- package/dist/server/mcp-bridge.js +13 -0
- package/dist/server/patch-tool.js +1 -0
- package/dist/server/patch-turn-end-boundary.js +77 -0
- package/dist/server/permission-preset.js +44 -0
- package/dist/server/plan-mode.js +376 -0
- package/dist/server/plugin-facilities.js +36 -3
- package/dist/server/plugins.js +6 -3
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/serialize.js +11 -0
- package/dist/server/settings-service.js +18 -6
- package/dist/server/subagents.js +360 -408
- package/dist/server/text-sniff.js +15 -9
- package/dist/server/tool-manager.js +48 -4
- package/dist/server/update-check.js +11 -3
- package/dist/server/wait-subscription-scan.js +5 -1
- package/package.json +3 -3
- package/themes/zhupi-dark.css +5 -30
- package/themes/zhupi.css +5 -30
- package/web/dist/assets/{TerminalPanel-O3GyFIGG.js → TerminalPanel-CITBqQrb.js} +1 -1
- package/web/dist/assets/index-DwToli0J.css +1 -0
- package/web/dist/assets/index-oI7UKOqJ.js +368 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-B7Zp23Vc.css +0 -1
- package/web/dist/assets/index-ByfoaDwp.js +0 -366
|
@@ -187,21 +187,27 @@ export function looksLikeText(buf) {
|
|
|
187
187
|
}
|
|
188
188
|
return control / Math.max(text.length, 1) < 0.02;
|
|
189
189
|
}
|
|
190
|
-
/** Decode bytes: strict UTF-8 first
|
|
191
|
-
*
|
|
192
|
-
*
|
|
190
|
+
/** Decode bytes: strict UTF-8 first (retrying with up to 3 truncated tail
|
|
191
|
+
* bytes removed for excerpt/preview buffers cut mid-character), falling back
|
|
192
|
+
* to GBK (Windows legacy Chinese files), then latin1 as a last resort — so
|
|
193
|
+
* previews and inline attachments never show mojibake for GBK/GB2312 files
|
|
194
|
+
* nor for truncated UTF-8 files (issue #392). */
|
|
193
195
|
export function decodeText(buf) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
196
|
+
for (let cut = 0; cut < 4; cut++) {
|
|
197
|
+
const slice = cut === 0 ? buf : buf.subarray(0, Math.max(0, buf.length - cut));
|
|
198
198
|
try {
|
|
199
|
-
return new TextDecoder("
|
|
199
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(slice);
|
|
200
200
|
}
|
|
201
201
|
catch {
|
|
202
|
-
|
|
202
|
+
// Tail byte might be cut mid-character; try trimming up to 3 bytes.
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
+
try {
|
|
206
|
+
return new TextDecoder("gbk").decode(buf);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return buf.toString("latin1");
|
|
210
|
+
}
|
|
205
211
|
}
|
|
206
212
|
/** Sniff an image MIME type from magic bytes (extension is only a hint).
|
|
207
213
|
* Returns null when the bytes don't look like a known raster format —
|
|
@@ -25,8 +25,11 @@ export const TERMINAL_TOOL_NAMES = [
|
|
|
25
25
|
"terminal_read",
|
|
26
26
|
"terminal_wait",
|
|
27
27
|
];
|
|
28
|
-
/** 第一方子代理工具(定义见 subagents.ts
|
|
29
|
-
export const
|
|
28
|
+
/** 第一方子代理工具(定义见 subagents.ts,统一为单 action 工具)。 */
|
|
29
|
+
export const SUBAGENT_TOOL_NAME = "subagent";
|
|
30
|
+
export const SUBAGENT_TOOL_NAMES = [SUBAGENT_TOOL_NAME];
|
|
31
|
+
/** 旧版 8 个独立子代理工具名(持久化配置迁移用)。 */
|
|
32
|
+
export const LEGACY_SUBAGENT_TOOL_NAMES = [
|
|
30
33
|
"subagent_spawn",
|
|
31
34
|
"subagent_get_result",
|
|
32
35
|
"subagent_steer",
|
|
@@ -251,7 +254,7 @@ export const AGENT_TOOL_CATALOG = [
|
|
|
251
254
|
},
|
|
252
255
|
];
|
|
253
256
|
/** SDK 核心内置工具名(可被门控显式禁用或被预设白名单过滤)。 */
|
|
254
|
-
export const CORE_BUILTIN_TOOL_NAMES = ["bash", "read", "edit", "write"];
|
|
257
|
+
export const CORE_BUILTIN_TOOL_NAMES = ["bash", "read", "edit", "write", "powershell", "ls", "grep", "find"];
|
|
255
258
|
export function isCoreBuiltinTool(name) {
|
|
256
259
|
return CORE_BUILTIN_TOOL_NAMES.includes(name);
|
|
257
260
|
}
|
|
@@ -268,7 +271,11 @@ export function normalizeDisabledAgentTools(v) {
|
|
|
268
271
|
const out = [];
|
|
269
272
|
for (const x of v) {
|
|
270
273
|
// 旧名迁移:markers_list → todo_list(改名前已关闭的用户保持关闭)。
|
|
271
|
-
|
|
274
|
+
let name = x === LEGACY_MARKERS_LIST_TOOL_NAME ? MARKERS_LIST_TOOL_NAME : x;
|
|
275
|
+
// 旧版子代理工具迁移:任意旧 subagent_* 关闭均迁移为关闭 subagent 工具。
|
|
276
|
+
if (typeof name === "string" && LEGACY_SUBAGENT_TOOL_NAMES.includes(name)) {
|
|
277
|
+
name = SUBAGENT_TOOL_NAME;
|
|
278
|
+
}
|
|
272
279
|
if (typeof name === "string" && (KNOWN_NAMES.has(name) || isCoreBuiltinTool(name)) && !out.includes(name)) {
|
|
273
280
|
out.push(name);
|
|
274
281
|
}
|
|
@@ -363,7 +370,9 @@ export const PI_AGENT_PRESETS = [
|
|
|
363
370
|
trust: "system",
|
|
364
371
|
isDefault: true,
|
|
365
372
|
name: "全功能",
|
|
373
|
+
nameEn: "Full access",
|
|
366
374
|
description: "提供全部可用工具与扩展能力(默认)",
|
|
375
|
+
descriptionEn: "All available tools and extension capabilities (default)",
|
|
367
376
|
order: 0,
|
|
368
377
|
},
|
|
369
378
|
{
|
|
@@ -371,7 +380,9 @@ export const PI_AGENT_PRESETS = [
|
|
|
371
380
|
trust: "system",
|
|
372
381
|
isDefault: false,
|
|
373
382
|
name: "极简模式",
|
|
383
|
+
nameEn: "Minimal",
|
|
374
384
|
description: "仅保留 bash 与 read;插件工具、技能名录与终端引导同步隐藏",
|
|
385
|
+
descriptionEn: "Keeps only bash and read; plugin tools, skill catalog and terminal guidance are hidden too",
|
|
375
386
|
order: 1,
|
|
376
387
|
},
|
|
377
388
|
{
|
|
@@ -379,7 +390,9 @@ export const PI_AGENT_PRESETS = [
|
|
|
379
390
|
trust: "system",
|
|
380
391
|
isDefault: false,
|
|
381
392
|
name: "代码开发",
|
|
393
|
+
nameEn: "Code development",
|
|
382
394
|
description: "专注于代码读写与执行(bash, read, edit, write, edit_soft);插件工具与技能名录同步隐藏",
|
|
395
|
+
descriptionEn: "Focused on reading, writing and running code (bash, read, edit, write, edit_soft); plugin tools and skill catalog are hidden too",
|
|
383
396
|
order: 2,
|
|
384
397
|
},
|
|
385
398
|
{
|
|
@@ -387,7 +400,9 @@ export const PI_AGENT_PRESETS = [
|
|
|
387
400
|
trust: "system",
|
|
388
401
|
isDefault: false,
|
|
389
402
|
name: "只读分析",
|
|
403
|
+
nameEn: "Read-only analysis",
|
|
390
404
|
description: "仅保留只读工具,禁止写操作;插件工具同步隐藏(读写未知,保守处理)",
|
|
405
|
+
descriptionEn: "Keeps only read-only tools and forbids writes; plugin tools are hidden too (read/write unknown, handled conservatively)",
|
|
391
406
|
order: 3,
|
|
392
407
|
},
|
|
393
408
|
{
|
|
@@ -395,7 +410,9 @@ export const PI_AGENT_PRESETS = [
|
|
|
395
410
|
trust: "system",
|
|
396
411
|
isDefault: false,
|
|
397
412
|
name: "纯对话",
|
|
413
|
+
nameEn: "Chat only",
|
|
398
414
|
description: "无工具问答模式,模型不调用任何工具;插件工具与技能名录同步隐藏",
|
|
415
|
+
descriptionEn: "No-tools Q&A: the model never calls a tool; plugin tools and skill catalog are hidden too",
|
|
399
416
|
order: 4,
|
|
400
417
|
},
|
|
401
418
|
];
|
|
@@ -404,19 +421,45 @@ export const PI_PERMISSION_OPTIONS = [
|
|
|
404
421
|
{
|
|
405
422
|
value: "read-only",
|
|
406
423
|
name: "只读模式",
|
|
424
|
+
nameEn: "Read Only",
|
|
407
425
|
description: "禁止所有文件修改(write/edit/edit_soft)及任何非只读操作",
|
|
426
|
+
descriptionEn: "Forbids all file modification (write/edit/edit_soft) and any non-read-only operation",
|
|
408
427
|
},
|
|
409
428
|
{
|
|
410
429
|
value: "workspace-write-never",
|
|
411
430
|
name: "工作区内修改",
|
|
431
|
+
nameEn: "Workspace Write",
|
|
412
432
|
description: "仅允许在当前工作区目录下修改文件,工作区外写操作一律拒绝",
|
|
433
|
+
descriptionEn: "Only files under the current workspace may be modified; writes outside it are always denied",
|
|
413
434
|
},
|
|
414
435
|
{
|
|
415
436
|
value: "danger-full-access",
|
|
416
437
|
name: "完全权限",
|
|
438
|
+
nameEn: "Full access",
|
|
417
439
|
description: "允许修改任意目录文件及执行全量操作(需要二次确认)",
|
|
440
|
+
descriptionEn: "Allows modifying files anywhere and running any operation (needs a second confirmation)",
|
|
418
441
|
},
|
|
419
442
|
];
|
|
443
|
+
/**
|
|
444
|
+
* 预设/权限文案随界面语言落定:中文界面用服务端默认文案,其它语言用
|
|
445
|
+
* nameEn ?? name(与审批规则的 labelEn/reasonEn、插件的 label/labelEn 同一约定;
|
|
446
|
+
* 其它语言包同理回落英文)。服务端 notice 的 textEn 也走这里,否则英文界面会看到
|
|
447
|
+
* `Switched to preset "全功能"`。
|
|
448
|
+
*
|
|
449
|
+
* lang 是 ServerLang(resolveServerLang 的产物):只有 "zh" 算中文。
|
|
450
|
+
* 本文件保持零依赖,故直接比字符串而不 import server/i18n.js。
|
|
451
|
+
*/
|
|
452
|
+
export function localizedName(item, lang, fallback = "") {
|
|
453
|
+
if (!item)
|
|
454
|
+
return fallback;
|
|
455
|
+
return lang === "zh" ? (item.name ?? item.nameEn ?? fallback) : (item.nameEn ?? item.name ?? fallback);
|
|
456
|
+
}
|
|
457
|
+
/** localizedName 的描述版本(description 两边都可缺省 → undefined)。 */
|
|
458
|
+
export function localizedDescription(item, lang) {
|
|
459
|
+
if (!item)
|
|
460
|
+
return undefined;
|
|
461
|
+
return lang === "zh" ? (item.description ?? item.descriptionEn) : (item.descriptionEn ?? item.description);
|
|
462
|
+
}
|
|
420
463
|
/** 按预设过滤活跃工具名。 */
|
|
421
464
|
export function filterToolsByPreset(tools, preset) {
|
|
422
465
|
const all = Array.from(tools);
|
|
@@ -437,6 +480,7 @@ export function filterToolsByPreset(tools, preset) {
|
|
|
437
480
|
"edit",
|
|
438
481
|
"edit_soft",
|
|
439
482
|
"bash",
|
|
483
|
+
"powershell",
|
|
440
484
|
"terminal_create",
|
|
441
485
|
"terminal_input",
|
|
442
486
|
"terminal_close",
|
|
@@ -466,13 +466,21 @@ function readVendoredPiCore(agentDir) {
|
|
|
466
466
|
return null;
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* 宿主 SDK 及其 peer/内部包(@earendil-works/* / @mariozechner/*):
|
|
471
|
+
* 属于宿主引擎随核心一同分发或由别名提供的 SDK 内部构件,非用户安装的独立扩展组件;
|
|
472
|
+
* 既不应在组件列表重复展示,也不能被 `pi update npm:<name>` 更新(会报 No matching package found,issue #393)。
|
|
473
|
+
*/
|
|
474
|
+
export function isHostProvidedPackage(name) {
|
|
475
|
+
return name === PI_CORE_PACKAGE || name.startsWith("@earendil-works/") || name.startsWith("@mariozechner/");
|
|
476
|
+
}
|
|
469
477
|
/**
|
|
470
478
|
* Build the full local target list: webui + the pi core + installed packages
|
|
471
479
|
* + git-source extensions (issue #178) from global and project settings.
|
|
472
480
|
* The pi core version comes from the CLI probe (injectable for tests), falling
|
|
473
481
|
* back to the vendored copy under <agentDir>/npm/node_modules. Packages
|
|
474
|
-
* listing the core directly are filtered out so the
|
|
475
|
-
*
|
|
482
|
+
* listing the core directly or host-provided SDK peers are filtered out so the
|
|
483
|
+
* pi-core row wins and host peers are not listed as updatable (issue #393).
|
|
476
484
|
*/
|
|
477
485
|
export function collectTargets(agentDir, webuiVersion, probePiCore = defaultProbePiCore, opts) {
|
|
478
486
|
const targets = [{ name: "pi-web-ui", version: webuiVersion, kind: "webui" }];
|
|
@@ -484,7 +492,7 @@ export function collectTargets(agentDir, webuiVersion, probePiCore = defaultProb
|
|
|
484
492
|
kind: "pi-core",
|
|
485
493
|
});
|
|
486
494
|
}
|
|
487
|
-
targets.push(...listInstalledPackages(agentDir).filter((pkg) => pkg.name
|
|
495
|
+
targets.push(...listInstalledPackages(agentDir).filter((pkg) => !isHostProvidedPackage(pkg.name)));
|
|
488
496
|
targets.push(...listGitExtensions(agentDir, opts?.projectCwd));
|
|
489
497
|
return targets;
|
|
490
498
|
}
|
|
@@ -239,12 +239,16 @@ export function hasPendingWaitSubscription(options) {
|
|
|
239
239
|
/**
|
|
240
240
|
* 纯函数版置换决策:true = 保留(不得 dispose),false = 调用方可释放。
|
|
241
241
|
* Pure decision core of displaceActive(): true = retain, false = may dispose.
|
|
242
|
-
* 顺序与 displaceActive 保持一致:review/wizard → streaming → terminals →
|
|
242
|
+
* 顺序与 displaceActive 保持一致:pin → review/wizard → streaming → terminals →
|
|
243
243
|
* active subagent run → pending wake → listed+continued(「打开后继续过」的会话也保留)。
|
|
244
244
|
*/
|
|
245
245
|
export function shouldRetainActive(input) {
|
|
246
|
+
if (input.pinned)
|
|
247
|
+
return true;
|
|
246
248
|
if (input.reviewing || input.wizardRunning)
|
|
247
249
|
return true;
|
|
250
|
+
if (input.promptInFlight)
|
|
251
|
+
return true;
|
|
248
252
|
if (input.streaming || input.compacting)
|
|
249
253
|
return true;
|
|
250
254
|
if (input.openTerminals > 0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.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": {
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"lint:fix": "oxlint --fix server/ web/src/ extensions/ scripts/ desktop/"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-server": "^0.1.
|
|
87
|
-
"@deepseek-ai/dsh-sdk-protocol": "^0.1.
|
|
86
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-server": "^0.1.5-rc.3",
|
|
87
|
+
"@deepseek-ai/dsh-sdk-protocol": "^0.1.5-rc.3",
|
|
88
88
|
"@earendil-works/pi-coding-agent": ">=0.85.1",
|
|
89
89
|
"@xterm/addon-fit": "^0.11.0",
|
|
90
90
|
"@xterm/xterm": "^6.0.0",
|
package/themes/zhupi-dark.css
CHANGED
|
@@ -198,35 +198,9 @@ body,
|
|
|
198
198
|
padding: 2px 0 10px;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
/*
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
/* 兜底路径(用户消息 / 纯文本回复,无 head 可宿主):底部操作行改为悬停
|
|
205
|
-
浮牌,钉在消息右上角的空纸面上 */
|
|
206
|
-
.msg-actions {
|
|
207
|
-
position: absolute;
|
|
208
|
-
top: 0;
|
|
209
|
-
right: 0;
|
|
210
|
-
margin: 0;
|
|
211
|
-
z-index: 2;
|
|
212
|
-
padding: 2px;
|
|
213
|
-
background: var(--menu-bg) !important;
|
|
214
|
-
border: 1px solid var(--border) !important;
|
|
215
|
-
border-radius: 4px !important;
|
|
216
|
-
box-shadow: 0 4px 14px -4px rgba(0, 0, 0, 0.5) !important;
|
|
217
|
-
opacity: 0;
|
|
218
|
-
pointer-events: none;
|
|
219
|
-
transition:
|
|
220
|
-
opacity 0.12s ease,
|
|
221
|
-
pointer-events 0.12s ease;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
.msg:hover .msg-actions,
|
|
225
|
-
.msg-actions.has-open-dropdown,
|
|
226
|
-
.msg-actions:focus-within {
|
|
227
|
-
opacity: 1;
|
|
228
|
-
pointer-events: auto;
|
|
229
|
-
}
|
|
201
|
+
/* 操作按钮:卡头只有自己的复制键(.chead-copy),消息级按钮(复制三件套 /
|
|
202
|
+
分叉 / 回滚 / 朗读)统一在底部 .msg-actions 行,流内排在消息气泡/正文下边,
|
|
203
|
+
桌面端与手机端保持一致。 */
|
|
230
204
|
|
|
231
205
|
/* 角色行:书目式小标 */
|
|
232
206
|
.msg-meta {
|
|
@@ -245,7 +219,8 @@ body,
|
|
|
245
219
|
margin-left: -6px;
|
|
246
220
|
}
|
|
247
221
|
|
|
248
|
-
/*
|
|
222
|
+
/* 正文块上的单一复制键去重:卡头现在只有自己的复制键,消息级复制
|
|
223
|
+
(纯文本 / Markdown / 长图三件套)统一在底部 .msg-actions 行里。 */
|
|
249
224
|
.msg-text-copy {
|
|
250
225
|
display: none !important;
|
|
251
226
|
}
|
package/themes/zhupi.css
CHANGED
|
@@ -205,35 +205,9 @@ body,
|
|
|
205
205
|
padding: 2px 0 10px;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
/*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
/* 兜底路径(用户消息 / 纯文本回复,无 head 可宿主):底部操作行改为悬停
|
|
212
|
-
浮牌,钉在消息右上角的空纸面上 */
|
|
213
|
-
.msg-actions {
|
|
214
|
-
position: absolute;
|
|
215
|
-
top: 0;
|
|
216
|
-
right: 0;
|
|
217
|
-
margin: 0;
|
|
218
|
-
z-index: 2;
|
|
219
|
-
padding: 2px;
|
|
220
|
-
background: var(--menu-bg) !important;
|
|
221
|
-
border: 1px solid var(--border) !important;
|
|
222
|
-
border-radius: 4px !important;
|
|
223
|
-
box-shadow: 0 4px 14px -4px rgba(38, 34, 24, 0.35) !important;
|
|
224
|
-
opacity: 0;
|
|
225
|
-
pointer-events: none;
|
|
226
|
-
transition:
|
|
227
|
-
opacity 0.12s ease,
|
|
228
|
-
pointer-events 0.12s ease;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.msg:hover .msg-actions,
|
|
232
|
-
.msg-actions.has-open-dropdown,
|
|
233
|
-
.msg-actions:focus-within {
|
|
234
|
-
opacity: 1;
|
|
235
|
-
pointer-events: auto;
|
|
236
|
-
}
|
|
208
|
+
/* 操作按钮:卡头只有自己的复制键(.chead-copy),消息级按钮(复制三件套 /
|
|
209
|
+
分叉 / 回滚 / 朗读)统一在底部 .msg-actions 行,流内排在消息气泡/正文下边,
|
|
210
|
+
桌面端与手机端保持一致。 */
|
|
237
211
|
|
|
238
212
|
/* 角色行:书目式小标 */
|
|
239
213
|
.msg-meta {
|
|
@@ -252,7 +226,8 @@ body,
|
|
|
252
226
|
margin-left: -6px;
|
|
253
227
|
}
|
|
254
228
|
|
|
255
|
-
/*
|
|
229
|
+
/* 正文块上的单一复制键去重:卡头现在只有自己的复制键,消息级复制
|
|
230
|
+
(纯文本 / Markdown / 长图三件套)统一在底部 .msg-actions 行里。 */
|
|
256
231
|
.msg-text-copy {
|
|
257
232
|
display: none !important;
|
|
258
233
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,o as f,p,r as m,s as h,t as g,u as _}from"./index-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,o as f,p,r as m,s as h,t as g,u as _}from"./index-oI7UKOqJ.js";import{n as v,t as y}from"./xterm-Cf5xxe4g.js";var b=e(t(),1);function x(e,t){return Math.abs(t)>10&&Math.abs(t)>Math.abs(e)*1.2}function S(e,t){return e-t}var C=n();function w(e){let t=null;return{clean:e.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(e,n)=>(t=Number(n),`\r
|
|
2
2
|
`)).replace(/\r?\n?\x1b\[90m\[(?:进程已退出,退出码 |Process exited with code )-?\d+\]\x1b\[0m\r?\n?/g,`\r
|
|
3
3
|
`),exitCode:t}}function T({conversationId:e,terminalId:t,command:n,cwd:i,title:a,agentBash:o,active:s,running:c,exitCode:u,register:d}){let p=(0,b.useRef)(null),h=(0,b.useRef)(null),{locale:g}=f(),_=(0,b.useRef)(g);_.current=g;let T=n?JSON.stringify(n):``,E=(0,b.useRef)(c===!1);(0,b.useEffect)(()=>{let c=p.current;if(!c)return;let u=new v({theme:l(),fontFamily:`"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace`,fontSize:13,cursorBlink:!0,scrollback:8e3}),f=new y;u.loadAddon(f),u.open(c),h.current={term:u,fit:f},s&&u.focus();let g=()=>{u.options.theme=l()};window.addEventListener(m,g),u.attachCustomKeyEventHandler(e=>{if(e.type!==`keydown`)return!0;let t=e.key?.toLowerCase();if((e.ctrlKey||e.metaKey)&&t===`v`)return!1;if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&t===`c`&&u.hasSelection()){let e=u.textarea;return e&&(e.value=u.getSelection(),e.select()),!1}return!0});let b=d(e,t,{write:e=>u.write(w(e).clean),dispose:()=>u.dispose()}),C=()=>{try{f.fit(),r({type:`terminal_resize`,terminalId:t,conversationId:e,cols:u.cols,rows:u.rows})}catch{}},T=requestAnimationFrame(()=>{try{f.fit()}catch{}E.current||r(n?{type:`run_command`,terminalId:t,conversationId:e,command:n,cols:u.cols,rows:u.rows}:{type:`terminal_create`,terminalId:t,title:a,locale:_.current,agentBash:o,conversationId:e,cwd:i,cols:u.cols,rows:u.rows})}),D=u.onData(n=>{r({type:`terminal_input`,terminalId:t,conversationId:e,data:n})}),O=u.element,k=null,A=0,j=0,M=0,N=!1,P=e=>{if(e.touches.length!==1){k=null,N=!1;return}let t=e.touches[0];k=t.identifier,A=t.clientX,j=t.clientY,M=t.clientY,N=!1},F=e=>{if(k===null||e.touches.length!==1)return;let t=e.touches[0];if(t.identifier!==k)return;if(!N){if(!x(t.clientX-A,t.clientY-j))return;N=!0}e.preventDefault();let n=S(M,t.clientY);M=t.clientY,n!==0&&typeof WheelEvent<`u`&&(c.querySelector(`.xterm-viewport`)??O)?.dispatchEvent(new WheelEvent(`wheel`,{bubbles:!0,cancelable:!0,deltaY:n,deltaMode:0,clientX:t.clientX,clientY:t.clientY}))},I=e=>{if(k!==null){if(e.touches.length>0){k=null,N=!1;return}k=null,N=!1}};O?.addEventListener(`touchstart`,P,{passive:!0}),O?.addEventListener(`touchmove`,F,{passive:!1}),O?.addEventListener(`touchend`,I),O?.addEventListener(`touchcancel`,I);let L=null;return typeof ResizeObserver<`u`&&(L=new ResizeObserver(()=>{c.offsetWidth>0&&c.offsetHeight>0&&C()}),L.observe(c)),()=>{cancelAnimationFrame(T),D.dispose(),O?.removeEventListener(`touchstart`,P),O?.removeEventListener(`touchmove`,F),O?.removeEventListener(`touchend`,I),O?.removeEventListener(`touchcancel`,I),window.removeEventListener(m,g),L?.disconnect(),b(),u.dispose(),h.current=null}},[e,t,T,d]),(0,b.useEffect)(()=>{if(!s)return;let n=requestAnimationFrame(()=>{let n=h.current;if(n){try{n.fit.fit(),r({type:`terminal_resize`,terminalId:t,conversationId:e,cols:n.term.cols,rows:n.term.rows})}catch{}n.term.focus()}});return()=>cancelAnimationFrame(n)},[s]);let{t:D}=f(),O=(0,b.useRef)(void 0);return(0,b.useEffect)(()=>{if(c===O.current||(O.current=c,c!==!1))return;let e=h.current;e&&e.term.write(`\r\n\x1b[90m${D(`exitBanner`,{code:u??``})}\x1b[0m\r\n`)},[c,t,u]),(0,C.jsx)(`div`,{ref:p,className:`term-xterm ${s?``:`hidden`}`})}var E={name:``,command:``,cwd:"${pwd}"};function D({chat:e,terminal:t,uiTerminalToolbar:n,onUiAction:l}){let f=h(),[m,v]=(0,b.useState)(null),[y,x]=(0,b.useState)(!1),[S,w]=(0,b.useState)(!1),[D,O]=(0,b.useState)(null),[k,A]=(0,b.useState)(E),[j,M]=(0,b.useState)(null),N=(0,b.useRef)(null),[P,F]=(0,b.useState)(!0),[I,L]=(0,b.useState)(null),[R,z]=(0,b.useState)(``);(0,b.useEffect)(()=>{e.terminals.length===0?v(null):e.terminals.some(e=>e.id===m)||v(e.terminals[e.terminals.length-1].id)},[e.terminals,m]),(0,b.useEffect)(()=>{e.terminalActiveId&&(v(e.terminalActiveId),x(!1))},[e.terminalActiveId]),(0,b.useEffect)(()=>()=>{N.current&&clearTimeout(N.current)},[]);let B=e.terminals.filter(e=>!e.agentBash),V=e.terminals.filter(e=>e.agentBash),H=n=>{if(!e.ready)return;let r=i(),a=e.activeConversationId||e.state?.conversationId||``;t.create({...n,id:r,conversationId:a,cols:n.cols??80,rows:n.rows??24,running:!0,exitCode:null}),v(r),x(!1)},U=()=>H({title:f(`terminalTitle`,{n:B.length+1}),cwd:e.state?.cwd??``}),W=n=>{let i=n.name||n.command,a=e.terminals.find(e=>e.title===i);if(a){t.restart(a.id),v(a.id),r({type:`run_command`,terminalId:a.id,conversationId:a.conversationId,command:n,cols:80,rows:24});return}H({title:i,cwd:e.state?.cwd??``,command:n})},G=n=>{let i=e.terminals.find(e=>e.id===n);if(i&&r({type:`terminal_kill`,terminalId:n,conversationId:i.conversationId}),t.close(n),m===n){let t=e.terminals.filter(e=>e.id!==n);v(t.length>0?t[t.length-1].id:null)}},K=e=>(0,C.jsxs)(`div`,{className:`term-tab ${e.id===m?`active`:``}`,children:[(0,C.jsxs)(`button`,{type:`button`,className:`term-tab-main`,title:`${e.cwd}${e.command?`\n> ${e.command.command}`:``}`,onClick:()=>{I||(v(e.id),x(!1))},children:[(0,C.jsx)(`span`,{className:`term-tab-dot ${e.running?`run`:`exit`}`}),(0,C.jsxs)(`span`,{className:`term-tab-title`,children:[I===e.id?(0,C.jsx)(`input`,{autoFocus:!0,className:`term-tab-rename-input`,value:R,placeholder:e.title,onClick:e=>e.stopPropagation(),onChange:e=>z(e.target.value),onKeyDown:t=>{if(t.stopPropagation(),t.key===`Enter`&&!t.nativeEvent.isComposing){let t=R.trim();t&&r({type:`rename_terminal`,terminalId:e.id,conversationId:e.conversationId,title:t}),L(null)}else t.key===`Escape`&&L(null)},onBlur:()=>L(null)}):e.title,!e.running&&(0,C.jsx)(`span`,{className:`term-tab-exit`,children:f(`exited`,{code:e.exitCode===null?``:` ${e.exitCode}`})})]})]}),(0,C.jsx)(`button`,{type:`button`,className:`term-tab-close term-tab-rename`,title:f(`renameTerminal`),onClick:t=>{t.stopPropagation(),z(e.title),L(e.id)},children:(0,C.jsx)(u,{})}),(0,C.jsx)(`button`,{type:`button`,className:`term-tab-close`,title:f(`closeTerminal`),onClick:()=>G(e.id),children:(0,C.jsx)(s,{})})]},e.id),q=()=>{w(!0),O(null),A(E)},J=t=>{let n=e.commands[t];n&&(w(!1),O(t),A({name:n.name,command:n.command,cwd:n.cwd??``}))},Y=()=>{w(!1),O(null)},X=()=>{let t=k.name.trim(),n=k.command.trim();if(!t||!n)return;let i={name:t,command:n,cwd:k.cwd.trim()||void 0},a=S?[...e.commands,i]:D===null?e.commands:e.commands.map((e,t)=>t===D?i:e);r({type:`save_commands`,commands:a}),Y()},Z=t=>{if(j===t){let n=e.commands.filter((e,n)=>n!==t);r({type:`save_commands`,commands:n}),M(null),N.current&&clearTimeout(N.current)}else M(t),N.current&&clearTimeout(N.current),N.current=setTimeout(()=>M(null),2500)},ee=S||D!==null,te=n===void 0?[`host:term-cmd-refresh`,`host:term-cmd-new`,`host:term-tab-new`].map(e=>({id:e,source:`host`})):n.filter(e=>!e.hidden),Q=(e,t)=>{let n=new Set(e);return te.filter(e=>n.has(e.id)||t&&e.source!==`host`)},$={"host:term-cmd-refresh":(0,C.jsx)(`button`,{type:`button`,className:`panel-refresh`,title:f(`rerun`),onClick:()=>r({type:`list_commands`}),children:(0,C.jsx)(p,{})}),"host:term-cmd-new":(0,C.jsx)(`button`,{type:`button`,className:`panel-new`,title:f(`newCommand`),onClick:q,children:(0,C.jsx)(o,{})}),"host:term-tab-new":(0,C.jsx)(`button`,{type:`button`,className:`panel-new`,title:f(`newTerminal`),onClick:U,children:(0,C.jsx)(o,{})})};return(0,C.jsxs)(`div`,{className:`terminal-view`,children:[(0,C.jsxs)(`aside`,{className:`term-side term-commands ${y?`open`:``}`,children:[(0,C.jsxs)(`div`,{className:`panel-header`,children:[(0,C.jsx)(`span`,{className:`panel-title`,children:f(`commands`)}),(0,C.jsx)(`div`,{className:`panel-header-actions`,children:g(Q([`host:term-cmd-refresh`,`host:term-cmd-new`],!1),$,l)})]}),(0,C.jsx)(`div`,{className:`panel-body`,children:ee?(0,C.jsxs)(`div`,{className:`cmd-form`,children:[(0,C.jsx)(`label`,{htmlFor:`cmd-name`,children:f(`name`)}),(0,C.jsx)(`input`,{id:`cmd-name`,className:`cmd-input`,value:k.name,placeholder:f(`exampleName`),autoFocus:!0,onChange:e=>A({...k,name:e.target.value})}),(0,C.jsx)(`label`,{htmlFor:`cmd-command`,children:f(`command`)}),(0,C.jsx)(`input`,{id:`cmd-command`,className:`cmd-input`,value:k.command,placeholder:f(`exampleCommand`),onChange:e=>A({...k,command:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&!e.nativeEvent.isComposing&&X()}}),(0,C.jsxs)(`label`,{htmlFor:`cmd-cwd`,children:[f(`directory`),` `,(0,C.jsx)(`span`,{className:`cmd-hint`,children:f(`cwdHint`)})]}),(0,C.jsx)(`input`,{id:`cmd-cwd`,className:`cmd-input`,value:k.cwd,placeholder:"${pwd}",onChange:e=>A({...k,cwd:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&!e.nativeEvent.isComposing&&X()}}),(0,C.jsxs)(`div`,{className:`cmd-form-actions`,children:[(0,C.jsx)(`button`,{type:`button`,className:`btn`,onClick:Y,children:f(`cancel`)}),(0,C.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:!k.name.trim()||!k.command.trim(),onClick:X,children:f(`save`)})]})]}):(0,C.jsxs)(C.Fragment,{children:[e.commands.length===0&&(0,C.jsx)(`div`,{className:`panel-empty`,children:f(`noCommands`)}),e.commands.map((e,t)=>(0,C.jsxs)(`div`,{className:`cmd-item`,children:[(0,C.jsx)(`button`,{type:`button`,className:`cmd-run`,title:f(`clickToRun`),onClick:()=>W(e),children:(0,C.jsx)(a,{})}),(0,C.jsxs)(`button`,{type:`button`,className:`cmd-main`,title:f(`clickToRun`),onClick:()=>W(e),children:[(0,C.jsx)(`span`,{className:`cmd-name`,children:e.name}),(0,C.jsx)(`span`,{className:`cmd-command`,children:e.command}),e.cwd&&(0,C.jsx)(`span`,{className:`cmd-cwd`,children:e.cwd})]}),(0,C.jsx)(`button`,{type:`button`,className:`cmd-act`,title:f(`edit`),onClick:()=>J(t),children:(0,C.jsx)(u,{})}),(0,C.jsx)(`button`,{type:`button`,className:`cmd-act del ${j===t?`confirm`:``}`,title:f(`delete`),onClick:()=>Z(t),children:j===t?f(`confirmQ`):(0,C.jsx)(c,{})})]},t))]})}),(0,C.jsxs)(`div`,{className:`term-tabs-block`,children:[(0,C.jsxs)(`div`,{className:`panel-header`,children:[(0,C.jsx)(`span`,{className:`panel-title`,children:f(`terminal`)}),g(Q([`host:term-tab-new`],!0),$,l)]}),(0,C.jsxs)(`div`,{className:`panel-body`,children:[e.terminals.length===0&&(0,C.jsx)(`div`,{className:`panel-empty`,children:f(`noTerminal`)}),B.map(K),V.length>0&&(0,C.jsxs)(`div`,{className:`term-folder`,children:[(0,C.jsxs)(`button`,{type:`button`,className:`term-folder-header ${P?`open`:``}`,title:f(`aiBashGroup`),onClick:()=>F(e=>!e),children:[(0,C.jsx)(`span`,{className:`term-folder-caret`,children:P?`▾`:`▸`}),(0,C.jsx)(`span`,{className:`term-folder-title`,children:f(`aiBashGroup`)}),(0,C.jsx)(`span`,{className:`term-folder-count`,children:V.length})]}),P&&(0,C.jsx)(`div`,{className:`term-folder-body`,children:V.map(K)})]})]})]})]}),(0,C.jsxs)(`div`,{className:`term-main`,children:[y&&(0,C.jsx)(`div`,{className:`drawer-backdrop`,onClick:()=>x(!1)}),(0,C.jsx)(`button`,{type:`button`,className:`term-side-toggle`,title:f(`commands`),onClick:()=>x(e=>!e),children:(0,C.jsx)(_,{})}),e.terminals.length===0?(0,C.jsxs)(`div`,{className:`term-empty`,children:[(0,C.jsx)(d,{className:`term-empty-icon`}),(0,C.jsx)(`div`,{className:`term-empty-title`,children:f(`builtinTerminal`)}),(0,C.jsx)(`div`,{className:`term-empty-sub`,children:f(`termEmptySub`)})]}):e.terminals.map(e=>(0,C.jsx)(T,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,title:e.title,agentBash:e.agentBash,active:e.id===m,running:e.running,exitCode:e.exitCode,register:t.register},`${e.conversationId}:${e.id}`))]})]})}export{D as TerminalPanel};
|