@jetcrab/pi-desk 1.1.33 → 1.1.38-dev

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 (67) hide show
  1. package/README.md +10 -10
  2. package/README.zh-CN.md +10 -10
  3. package/docs/commands.md +13 -2
  4. package/docs/plugins.md +10 -0
  5. package/package.json +3 -3
  6. package/src/client/l2_biz/pi-settings/l2-pi-settings-locale.ts +3 -3
  7. package/src/client/l2_biz/plugin-host/l2-plugin-host-runtime.ts +5 -6
  8. package/src/client/l2_biz/settings/l2-region-settings.tsx +30 -9
  9. package/src/client/l3_modules/region/l3-region-biz.ts +2 -2
  10. package/src/client/l4_foundation/plugin-host/l4-plugin-host-runtime.ts +20 -2
  11. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +29 -17
  12. package/src/common/l2_biz/project-preview/l2-project-history-messages.ts +9 -7
  13. package/src/common/l2_biz/settings/l2-settings-messages.ts +82 -69
  14. package/src/common/l2_biz/terminal/l2-terminal-locale-messages.ts +1 -1
  15. package/src/common/l2_biz/workbench/l2-workbench-locale-messages.ts +48 -38
  16. package/src/common/l2_biz/workbench/l2-workbench-picker-messages.ts +12 -8
  17. package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +24 -20
  18. package/src/common/l3_modules/project-files/l3-project-files-locale-messages.ts +29 -17
  19. package/src/common/l4_foundation/locale/l4-host-error-messages.ts +5 -5
  20. package/src/server/l2_biz/plugin-management/l2-plugin-commands.ts +8 -7
  21. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +51 -2
  22. package/src/server/l4_foundation/pi/l4-pi-global-plugin-runtime.ts +9 -1
  23. package/src/server/l4_foundation/pi/l4-pi-local-extension-maintenance.ts +118 -0
  24. package/src/server/l4_foundation/pi/l4-pi-plugin-owner-runtime.ts +4 -1
  25. package/src/server/l4_foundation/pi/l4-pi-plugin-source-changes.ts +4 -0
  26. package/src/server/l4_foundation/pidesk/l4-pidesk-command.ts +1 -1
  27. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  28. package/temp/build/pi-desk/release/.next/build-manifest.json +2 -2
  29. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  30. package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
  31. package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
  32. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  33. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  34. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +1 -1
  35. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  36. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  37. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  39. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  40. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  41. package/temp/build/pi-desk/release/.next/server/app/page.js +3 -3
  42. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  43. package/temp/build/pi-desk/release/.next/server/app/project-preview/page_client-reference-manifest.js +1 -1
  44. package/temp/build/pi-desk/release/.next/server/chunks/16438.js +1 -1
  45. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +2 -2
  46. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +2 -2
  47. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +3 -3
  48. package/temp/build/pi-desk/release/.next/server/chunks/47757.js +1 -1
  49. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +2 -2
  50. package/temp/build/pi-desk/release/.next/server/chunks/84231.js +3 -3
  51. package/temp/build/pi-desk/release/.next/server/chunks/{18748.js → 88353.js} +1 -1
  52. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +4 -4
  53. package/temp/build/pi-desk/release/.next/server/instrumentation.js +1 -1
  54. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  55. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  56. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  57. package/temp/build/pi-desk/release/.next/static/chunks/11937-c21edad81915b999.js +1 -0
  58. package/temp/build/pi-desk/release/.next/static/chunks/15302-e9fd56a464b5267b.js +1 -0
  59. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-1ec03fc5766568f6.js +1 -0
  60. package/temp/build/pi-desk/release/.next/static/chunks/app/page-402842ab5d6afb08.js +158 -0
  61. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  62. package/temp/build/pi-desk/release/.next/static/chunks/11937-d5d4d2199356c3ed.js +0 -1
  63. package/temp/build/pi-desk/release/.next/static/chunks/15302-6e070e13fa65c863.js +0 -1
  64. package/temp/build/pi-desk/release/.next/static/chunks/app/layout-fdea504082053156.js +0 -1
  65. package/temp/build/pi-desk/release/.next/static/chunks/app/page-eb5c2229f59dda1a.js +0 -158
  66. /package/temp/build/pi-desk/release/.next/static/{IDVi-aOj0xwp4DoZljk5f → ljXlpZIQpD6BvWyD8Fmc3}/_buildManifest.js +0 -0
  67. /package/temp/build/pi-desk/release/.next/static/{IDVi-aOj0xwp4DoZljk5f → ljXlpZIQpD6BvWyD8Fmc3}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -87,16 +87,16 @@ Open **<http://localhost:6233>**.
87
87
 
88
88
  The web service runs Pi sessions. Browser, desktop, and mobile clients connect to the same service.
89
89
 
90
- | Application | Source | Purpose |
91
- | -------------------- | ----------------------------------- | --------------------------------- |
92
- | Web service | [`src/`](./src) and [`bin/`](./bin) | Browser client and Pi integration |
93
- | Windows | [`apps/desktop/`](./apps/desktop) | Tauri desktop shell |
94
- | macOS (experimental) | [`apps/desktop/`](./apps/desktop) | Tauri desktop shell |
95
- | Android | [`apps/android/`](./apps/android) | Native Android client for Pi Desk |
96
- | iOS (experimental) | [`apps/ios/`](./apps/ios) | SwiftUI / WKWebView client |
97
- | Tunnel | [`apps/tunnel/`](./apps/tunnel) | Rust TCP tunnel server and client |
98
-
99
- > macOS and iOS clients are experimental and are not included in stable releases. The author does not own a Mac or an iOS device, so these clients have not been tested on real hardware and may not work.
90
+ | Application | Source | Purpose |
91
+ | ------------------ | ----------------------------------- | --------------------------------- |
92
+ | Web service | [`src/`](./src) and [`bin/`](./bin) | Browser client and Pi integration |
93
+ | Windows | [`apps/desktop/`](./apps/desktop) | Tauri desktop shell |
94
+ | macOS | [`apps/desktop/`](./apps/desktop) | Tauri desktop shell |
95
+ | Android | [`apps/android/`](./apps/android) | Native Android client for Pi Desk |
96
+ | iOS (experimental) | [`apps/ios/`](./apps/ios) | SwiftUI / WKWebView client |
97
+ | Tunnel | [`apps/tunnel/`](./apps/tunnel) | Rust TCP tunnel server and client |
98
+
99
+ > iOS clients are experimental and are not included in stable releases. The author does not own an iOS device, so these clients have not been tested on real hardware and may not work.
100
100
 
101
101
  ## Plugins and SDK
102
102
 
package/README.zh-CN.md CHANGED
@@ -87,16 +87,16 @@ pi-desk
87
87
 
88
88
  Web 服务负责运行 Pi 会话,浏览器、桌面和手机客户端连接同一个服务。
89
89
 
90
- | 应用 | 源码 | 用途 |
91
- | --------------- | ---------------------------------- | ---------------------------------- |
92
- | Web 服务 | [`src/`](./src) 与 [`bin/`](./bin) | 浏览器客户端与 Pi 集成 |
93
- | Windows | [`apps/desktop/`](./apps/desktop) | Tauri 桌面壳 |
94
- | macOS(实验性) | [`apps/desktop/`](./apps/desktop) | Tauri 桌面壳 |
95
- | Android | [`apps/android/`](./apps/android) | 连接 Pi Desk 的原生 Android 客户端 |
96
- | iOS(实验性) | [`apps/ios/`](./apps/ios) | SwiftUI / WKWebView 客户端 |
97
- | 隧道 | [`apps/tunnel/`](./apps/tunnel) | Rust TCP 隧道服务与客户端 |
98
-
99
- > macOS 和 iOS 客户端目前为实验性支持,尚未纳入稳定版本。作者没有 Mac 和 iOS 设备,未进行实机验证,可能无法正常使用。
90
+ | 应用 | 源码 | 用途 |
91
+ | ------------- | ---------------------------------- | ---------------------------------- |
92
+ | Web 服务 | [`src/`](./src) 与 [`bin/`](./bin) | 浏览器客户端与 Pi 集成 |
93
+ | Windows | [`apps/desktop/`](./apps/desktop) | Tauri 桌面壳 |
94
+ | macOS | [`apps/desktop/`](./apps/desktop) | Tauri 桌面壳 |
95
+ | Android | [`apps/android/`](./apps/android) | 连接 Pi Desk 的原生 Android 客户端 |
96
+ | iOS(实验性) | [`apps/ios/`](./apps/ios) | SwiftUI / WKWebView 客户端 |
97
+ | 隧道 | [`apps/tunnel/`](./apps/tunnel) | Rust TCP 隧道服务与客户端 |
98
+
99
+ > iOS 客户端目前为实验性支持,尚未纳入稳定版本。作者没有 iOS 设备,未进行实机验证,可能无法正常使用。
100
100
 
101
101
  ## 插件与 SDK
102
102
 
package/docs/commands.md CHANGED
@@ -39,7 +39,7 @@
39
39
  | 安装并跟随 dev | `["plugins", "install", "<name>", "--scope", "global", "--tag", "dev"]` |
40
40
  | 安装指定 npm 版本 | `["plugins", "install", "<name>", "--scope", "global", "--version", "1.2.3"]` |
41
41
  | 重载已安装插件当前代码 | `["plugins", "reload", "<name-1>", "<name-2>", "--scope", "global"]` |
42
- | 卸载包来源 | `["plugins", "remove", "<name-1>", "<name-2>", "--scope", "global"]` |
42
+ | 卸载插件并释放实例 | `["plugins", "remove", "<name-1>", "<name-2>", "--scope", "global"]` |
43
43
 
44
44
  `install` 包含首次安装、更新和重装;目标版本已安装也会重装。`reload` 只重新加载本机现有码,不下载、不修改版本或渠道、不重启宿主,也不重载已有 Pi 会话;它不接受 `--version` 或 `--tag`。
45
45
 
@@ -59,7 +59,18 @@
59
59
  - `list --check-updates` 不安装;附加 `--tag dev` 只筛选跟随 dev 的插件,不改变渠道。标签缺失或查询失败报告对应项错误,不回退 `latest`。
60
60
  - 只调整渠道而不安装:在插件管理界面多选后使用“设置渠道”。
61
61
 
62
- 包文件与 Pi `settings.json` 由官方包管理器维护,不手动覆盖安装目录或包声明。`remove` 只卸载管理入口支持的包来源,不附带业务数据清理;裸扩展卸载不在此命令能力中,不以删除源码或新增禁用规则代替包卸载。
62
+ 包文件与 Pi `settings.json` 由官方包管理器维护,不手动覆盖安装目录或包声明。
63
+
64
+ ### 卸载
65
+
66
+ `remove` 在同一操作中等待目标调用结束、撤销 Node/Browser 注册、等待有界清理,再移除来源;成功通知表示该来源已从管理清单移除,不清理业务配置和业务数据。
67
+
68
+ - 包来源沿用官方 Pi 包卸载;本地目录形式的包不会被当作裸扩展删除源码。
69
+ - `<AgentDir>/extensions/` 中的本地扩展会删除对应的直接文件或目录;符号链接只删除链接,保留目标源码。
70
+ - 全局配置精确引用的外部扩展只移除加载声明,保留外部源码。无法精确拆分的模式规则需先调整原规则,不新增禁用项代替卸载。
71
+ - 目录已删除但仍有已应用来源记录时,清单保留原名称,可用 `remove` 释放残留实例并移除记录,然后加载同名新来源。
72
+
73
+ 卸载不会自动重载已有 Pi 会话的 Native 工具;需要已有会话采用移除结果时使用 `session reload`。
63
74
 
64
75
  ## 让已有会话采用当前配置
65
76
 
package/docs/plugins.md CHANGED
@@ -339,6 +339,16 @@ try {
339
339
 
340
340
  ## 更新生效
341
341
 
342
+ 插件界面可在用户点击按钮后重载指定会话的 Pi 配置:
343
+
344
+ ```ts
345
+ if (!host.pi.reload) throw new Error('请使用输入框「更多 → 重载 Pi 配置」')
346
+ await host.pi.reload(target.source.workId)
347
+ // 确认视图仍有效后,调用自己的 Session Method 重新查询。
348
+ ```
349
+
350
+ `reload(workId: string): Promise<void>` 等同输入框「更多 → 重载 Pi 配置」,宿主按 workId 查找调用时工作窗口当前绑定的 Pi 会话,保留其聊天历史、sessionId 和当前分支,重新加载配置、Skills 及已安装插件的本机代码;插件无需指定 sessionId 或 branchId。不下载包、不重载 Node/Browser Entry、不刷新网页。仅人工操作触发,不在 mount、缺少方法或重连时自动执行。会话忙碌、workId 不存在或 Browser Owner 失效时拒绝;传输期间绑定变化仍由现有重载流程校验并返回错误,断线或超时结果未知时不得自动重试。Promise 等待重载流程结束,不保证每个插件成功加载;结束后重新查询功能状态,仍不可用时保留错误和手动恢复入口。调用期间禁用重复操作;关闭视图不取消已提交重载,迟到结果不得更新旧视图。旧宿主可能没有可选 `host.pi.reload`,缺失时引导使用宿主菜单;不能只更新 SDK 依赖就获得此能力。
351
+
342
352
  包维护不阻塞聊天订阅、刷新或其他会话。缺失或版本不符的包会被跳过,导入/工厂失败的入口不加载;Pi 生命周期回调和插件 Provider 注册的明确错误保留在插件管理诊断中,不升级为整会话失败。正常扩展继续加载。此隔离不终止同步死循环,也不回滚插件已发生的外部副作用;整体初始化超时仍可能使当前会话回退基础聊天,不阻塞其他会话的历史同步。
343
353
 
344
354
  `plugins install/reload` 应用 Node/Browser 能力;目标插件活动视图重挂,旧 Browser Owner 失效。现有 Native 会话不自动重载,新 UI 可能连接旧会话方法,需兼容既有消息和方法数据;需要现有会话加载当前原生代码时使用 `session reload`。热加载无法处理的资源按宿主已有重启流程处理,不把重新挂载界面当作后端任务已停止。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetcrab/pi-desk",
3
- "version": "1.1.33",
3
+ "version": "1.1.38-dev",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -70,7 +70,7 @@
70
70
  "viewerjs": "^1.11.8",
71
71
  "ws": "^8.21.3",
72
72
  "zod": "^4.4.3",
73
- "@jetcrab/pi-desk-sdk": "^1.1.10"
73
+ "@jetcrab/pi-desk-sdk": "^1.1.14-dev"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@earendil-works/pi-agent-core": "1.2.0",
@@ -112,7 +112,7 @@
112
112
  "build:plugin-host-runtime": "pnpm --filter @jetcrab/pi-desk-sdk build",
113
113
  "start": "node bin/pi-desk.js --no-open",
114
114
  "test:package": "node --test tests/l4-pi-desk-package.test.mjs",
115
- "test:pidesk": "node --import tsx --test --test-concurrency=1 tests/l4-pidesk-command.test.ts tests/l2-plugin-commands.test.ts tests/l4-pidesk-session-reload.test.ts tests/l4-pidesk-runtime.test.ts tests/l4-pidesk-runtime-bundle.test.mjs tests/l4-pidesk-docs.test.mjs tests/l4-pidesk-installed-runners.test.mjs tests/l4-pidesk-package-maintenance.test.ts tests/l4-pidesk-release-gate.test.mjs tests/l4-pi-desk-process-tree.test.mjs tests/l4-pi-runner-process.test.ts tests/l4-pi-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs",
115
+ "test:pidesk": "node --import tsx --test --test-concurrency=1 tests/l4-pidesk-command.test.ts tests/l2-plugin-commands.test.ts tests/l2-plugin-management-remove.test.ts tests/l2-plugin-management-batch.test.ts tests/l4-pi-local-extension-maintenance.test.ts tests/l4-pidesk-session-reload.test.ts tests/l4-pidesk-runtime.test.ts tests/l4-pidesk-runtime-bundle.test.mjs tests/l4-pidesk-docs.test.mjs tests/l4-pidesk-installed-runners.test.mjs tests/l4-pidesk-package-maintenance.test.ts tests/l4-pidesk-release-gate.test.mjs tests/l4-pi-desk-process-tree.test.mjs tests/l4-pi-runner-process.test.ts tests/l4-pi-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs && node --conditions=react-server --import tsx --test --test-concurrency=1 tests/l4-pi-plugin-owner-replace.test.ts tests/l4-pi-plugin-source-changes.test.ts",
116
116
  "test:app-websocket": "pnpm build && node --import tsx --test tests/l2-chat-state.test.ts tests/l2-workbench-chat-repository.test.ts tests/l2-workbench-chat.test.ts tests/l2-workbench-layout.test.ts tests/l2-work-session-column-layout.test.ts tests/l2-work-session-update.test.ts tests/l1-app-websocket.test.mjs",
117
117
  "lint": "eslint .",
118
118
  "lint:production": "eslint . --ignore-pattern \"apps/desktop/**\" --ignore-pattern \"**/tests/**\" --ignore-pattern \"**/__tests__/**\" --ignore-pattern \"**/*.test.*\" --ignore-pattern \"**/*.spec.*\"",
@@ -85,11 +85,11 @@ const piSettings: Record<string, string> = {
85
85
  启动程序: 'Command',
86
86
  '参数(每行一个)': 'Arguments (one per line)',
87
87
  '服务说明(可选)': 'Description (optional)',
88
- 工具使用方式: 'Tool exposure',
88
+ 工具使用方式: 'Tool access',
89
89
  沿用默认: 'Use default',
90
- 通过脚本调用: 'Via scripts',
90
+ 通过脚本调用: 'Via codemode',
91
91
  按需发现: 'On-demand discovery',
92
- 直接使用: 'Direct use',
92
+ 直接使用: 'Direct call',
93
93
  隐藏: 'Hidden',
94
94
  '高级配置(JSON)': 'Advanced configuration (JSON)',
95
95
  '继承全局连接,仅保存本项目启用与曝光设置,不复制全局凭据。':
@@ -1,11 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { replaceL4HostSettings } from '@client/l4_foundation/locale/l4-region-store'
4
- import type {
5
- PiNativeBrowserChannel,
6
- PiDeskBrowserChannel,
7
- PluginPushMessage
8
- } from '@jetcrab/pi-desk-sdk/browser'
4
+ import type { PiDeskBrowserChannel, PluginPushMessage } from '@jetcrab/pi-desk-sdk/browser'
9
5
  import { L2ChatSocketContracts } from '@common/l2_biz/chat/l2-chat-websocket-contract'
10
6
  import { L2PluginSocketContracts } from '@common/l2_biz/plugin/l2-plugin-websocket-contract'
11
7
  import {
@@ -41,7 +37,7 @@ function createTransport(
41
37
  owner: L4AppSocketOwner,
42
38
  canLoadEntries: () => boolean
43
39
  ): L4PluginHostTransport {
44
- const pi: PiNativeBrowserChannel = {
40
+ const pi: L4PluginHostTransport['pi'] = {
45
41
  prompt: (source, input) =>
46
42
  owner.request(L2ChatSocketContracts.send, {
47
43
  source,
@@ -79,6 +75,9 @@ function createTransport(
79
75
  },
80
76
  interrupt: async (source): Promise<void> => {
81
77
  await owner.request(L2ChatSocketContracts.interrupt, { source })
78
+ },
79
+ reload: async (source): Promise<void> => {
80
+ await owner.request(L2ChatSocketContracts.reload, { source })
82
81
  }
83
82
  }
84
83
 
@@ -1,11 +1,12 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useMemo, useState } from 'react'
3
+ import { useEffect, useMemo, useRef, useState } from 'react'
4
4
  import { useTranslation } from 'react-i18next'
5
5
  import type { L4Locale } from '@common/l4_foundation/locale/l4-locale'
6
6
  import { useL4AppSocket } from '@client/l4_foundation/realtime/app-socket/l4-app-socket'
7
7
  import { useL4Region } from '@client/l4_foundation/locale/l4-region-provider'
8
8
  import { L4SearchSelect } from '@client/l4_foundation/ui/l4-search-select'
9
+ import { useL4ConfirmDialog } from '@client/l4_foundation/ui/l4-confirm-dialog'
9
10
  import { Button } from '@client/l4_foundation/ui/shadcn/button'
10
11
  import {
11
12
  Select,
@@ -28,6 +29,9 @@ export function L2RegionSettings({
28
29
  const { t } = useTranslation('settings')
29
30
  const region = useL4Region()
30
31
  const { appSocket } = useL4AppSocket()
32
+ const { confirm, dialog } = useL4ConfirmDialog()
33
+ const [changingLanguage, setChangingLanguage] = useState(false)
34
+ const languagePending = useRef(false)
31
35
  const [error, setError] = useState<string | null>(null)
32
36
  const [sampleTime, setSampleTime] = useState(() => new Date())
33
37
  useEffect(() => {
@@ -42,15 +46,29 @@ export function L2RegionSettings({
42
46
  )
43
47
 
44
48
  const chooseLanguage = async (value: L4Locale): Promise<void> => {
49
+ if (languagePending.current) return
50
+ languagePending.current = true
51
+ setChangingLanguage(true)
45
52
  setError(null)
46
- const result = await changeL3RegionLanguage(
47
- value,
48
- t('common:reloadConfirm'),
49
- appSocket,
50
- canLeave
51
- )
52
- if (result === 'save-failed') setError(t('common:localeError'))
53
- if (result === 'blocked') setError(t('common:reloadBlocked'))
53
+ try {
54
+ const result = await changeL3RegionLanguage(
55
+ value,
56
+ () =>
57
+ confirm({
58
+ title: t('language'),
59
+ description: t('common:reloadConfirm'),
60
+ confirmLabel: t('common:reload'),
61
+ confirmVariant: 'default'
62
+ }),
63
+ appSocket,
64
+ canLeave
65
+ )
66
+ if (result === 'save-failed') setError(t('common:localeError'))
67
+ if (result === 'blocked') setError(t('common:reloadBlocked'))
68
+ } finally {
69
+ languagePending.current = false
70
+ setChangingLanguage(false)
71
+ }
54
72
  }
55
73
 
56
74
  const chooseTimeZone = async (value: string): Promise<void> => {
@@ -59,6 +77,7 @@ export function L2RegionSettings({
59
77
 
60
78
  return (
61
79
  <section className="@container min-w-0 space-y-6" aria-labelledby="region-settings-title">
80
+ {dialog}
62
81
  <h2 id="region-settings-title" className="text-xl font-semibold">
63
82
  {t('region')}
64
83
  </h2>
@@ -69,6 +88,7 @@ export function L2RegionSettings({
69
88
  </label>
70
89
  <Select
71
90
  value={region.localePreference}
91
+ disabled={changingLanguage}
72
92
  onValueChange={(value) => {
73
93
  if (value === 'zh-CN' || value === 'en') void chooseLanguage(value)
74
94
  }}
@@ -93,6 +113,7 @@ export function L2RegionSettings({
93
113
  type="button"
94
114
  size="sm"
95
115
  variant="outline"
116
+ disabled={changingLanguage}
96
117
  onClick={() => void chooseLanguage(region.localePreference)}
97
118
  >
98
119
  {t('reloadLanguage')}
@@ -7,14 +7,14 @@ import { readL4Region } from '@client/l4_foundation/locale/l4-region-store'
7
7
 
8
8
  export async function changeL3RegionLanguage(
9
9
  locale: L4Locale,
10
- confirmation: string,
10
+ confirmReload: () => Promise<boolean>,
11
11
  appSocket: L4AppSocketClient,
12
12
  canLeave?: () => boolean | Promise<boolean>
13
13
  ): Promise<'changed' | 'cancelled' | 'blocked' | 'save-failed'> {
14
14
  const current = readL4Region()
15
15
  if (!current) return 'cancelled'
16
16
  const reload = locale !== current.locale
17
- if (reload && !window.confirm(confirmation)) return 'cancelled'
17
+ if (reload && !(await confirmReload())) return 'cancelled'
18
18
  if (reload && canLeave && !(await canLeave())) return 'blocked'
19
19
  try {
20
20
  await appSocket.request(L3AppRuntimeSocketContracts.apply, {
@@ -140,7 +140,9 @@ export interface L4PluginRegistrySnapshot {
140
140
  }
141
141
 
142
142
  export interface L4PluginHostTransport {
143
- readonly pi: PiNativeBrowserChannel
143
+ readonly pi: Omit<PiNativeBrowserChannel, 'reload'> & {
144
+ reload?(source: PluginSource): Promise<void>
145
+ }
144
146
  listBrowserEntries(): Promise<readonly L4PluginBrowserEntryDescriptor[]>
145
147
  subscribePush(listener: (message: PluginPushMessage) => void): () => void
146
148
  createPiDeskChannel(pluginName: string): PiDeskBrowserChannel
@@ -1885,6 +1887,7 @@ export class L4PluginHostRuntime {
1885
1887
  }
1886
1888
  const piDesk = this.transport.createPiDeskChannel(pluginName)
1887
1889
  const pi = this.transport.pi
1890
+ const reloadPi = pi.reload
1888
1891
  const subscribe = (
1889
1892
  listeners: Set<() => void>,
1890
1893
  listener: () => void,
@@ -1987,7 +1990,22 @@ export class L4PluginHostRuntime {
1987
1990
  execute: (source, input) => invoke(() => pi.bash.execute(source, input)),
1988
1991
  abort: (source) => invoke(() => pi.bash.abort(source))
1989
1992
  },
1990
- interrupt: (source) => invoke(() => pi.interrupt(source))
1993
+ interrupt: (source) => invoke(() => pi.interrupt(source)),
1994
+ ...(reloadPi
1995
+ ? {
1996
+ reload: (workId: string): Promise<void> =>
1997
+ invoke(async () => {
1998
+ if (typeof workId !== 'string' || !workId.trim()) {
1999
+ throw new Error('重载 Pi 配置需要有效 workId')
2000
+ }
2001
+ const workSession = this.workSessions.find(
2002
+ (item) => item.source.workId === workId
2003
+ )
2004
+ if (!workSession) throw new Error('工作会话不存在')
2005
+ await reloadPi(structuredClone(workSession.source))
2006
+ })
2007
+ }
2008
+ : {})
1991
2009
  },
1992
2010
  workSessions,
1993
2011
  workSessionFiles: {
@@ -25,7 +25,7 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
25
25
  phase_applying: 'Applying',
26
26
  phase_waiting: 'Waiting to activate',
27
27
  phase_failed: 'Failed',
28
- updateCheckFailed: 'Could not check plugin updates',
28
+ updateCheckFailed: 'Could not check for plugin updates',
29
29
  actionFailed: '{{action}} failed',
30
30
  retrying: 'Retrying {{name}}',
31
31
  retryFailed: 'Could not retry plugin UI',
@@ -35,7 +35,7 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
35
35
  title: 'Plugins',
36
36
  description: 'Manage plugin packages and local extensions.',
37
37
  noPluginDescription: 'No description available',
38
- checkUpdates: 'Check updates',
38
+ checkUpdates: 'Check for updates',
39
39
  checkingUpdates: 'Checking…',
40
40
  restartApply: 'Restart to finish',
41
41
  installPlugin: 'Install plugin',
@@ -89,12 +89,18 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
89
89
  contributionSidebar: 'Session sidebar',
90
90
  contributionWidget: 'Composer widget',
91
91
  contributionSessionHeader: 'Session header item',
92
- tagTools: '{{count}} tools',
93
- tagFixed: '{{count}} fixed prompts',
94
- tagDynamic: '{{count}} dynamic hooks',
95
- tagOverride: '{{count}} builtin overrides',
96
- tagManual: '{{count}} manual prompts',
97
- tagInteraction: '{{count}} interactions',
92
+ tagTools_one: '{{count}} tool',
93
+ tagTools_other: '{{count}} tools',
94
+ tagFixed_one: '{{count}} fixed prompt',
95
+ tagFixed_other: '{{count}} fixed prompts',
96
+ tagDynamic_one: '{{count}} dynamic hook',
97
+ tagDynamic_other: '{{count}} dynamic hooks',
98
+ tagOverride_one: '{{count}} built-in override',
99
+ tagOverride_other: '{{count}} built-in overrides',
100
+ tagManual_one: '{{count}} manual prompt',
101
+ tagManual_other: '{{count}} manual prompts',
102
+ tagInteraction_one: '{{count}} interaction',
103
+ tagInteraction_other: '{{count}} interactions',
98
104
  unknown: 'Unknown',
99
105
  stateActive: 'Active',
100
106
  stateInactive: 'Inactive',
@@ -113,7 +119,7 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
113
119
  rawSchema: 'Raw schema',
114
120
  toolCapabilities: 'Tool capabilities',
115
121
  toolDescription: 'Each tool shows its purpose, parameters and declared system prompts.',
116
- overridesBuiltin: 'Overrides a Pi builtin tool',
122
+ overridesBuiltin: 'Overrides a Pi built-in tool',
117
123
  noDescription: 'No description provided',
118
124
  systemPrompts: 'System prompts',
119
125
  inactivePrompt: 'This tool is inactive; its prompts do not enter the system prompt.',
@@ -195,7 +201,7 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
195
201
  download_local: 'No override (local configuration)',
196
202
  download_auto: 'Automatic',
197
203
  download_domestic: 'China mirror',
198
- download_official: 'Official npm',
204
+ download_official: 'Official npm registry',
199
205
  download_custom: 'Custom',
200
206
  registryUrl: 'Registry URL',
201
207
  sourceDetails: 'View source URL',
@@ -235,7 +241,7 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
235
241
  compatibilityUnknown: 'Compatibility not verified',
236
242
  homepage: 'Website',
237
243
  repository: 'Source code',
238
- removeDescription: 'Remove this plugin. Saved configuration and data will be kept.',
244
+ removeDescription: 'Uninstall this plugin. Saved configuration and data will be kept.',
239
245
  thirdPartyDescription:
240
246
  'This plugin can execute code on your computer. Only install it if you trust its publisher and source.',
241
247
  confirmChange: 'Apply this change to the selected plugin.',
@@ -328,12 +334,18 @@ export const L2_PLUGIN_MANAGEMENT_MESSAGES = {
328
334
  contributionSidebar: '会话侧栏',
329
335
  contributionWidget: '输入区挂件',
330
336
  contributionSessionHeader: '会话标题项',
331
- tagTools: '工具 {{count}}',
332
- tagFixed: '固定提示 {{count}}',
333
- tagDynamic: '动态影响 {{count}}',
334
- tagOverride: '替换内置 {{count}}',
335
- tagManual: '手动提示 {{count}}',
336
- tagInteraction: '交互 {{count}}',
337
+ tagTools_one: '工具 {{count}}',
338
+ tagTools_other: '工具 {{count}}',
339
+ tagFixed_one: '固定提示 {{count}}',
340
+ tagFixed_other: '固定提示 {{count}}',
341
+ tagDynamic_one: '动态影响 {{count}}',
342
+ tagDynamic_other: '动态影响 {{count}}',
343
+ tagOverride_one: '替换内置 {{count}}',
344
+ tagOverride_other: '替换内置 {{count}}',
345
+ tagManual_one: '手动提示 {{count}}',
346
+ tagManual_other: '手动提示 {{count}}',
347
+ tagInteraction_one: '交互 {{count}}',
348
+ tagInteraction_other: '交互 {{count}}',
337
349
  unknown: '未知',
338
350
  stateActive: '启用',
339
351
  stateInactive: '未启用',
@@ -17,7 +17,7 @@ export const L2_PROJECT_HISTORY_MESSAGES = {
17
17
  noRecentProjects: 'No project history. Enter a path or browse directories.',
18
18
  currentHead: 'Current HEAD',
19
19
  localBranch: 'Local branch',
20
- remoteBranch: 'Remote branch · local record',
20
+ remoteBranch: 'Remote-tracking branch',
21
21
  retry: 'Retry',
22
22
  compareDirect: 'Compare directly',
23
23
  loadingCommits: 'Loading commits…',
@@ -29,17 +29,18 @@ export const L2_PROJECT_HISTORY_MESSAGES = {
29
29
  loadedLimit: '2,000 commits loaded. Narrow your search.',
30
30
  noMoreCommits: 'All commits loaded',
31
31
  backCommits: 'Back to commits',
32
- commitChanges: 'Commit changes',
32
+ commitChanges: 'Changes in commit',
33
33
  cumulativeChanges: 'Cumulative changes',
34
- loadedFiles: '{{count}} files loaded',
34
+ loadedFiles_one: '{{count}} file loaded',
35
+ loadedFiles_other: '{{count}} files loaded',
35
36
  backCumulative: 'Back to cumulative changes',
36
37
  loadingChanges: 'Loading changes…',
37
38
  noDiff: 'No file differences',
38
39
  loadMoreFiles: 'Load more files',
39
- changesLimit: 'Change file display limit reached.',
40
+ changesLimit: 'Too many changed files to display.',
40
41
  commitDetails: 'Commit details',
41
42
  copySha: 'Copy commit SHA',
42
- compareParent: 'Compare parent commit',
43
+ compareParent: 'Compare with parent commit',
43
44
  parentCommit: 'Parent {{count}} · {{oid}}',
44
45
  mergeBaseToTarget: 'Common ancestor → target commit',
45
46
  baseToTarget: 'Base commit → target commit',
@@ -61,7 +62,7 @@ export const L2_PROJECT_HISTORY_MESSAGES = {
61
62
  author: 'Commit author',
62
63
  searchCommits: 'Search commit message or SHA',
63
64
  refreshGit: 'Refresh Git history',
64
- localRecord: 'Local record',
65
+ localRecord: 'Cached locally',
65
66
  refUnavailable: 'Reference unavailable',
66
67
  aheadBehind: '{{ahead}} ahead · {{behind}} behind',
67
68
  chooseBaseBranch: 'Choose a base branch',
@@ -122,7 +123,8 @@ export const L2_PROJECT_HISTORY_MESSAGES = {
122
123
  backCommits: '返回提交列表',
123
124
  commitChanges: '提交变更',
124
125
  cumulativeChanges: '累计变更',
125
- loadedFiles: '已加载 {{count}} 个文件',
126
+ loadedFiles_one: '已加载 {{count}} 个文件',
127
+ loadedFiles_other: '已加载 {{count}} 个文件',
126
128
  backCumulative: '返回累计变更',
127
129
  loadingChanges: '读取变更…',
128
130
  noDiff: '没有文件差异',