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.
Files changed (41) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/README.md +26 -0
  3. package/README.zh-CN.md +25 -0
  4. package/bin/pi-web-ui.mjs +11 -2
  5. package/dist/server/agent-service.js +1195 -120
  6. package/dist/server/client-state.js +89 -1
  7. package/dist/server/compacted-history.js +146 -0
  8. package/dist/server/delegate-mode.js +149 -0
  9. package/dist/server/delegate-task.js +10 -10
  10. package/dist/server/dsh/dsh-agent-service.js +35 -2
  11. package/dist/server/dsh/dsh-client.js +3 -1
  12. package/dist/server/eval-tool.js +3 -1
  13. package/dist/server/goal-review-gate.js +263 -0
  14. package/dist/server/goal-service.js +1225 -445
  15. package/dist/server/index.js +35 -1
  16. package/dist/server/lsp-tool.js +13 -10
  17. package/dist/server/markers/builtins/todo.js +0 -2
  18. package/dist/server/mcp-bridge.js +13 -0
  19. package/dist/server/patch-tool.js +1 -0
  20. package/dist/server/patch-turn-end-boundary.js +77 -0
  21. package/dist/server/permission-preset.js +44 -0
  22. package/dist/server/plan-mode.js +376 -0
  23. package/dist/server/plugin-facilities.js +36 -3
  24. package/dist/server/plugins.js +6 -3
  25. package/dist/server/protocol-version.js +1 -1
  26. package/dist/server/serialize.js +11 -0
  27. package/dist/server/settings-service.js +18 -6
  28. package/dist/server/subagents.js +360 -408
  29. package/dist/server/text-sniff.js +15 -9
  30. package/dist/server/tool-manager.js +48 -4
  31. package/dist/server/update-check.js +11 -3
  32. package/dist/server/wait-subscription-scan.js +5 -1
  33. package/package.json +3 -3
  34. package/themes/zhupi-dark.css +5 -30
  35. package/themes/zhupi.css +5 -30
  36. package/web/dist/assets/{TerminalPanel-O3GyFIGG.js → TerminalPanel-CITBqQrb.js} +1 -1
  37. package/web/dist/assets/index-DwToli0J.css +1 -0
  38. package/web/dist/assets/index-oI7UKOqJ.js +368 -0
  39. package/web/dist/index.html +2 -2
  40. package/web/dist/assets/index-B7Zp23Vc.css +0 -1
  41. 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, falling back to GBK (Windows legacy
191
- * Chinese files), then latin1 as a last resort — so previews and inline
192
- * attachments never show mojibake for GBK/GB2312 encoded files. */
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
- try {
195
- return new TextDecoder("utf-8", { fatal: true }).decode(buf);
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("gbk").decode(buf);
199
+ return new TextDecoder("utf-8", { fatal: true }).decode(slice);
200
200
  }
201
201
  catch {
202
- return buf.toString("latin1");
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 SUBAGENT_TOOL_NAMES = [
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
- const name = x === LEGACY_MARKERS_LIST_TOOL_NAME ? MARKERS_LIST_TOOL_NAME : x;
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 pi-core row wins — never
475
- * two rows for the same package.
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 !== PI_CORE_PACKAGE));
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.96.1",
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.1-rc.2",
87
- "@deepseek-ai/dsh-sdk-protocol": "^0.1.1-rc.2",
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",
@@ -198,35 +198,9 @@ body,
198
198
  padding: 2px 0 10px;
199
199
  }
200
200
 
201
- /* 操作按钮:行内簇已提升为基线默认(.chead-actions,随消息悬停显隐、
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
- /* 正文块上的单一复制键同样去重:消息复制已在 head 行的操作簇里 */
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
- /* 操作按钮:行内簇已提升为基线默认(.chead-actions,随消息悬停显隐、
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
- /* 正文块上的单一复制键同样去重:消息复制已在 head 行的操作簇里 */
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-ByfoaDwp.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
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};