pi-web-ui 0.94.1 → 0.96.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 (100) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/README.md +5 -6
  3. package/README.zh-CN.md +4 -5
  4. package/bin/pi-web-ui.mjs +910 -237
  5. package/dist/server/agent-service.js +2655 -231
  6. package/dist/server/approval-rules.js +596 -0
  7. package/dist/server/attachment-store.js +129 -0
  8. package/dist/server/attachments.js +92 -174
  9. package/dist/server/bg-servers.js +45 -7
  10. package/dist/server/claim-files-tool.js +4 -8
  11. package/dist/server/claim-store.js +4 -2
  12. package/dist/server/client-state.js +67 -14
  13. package/dist/server/compact-context-tool.js +126 -0
  14. package/dist/server/composer-drafts.js +9 -0
  15. package/dist/server/context-budget.js +317 -0
  16. package/dist/server/control-socket.js +57 -28
  17. package/dist/server/conversation-read-tool.js +7 -17
  18. package/dist/server/dangling-tools.js +229 -0
  19. package/dist/server/delegate-task.js +25 -28
  20. package/dist/server/dsh/dsh-agent-service.js +69 -66
  21. package/dist/server/edit-soft-tool.js +84 -16
  22. package/dist/server/eval-tool.js +588 -0
  23. package/dist/server/file-archives.js +16 -5
  24. package/dist/server/files-service.js +65 -18
  25. package/dist/server/goal-service.js +371 -77
  26. package/dist/server/hashline-engine.js +703 -0
  27. package/dist/server/host-guard.js +94 -0
  28. package/dist/server/host-metrics.js +26 -2
  29. package/dist/server/i18n.js +3 -3
  30. package/dist/server/index.js +456 -80
  31. package/dist/server/lsp-tool.js +1371 -0
  32. package/dist/server/mcp-bridge.js +47 -3
  33. package/dist/server/model-admin.js +112 -32
  34. package/dist/server/office-parse.js +375 -0
  35. package/dist/server/patch-tool.js +85 -0
  36. package/dist/server/permission-preset.js +25 -0
  37. package/dist/server/plan-manager.js +114 -0
  38. package/dist/server/plugin-api-catalog.js +296 -0
  39. package/dist/server/plugin-catalog-sync.js +36 -19
  40. package/dist/server/plugin-catalog.js +11 -4
  41. package/dist/server/plugin-facilities.js +92 -21
  42. package/dist/server/plugin-install-spec.js +196 -0
  43. package/dist/server/plugin-installer.js +73 -0
  44. package/dist/server/plugin-llm.js +71 -65
  45. package/dist/server/plugin-manifest-validate.js +305 -0
  46. package/dist/server/plugin-project.js +117 -13
  47. package/dist/server/plugin-tool-guard.js +120 -0
  48. package/dist/server/plugin-updater.js +115 -12
  49. package/dist/server/plugins.js +950 -205
  50. package/dist/server/present-files-tool.js +9 -12
  51. package/dist/server/process-utils.js +16 -6
  52. package/dist/server/prompt-composer.js +9 -0
  53. package/dist/server/protocol-version.js +1 -1
  54. package/dist/server/read-tool.js +69 -30
  55. package/dist/server/resolve-global-sdk.js +30 -16
  56. package/dist/server/schedule-agent-tool.js +12 -15
  57. package/dist/server/scheduler-tasks.js +6 -0
  58. package/dist/server/sdk-origin.js +18 -2
  59. package/dist/server/serialize.js +111 -14
  60. package/dist/server/settings-service.js +112 -3
  61. package/dist/server/skill-tool.js +5 -7
  62. package/dist/server/subagent-templates.js +51 -0
  63. package/dist/server/subagents.js +83 -66
  64. package/dist/server/terminals.js +248 -81
  65. package/dist/server/tool-approval.js +84 -0
  66. package/dist/server/tool-manager.js +237 -12
  67. package/dist/server/tool-overrides.js +59 -0
  68. package/dist/server/update-check.js +28 -1
  69. package/dist/server/uploads.js +17 -2
  70. package/dist/server/wait-subscription-scan.js +18 -21
  71. package/dist/server/workspace-snapshot.js +113 -0
  72. package/dist/server/ws-client-id.js +29 -0
  73. package/dist/server/ws-pending-queue.js +60 -0
  74. package/extensions/webui.ts +60 -2
  75. package/package.json +5 -3
  76. package/plugin-sdk/README.md +25 -0
  77. package/plugin-sdk/index.d.ts +31 -38
  78. package/plugin-sdk/index.mjs +35 -19
  79. package/plugins/catalog.json +40 -0
  80. package/themes/aetheris.css +457 -0
  81. package/themes/ayu-light.css +6 -6
  82. package/themes/catppuccin-latte.css +6 -6
  83. package/themes/claude-code-dark.css +144 -0
  84. package/themes/codex.css +6 -6
  85. package/themes/everforest-light.css +6 -6
  86. package/themes/geist.css +6 -6
  87. package/themes/gruvbox-light.css +6 -6
  88. package/themes/kanagawa-lotus.css +6 -6
  89. package/themes/rose-pine-dawn.css +6 -6
  90. package/themes/solarized-light.css +6 -6
  91. package/themes/vs-code-dark.css +146 -0
  92. package/themes/zhupi-dark.css +658 -0
  93. package/themes/zhupi.css +711 -0
  94. package/web/dist/assets/{TerminalPanel-MVoxpJOA.js → TerminalPanel-DDChcYOh.js} +1 -1
  95. package/web/dist/assets/index-Do9RgJC3.js +366 -0
  96. package/web/dist/assets/index-DryOsILO.css +1 -0
  97. package/web/dist/assets/{markdown-eUQn_o9D.js → markdown-DXwnfD9T.js} +1 -1
  98. package/web/dist/index.html +3 -3
  99. package/web/dist/assets/index-B3S9MxnN.css +0 -1
  100. package/web/dist/assets/index-DVLrHI2E.js +0 -364
@@ -0,0 +1,94 @@
1
+ /**
2
+ * HTTP Host 白名单守卫(防 DNS rebinding,审查跟踪 issue #352)。
3
+ *
4
+ * 此前 Host/Origin 校验只覆盖 WS 升级,HTTP 路由完全不校验 Host;而
5
+ * originAllowed 的「Origin 与 Host 自比」在 rebinding 下必然相等——恶意网页
6
+ * 让自己的域名解析到 127.0.0.1(或私网 IP)后,浏览器发出的请求 Host 就是
7
+ * 攻击者域名,无 token 默认部署即可获得与本地用户等价的 API 面。
8
+ *
9
+ * 规则(纯函数,便于单测):
10
+ * - `hasAuthToken` 为真 → 直接放行(token 本身就是鉴权,rebinding 拿不到口令);
11
+ * - 显式白名单 PI_WEB_ALLOW_HOSTS 非空 → 严格模式,只认白名单(与 WS 侧
12
+ * 旧行为一致,回环/私网兜底不再生效);
13
+ * - Host 是回环(localhost / 127.0.0.0-127.255.255.255 / [::1])→ 放行;
14
+ * - Host 是私网/链路本地(10/8、172.16/12、192.168/16、169.254/16、
15
+ * ::ffff: 映射的上述段、fe80::/10、fc00::/7)→ 放行,保留「局域网用 IP
16
+ * 直接访问」的既有用法(rebinding 的 Host 是公网域名,不会命中这些段);
17
+ * - 其余(公网域名等)→ 拒绝。
18
+ */
19
+ /** 拆掉 Host 头里的端口与 IPv6 方括号,返回小写 hostname。 */
20
+ export function hostWithoutPort(authority) {
21
+ let a = authority.trim().toLowerCase();
22
+ if (a.startsWith("[")) {
23
+ const end = a.indexOf("]");
24
+ if (end === -1)
25
+ return "";
26
+ return a.slice(1, end);
27
+ }
28
+ // IPv6 裸形式(无方括号的 Host 头不合规,但容忍):含多个冒号时整体当 hostname。
29
+ const colon = a.lastIndexOf(":");
30
+ if (colon !== -1 && a.indexOf(":") === colon)
31
+ a = a.slice(0, colon);
32
+ return a;
33
+ }
34
+ function isIpv4Literal(host) {
35
+ const parts = host.split(".");
36
+ if (parts.length !== 4)
37
+ return null;
38
+ const octets = [];
39
+ for (const p of parts) {
40
+ if (!/^\d{1,3}$/.test(p))
41
+ return null;
42
+ const n = Number(p);
43
+ if (n > 255)
44
+ return null;
45
+ octets.push(n);
46
+ }
47
+ return octets;
48
+ }
49
+ /** 归一 IPv4-mapped IPv6(::ffff:a.b.c.d)为点分 IPv4,便于按段判断。 */
50
+ function unwrapIpv4Mapped(host) {
51
+ const m = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
52
+ return m ? m[1] : host;
53
+ }
54
+ export function isLoopbackHost(host) {
55
+ if (host === "localhost" || host.endsWith(".localhost"))
56
+ return true;
57
+ const h = unwrapIpv4Mapped(host);
58
+ if (h.includes(":"))
59
+ return h === "::1";
60
+ const o = isIpv4Literal(h);
61
+ return o !== null && o[0] === 127;
62
+ }
63
+ export function isPrivateLanHost(host) {
64
+ const h = unwrapIpv4Mapped(host);
65
+ if (h.includes(":")) {
66
+ // fe80::/10 链路本地;fc00::/7 (fc/fd 开头) ULA 私网。
67
+ return /^fe[89ab]/.test(h) || /^f[cd]/.test(h);
68
+ }
69
+ const o = isIpv4Literal(h);
70
+ if (!o)
71
+ return false;
72
+ const [a, b] = o;
73
+ if (a === 10)
74
+ return true;
75
+ if (a === 172 && b >= 16 && b <= 31)
76
+ return true;
77
+ if (a === 192 && b === 168)
78
+ return true;
79
+ if (a === 169 && b === 254)
80
+ return true;
81
+ return false;
82
+ }
83
+ export function httpHostAllowed(hostHeader, opts) {
84
+ if (opts.hasAuthToken)
85
+ return true;
86
+ const host = hostWithoutPort(hostHeader);
87
+ if (!host)
88
+ return false;
89
+ // PI_WEB_ALLOW_HOSTS 是文档化的「严格模式」:设置了就只认白名单(与 WS
90
+ // 侧旧行为一致),回环/私网兜底仅在未设置时生效。
91
+ if (opts.allowHosts.length > 0)
92
+ return opts.allowHosts.includes(host);
93
+ return isLoopbackHost(host) || isPrivateLanHost(host);
94
+ }
@@ -34,7 +34,21 @@ export function calculateHostMetrics(previous, current) {
34
34
  memoryPercent,
35
35
  };
36
36
  }
37
- export function createHostMetricsSampler(readSnapshot = defaultReadSnapshot) {
37
+ /** CPU 平滑窗口:上报最近 N 次有效读数的均值。心跳 2s 一次,默认 5 次 ≈ 10s 平均
38
+ * —— 单次 2s 采样的毛刺(编译/构建抖动)不再直接上底栏,观感与任务管理器的
39
+ * 曲线对齐。内存变化慢,不平滑,沿用瞬时值。 */
40
+ export const CPU_SMOOTH_WINDOW = 5;
41
+ /** 有效 CPU 读数序列的均值(空序列 → null,前端显示 —)。纯函数,可单测。 */
42
+ export function meanCpuPercent(readings) {
43
+ if (readings.length === 0)
44
+ return null;
45
+ let sum = 0;
46
+ for (const v of readings)
47
+ sum += v;
48
+ return sum / readings.length;
49
+ }
50
+ export function createHostMetricsSampler(readSnapshot = defaultReadSnapshot, windowSize = CPU_SMOOTH_WINDOW) {
51
+ const window = Math.max(1, Math.floor(windowSize));
38
52
  let previous = null;
39
53
  try {
40
54
  previous = readSnapshot();
@@ -43,6 +57,8 @@ export function createHostMetricsSampler(readSnapshot = defaultReadSnapshot) {
43
57
  // 初始基线读取异常时不击穿服务启动,待后续心跳重试
44
58
  previous = null;
45
59
  }
60
+ /** 最近 window 次有效 CPU 读数(totalDelta<=0 的 null 读数不进序列,避免空洞拉低均值)。 */
61
+ const history = [];
46
62
  return () => {
47
63
  const current = readSnapshot();
48
64
  if (!previous) {
@@ -54,6 +70,14 @@ export function createHostMetricsSampler(readSnapshot = defaultReadSnapshot) {
54
70
  }
55
71
  const metrics = calculateHostMetrics(previous, current);
56
72
  previous = current;
57
- return metrics;
73
+ if (metrics.cpuPercent !== null && Number.isFinite(metrics.cpuPercent)) {
74
+ history.push(metrics.cpuPercent);
75
+ if (history.length > window)
76
+ history.splice(0, history.length - window);
77
+ }
78
+ return {
79
+ cpuPercent: meanCpuPercent(history),
80
+ memoryPercent: metrics.memoryPercent,
81
+ };
58
82
  };
59
83
  }
@@ -17,9 +17,9 @@
17
17
  * Keys must be globally unique (module prefix guarantees it).
18
18
  * - `getServerBlock(lang, key, zhLines, enLines)` — multi-line blocks
19
19
  * (guidance arrays, prompt sections); tables store one `\n`-joined string.
20
- * - `bilingual(en, zh)` — tool DEFINITIONS stay static en+zh (baked into the
21
- * session at creation; the model works fine with English definitions
22
- * under any UI language). No keys needed.
20
+ * - `bilingual(en, zh)` — now used only for user-facing relay strings (error
21
+ * messages etc.). Tool DEFINITIONS (description/promptSnippet/promptGuidelines)
22
+ * are concise English-only. No keys needed.
23
23
  * - Template DATA (subagent_templates content, user overrides) stays zh/en
24
24
  * fields — it is user-editable config, not code copy.
25
25
  *