@jetcrab/pi-desk 1.1.1 → 1.1.5-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 (119) hide show
  1. package/docs/pi-desk/README.md +47 -0
  2. package/docs/pi-desk/commands.md +76 -0
  3. package/docs/pi-desk/examples/local-application/browser/application.js +106 -0
  4. package/docs/pi-desk/examples/local-application/browser/entry.js +9 -0
  5. package/docs/pi-desk/examples/local-application/package.json +9 -0
  6. package/docs/pi-desk/examples/local-application/pi-desk.ts +11 -0
  7. package/docs/pi-desk/plugins.md +119 -0
  8. package/package.json +3 -2
  9. package/src/client/l2_biz/pi-settings/hooks/l2-use-pi-settings.ts +37 -21
  10. package/src/client/l2_biz/pi-settings/l2-pi-settings.tsx +37 -43
  11. package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-editor.tsx +3 -1
  12. package/src/client/l2_biz/pi-settings/views/l2-mcp-settings-list.tsx +1 -1
  13. package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-catalog.ts +3 -1
  14. package/src/client/l2_biz/plugin-host/hooks/l2-use-plugin-download-source.ts +5 -1
  15. package/src/client/l2_biz/plugin-host/l2-plugin-capability-details.tsx +57 -41
  16. package/src/client/l2_biz/plugin-host/l2-plugin-management-controls.tsx +89 -116
  17. package/src/client/l2_biz/plugin-host/l2-plugin-management.tsx +492 -338
  18. package/src/client/l2_biz/plugin-host/l2-use-plugin-management.ts +72 -22
  19. package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-detail.tsx +104 -76
  20. package/src/client/l2_biz/plugin-host/views/l2-plugin-catalog-search.tsx +101 -56
  21. package/src/client/l2_biz/plugin-host/views/l2-plugin-direct-install.tsx +91 -59
  22. package/src/client/l2_biz/plugin-host/views/l2-plugin-download-source.tsx +36 -27
  23. package/src/client/l2_biz/plugin-host/views/l2-plugin-installed-list.tsx +218 -208
  24. package/src/client/l2_biz/plugin-host/views/l2-plugin-management-channel.tsx +12 -5
  25. package/src/client/l2_biz/plugin-host/views/l2-plugin-management-dialog.tsx +84 -106
  26. package/src/client/l2_biz/plugin-host/views/l2-plugin-readme.tsx +29 -15
  27. package/src/client/l2_biz/settings/hooks/l2-use-project-model-default-settings.ts +22 -11
  28. package/src/client/l2_biz/settings/l2-settings.tsx +6 -11
  29. package/src/client/l2_biz/settings/views/l2-project-model-default-settings.tsx +20 -14
  30. package/src/client/l2_biz/terminal/l2-terminal-workspace.tsx +35 -1
  31. package/src/client/l2_biz/workbench/file-workspace/l2-work-session-file-preview.tsx +6 -2
  32. package/src/client/l2_biz/workbench/hooks/l2-workbench-chat-viewport.ts +14 -1
  33. package/src/client/l2_biz/workbench/l2-workbench-branch-picker.tsx +33 -5
  34. package/src/client/l2_biz/workbench/l2-workbench-sidebar-layout.tsx +3 -1
  35. package/src/client/l2_biz/workbench/l2-workbench.tsx +20 -3
  36. package/src/client/l3_modules/conversation/l3-conversation-call-summary.tsx +0 -3
  37. package/src/client/l3_modules/project-files/l3-file-context-menu-content.tsx +1 -0
  38. package/src/client/l4_foundation/ui/l4-app-dialog.tsx +3 -1
  39. package/src/client/l4_foundation/ui/l4-image-viewer.tsx +21 -0
  40. package/src/client/l4_foundation/ui/shadcn/checkbox.tsx +27 -0
  41. package/src/client/l4_foundation/ui/shadcn/context-menu.tsx +8 -1
  42. package/src/client/l4_foundation/ui/shadcn/dropdown-menu.tsx +5 -0
  43. package/src/client/l4_foundation/ui/shadcn/popover.tsx +2 -0
  44. package/src/client/l4_foundation/ui/shadcn/select.tsx +2 -0
  45. package/src/client/l4_foundation/ui/shadcn/switch.tsx +31 -0
  46. package/src/client/l4_foundation/ui/shadcn/tabs.tsx +74 -0
  47. package/src/common/l2_biz/plugin/l2-plugin-management-messages.ts +52 -22
  48. package/src/common/l3_modules/conversation/l3-conversation-locale-messages.ts +2 -2
  49. package/src/common/l3_modules/conversation/l3-conversation-pidesk.ts +1 -1
  50. package/src/common/l4_foundation/plugin/l4-plugin-package.ts +1 -0
  51. package/src/server/l2_biz/plugin-management/l2-plugin-catalog.ts +16 -5
  52. package/src/server/l2_biz/plugin-management/l2-plugin-commands.ts +21 -1
  53. package/src/server/l2_biz/plugin-management/l2-plugin-management.ts +21 -2
  54. package/src/server/l2_biz/plugin-management/l2-plugin-updates.ts +17 -3
  55. package/src/server/l4_foundation/pi/l4-pi-basic-session-services.ts +3 -3
  56. package/src/server/l4_foundation/pi/l4-pi-package-maintenance-runner.mts +4 -11
  57. package/src/server/l4_foundation/pi/l4-pi-plugin-registry-config.ts +52 -34
  58. package/src/server/l4_foundation/pi/l4-pi-plugin-registry-network.ts +5 -3
  59. package/src/server/l4_foundation/pi/l4-pi-plugin-registry.ts +23 -8
  60. package/src/server/l4_foundation/pi/l4-pi-session-initialization.ts +2 -5
  61. package/src/server/l4_foundation/pidesk/l4-pidesk-command.ts +8 -1
  62. package/src/server/l4_foundation/pidesk/l4-pidesk-runtime.ts +24 -38
  63. package/src/server/l4_foundation/pidesk/l4-pidesk-system-prompt.ts +11 -0
  64. package/temp/build/pi-desk/release/.next/BUILD_ID +1 -1
  65. package/temp/build/pi-desk/release/.next/build-manifest.json +3 -3
  66. package/temp/build/pi-desk/release/.next/prerender-manifest.json +3 -3
  67. package/temp/build/pi-desk/release/.next/react-loadable-manifest.json +1 -1
  68. package/temp/build/pi-desk/release/.next/required-server-files.js +3 -3
  69. package/temp/build/pi-desk/release/.next/required-server-files.json +3 -3
  70. package/temp/build/pi-desk/release/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  71. package/temp/build/pi-desk/release/.next/server/app/_global-error.html +1 -1
  72. package/temp/build/pi-desk/release/.next/server/app/_global-error.rsc +3 -3
  73. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_full.segment.rsc +3 -3
  74. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +3 -3
  75. package/temp/build/pi-desk/release/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/temp/build/pi-desk/release/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  77. package/temp/build/pi-desk/release/.next/server/app/api/health/route.js +1 -1
  78. package/temp/build/pi-desk/release/.next/server/app/login/page.js +1 -1
  79. package/temp/build/pi-desk/release/.next/server/app/login/page_client-reference-manifest.js +1 -1
  80. package/temp/build/pi-desk/release/.next/server/app/page.js +79 -79
  81. package/temp/build/pi-desk/release/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/temp/build/pi-desk/release/.next/server/chunks/25406.js +8 -77
  83. package/temp/build/pi-desk/release/.next/server/chunks/{81107.js → 35348.js} +2 -2
  84. package/temp/build/pi-desk/release/.next/server/chunks/35985.js +4 -4
  85. package/temp/build/pi-desk/release/.next/server/chunks/36361.js +1 -1
  86. package/temp/build/pi-desk/release/.next/server/chunks/47757.js +6 -75
  87. package/temp/build/pi-desk/release/.next/server/chunks/50520.js +4 -4
  88. package/temp/build/pi-desk/release/.next/server/chunks/58330.js +1 -1
  89. package/temp/build/pi-desk/release/.next/server/chunks/84231.js +2 -2
  90. package/temp/build/pi-desk/release/.next/server/chunks/89236.js +2 -2
  91. package/temp/build/pi-desk/release/.next/server/chunks/92386.js +5 -5
  92. package/temp/build/pi-desk/release/.next/server/chunks/94518.js +1 -1
  93. package/temp/build/pi-desk/release/.next/server/chunks/9874.js +1 -1
  94. package/temp/build/pi-desk/release/.next/server/middleware-build-manifest.js +1 -1
  95. package/temp/build/pi-desk/release/.next/server/middleware-react-loadable-manifest.js +1 -1
  96. package/temp/build/pi-desk/release/.next/server/pages/500.html +1 -1
  97. package/temp/build/pi-desk/release/.next/server/server-reference-manifest.json +1 -1
  98. package/temp/build/pi-desk/release/.next/static/chunks/14197-3d3ddd426ac06c9c.js +3 -0
  99. package/temp/build/pi-desk/release/.next/static/chunks/34903-e4205c0becabea76.js +318 -0
  100. package/temp/build/pi-desk/release/.next/static/chunks/{54206.7c0792096df8a5cf.js → 54206.82a63ab971911eda.js} +3 -3
  101. package/temp/build/pi-desk/release/.next/static/chunks/74132-876f256c7a6101af.js +13 -0
  102. package/temp/build/pi-desk/release/.next/static/chunks/78923-17a9c337b29dcecd.js +1 -0
  103. package/temp/build/pi-desk/release/.next/static/chunks/96995-8f8313ea2ac3abd7.js +62 -0
  104. package/temp/build/pi-desk/release/.next/static/chunks/app/{layout-7c5409cced9c2501.js → layout-4faf3a52d219296d.js} +2 -2
  105. package/temp/build/pi-desk/release/.next/static/chunks/app/login/{page-8c87fb9e6c918848.js → page-bf893718bb151f3c.js} +1 -1
  106. package/temp/build/pi-desk/release/.next/static/chunks/app/page-28e1d77d6b884657.js +158 -0
  107. package/temp/build/pi-desk/release/.next/static/chunks/{webpack-45424d28099bbefa.js → webpack-7bfbf627379fb4c1.js} +1 -1
  108. package/temp/build/pi-desk/release/.next/static/css/3213ee28336bddc2.css +1 -0
  109. package/temp/build/pi-desk/release/.next/trace-build +1 -1
  110. package/src/server/l4_foundation/pidesk/l4-pidesk-skill-template.ts +0 -87
  111. package/temp/build/pi-desk/release/.next/static/chunks/27491-8b2d3e345b9142ea.js +0 -318
  112. package/temp/build/pi-desk/release/.next/static/chunks/7089-600a8f5593b7846c.js +0 -3
  113. package/temp/build/pi-desk/release/.next/static/chunks/74132-0884df6a6f11861a.js +0 -13
  114. package/temp/build/pi-desk/release/.next/static/chunks/78923-539a8b5f7af3c54a.js +0 -1
  115. package/temp/build/pi-desk/release/.next/static/chunks/96995-ae32167974365050.js +0 -62
  116. package/temp/build/pi-desk/release/.next/static/chunks/app/page-39b146293b6eb488.js +0 -158
  117. package/temp/build/pi-desk/release/.next/static/css/56486914a8ba0a1b.css +0 -1
  118. /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → IgHJ40fniC07-ioYUKobL}/_buildManifest.js +0 -0
  119. /package/temp/build/pi-desk/release/.next/static/{STVrBHA3z9KPtvBE4WcE2 → IgHJ40fniC07-ioYUKobL}/_ssgManifest.js +0 -0
@@ -0,0 +1,47 @@
1
+ # Pi Desk 文档
2
+
3
+ 这里只说明 Pi Desk 相对原生 Pi 新增或不同的行为。Pi 的配置、模型、会话格式和扩展基础沿用官方资料,不在此重复维护。
4
+
5
+ ## 按任务查阅
6
+
7
+ | 任务 | 资料 |
8
+ | --------------------------------------- | --------------------------------------------------------- |
9
+ | 让 AI 管理 Pi Desk、安装或重载插件 | [管理命令](./commands.md) |
10
+ | 开发 Pi Desk 应用、面板、通知或消息视图 | [插件开发](./plugins.md),含可直接加载的最小示例 |
11
+ | 安装、启动或升级 Pi Desk | [官网:安装与启动](https://pidesk.dev/docs/installation/) |
12
+ | 配置模型并完成第一次对话 | [官网:开始对话](https://pidesk.dev/docs/quickstart/) |
13
+ | 从手机或其他设备访问同一服务 | [官网:远程访问](https://pidesk.dev/docs/devices/) |
14
+ | 使用 Docker 部署、升级或备份 | [官网:Docker](https://pidesk.dev/docs/docker/) |
15
+ | 排查启动、模型、会话或插件问题 | [官网:常见问题](https://pidesk.dev/docs/faq/) |
16
+
17
+ 本目录及示例随当前 Pi Desk 版本分发;官网链接需要联网。已有官网说明先通过链接复用,不在本目录另存一份。
18
+
19
+ ## 需要原生 Pi 能力时
20
+
21
+ 优先读取会话系统提示所指向的已安装 Pi 文档和示例;下表路径相对那个 Pi 文档目录,不是当前工作目录或本目录。在线入口为 [Pi 官方文档](https://pi.dev/docs/latest),版本差异以实际安装版本的文档和类型为准。
22
+
23
+ | 主题 | Pi 官方资料 |
24
+ | ----------------------------- | ------------------------------------------------------------- |
25
+ | 配置作用域、设置与环境变量 | `configuration.md`、`settings.md`、`environment-variables.md` |
26
+ | 模型、服务商与自定义 Provider | `models.md`、`providers.md`、`custom-provider.md` |
27
+ | 会话历史、上下文与压缩 | `sessions.md`、`session-format.md`、`compaction.md` |
28
+ | 原生工具、命令、事件与包 | `extensions.md`、`packages.md` |
29
+ | Skills、提示模板与终端主题 | `skills.md`、`prompt-templates.md`、`themes.md` |
30
+ | MCP 与 codemode | `mcp.md`、`codemode.md` |
31
+ | 嵌入 Pi、进程控制与终端 UI | `sdk.md`、`rpc.md`、`json.md`、`tui.md` |
32
+ | 原生权限与项目信任 | `security.md` |
33
+
34
+ Pi Desk 的工作会话、界面操作和宿主管理与原生 Pi 命令不一定一一对应;这些差异归入下面的增量主题。
35
+
36
+ ## 待补充主题
37
+
38
+ 以下文件尚未创建,当前使用上面的已有资料。补充时只写右列范围,完成后加入任务导航。
39
+
40
+ | 文件 | 只补充的 Pi Desk 内容 |
41
+ | -------------------- | ---------------------------------------------------------------------------------------------- |
42
+ | `usage.md` | 工作会话、固定与并排、界面分支/Fork、消息队列、文件/Git 和终端操作;多设备共享与本地状态的区别 |
43
+ | `configuration.md` | 宿主设置、项目默认模型、能力模式与配置生效范围;哪些变更需要重载 |
44
+ | `running.md` | 主程序启动、桌面托管、服务与访问设备的区别,日志、数据位置、更新和重启影响 |
45
+ | `troubleshooting.md` | 按宿主启动、连接、模型接入、插件加载分层排查,定位日志与恢复入口 |
46
+
47
+ 插件开发继续维护在 `plugins.md`;Session、通知和消息视图示例按实际需要加入 `examples/`,不预建空示例目录。
@@ -0,0 +1,76 @@
1
+ # Pi Desk 管理命令
2
+
3
+ 本页说明宿主 `pidesk` 工具及结果语义;其他使用和开发资料见[文档入口](./README.md)。
4
+
5
+ ## 调用管理工具
6
+
7
+ `pidesk` 是宿主 API 工具,不是任意 Shell。参数是命令 token 数组,不包含 `pidesk` 前缀,也不解析管道、重定向或 Shell 表达式:
8
+
9
+ ```json
10
+ { "args": ["--help"] }
11
+ ```
12
+
13
+ 当前命令以 `pidesk --help` 为准,各级命令均支持 `--help`,例如 `{ "args": ["plugins", "install", "--help"] }`。
14
+
15
+ | 查询 | args |
16
+ | --------------------- | -------------------------------------------------------- |
17
+ | 宿主版本 | `["--version"]` |
18
+ | 当前宿主环境与目录 | `["info"]` |
19
+ | 插件清单与维护状态 | `["plugins", "list"]` |
20
+ | 按各插件渠道检查更新 | `["plugins", "list", "--check-updates"]` |
21
+ | 只检查跟随 dev 的插件 | `["plugins", "list", "--check-updates", "--tag", "dev"]` |
22
+ | 插件详情 | `["plugins", "show", "<name>", "--scope", "global"]` |
23
+
24
+ `info` 可取得当前 `agentDir`;下文 `<AgentDir>` 表示这个实际目录,不假定默认位置。管理入口只支持全局范围,不支持项目级管理。
25
+
26
+ ## 区分接纳与完成
27
+
28
+ - `mode: sync`:本次执行已经结束,直接使用返回结果。
29
+ - `mode: async`:操作已经接纳,尚未完成;宿主会把最终结果插入本会话并唤醒模型。没有独立工作时结束当前轮,等待最终通知,不轮询、不重复提交。
30
+ - 工具报错表示请求失败;已接纳、等待加载或等待重启都不能报告成已生效。
31
+ - 批量操作每批返回一条最终汇总,逐项区分成功、未接纳、失败和等待重启;某项失败不代表其余项失败。
32
+
33
+ ## 安装、更新与重载
34
+
35
+ | 操作 | args |
36
+ | ---------------------- | ----------------------------------------------------------------------------- |
37
+ | 安装、更新或重装 | `["plugins", "install", "<name>", "--scope", "global"]` |
38
+ | 批量安装或更新 | `["plugins", "install", "<name-1>", "<name-2>", "--scope", "global"]` |
39
+ | 安装并跟随 dev | `["plugins", "install", "<name>", "--scope", "global", "--tag", "dev"]` |
40
+ | 安装指定 npm 版本 | `["plugins", "install", "<name>", "--scope", "global", "--version", "1.2.3"]` |
41
+ | 重载已安装插件当前代码 | `["plugins", "reload", "<name-1>", "<name-2>", "--scope", "global"]` |
42
+ | 卸载包来源 | `["plugins", "remove", "<name-1>", "<name-2>", "--scope", "global"]` |
43
+
44
+ `install` 包含首次安装、更新和重装;目标版本已安装也会重装。`reload` 只重新加载本机现有码,不下载、不修改版本或渠道、不重启宿主,也不重载已有 Pi 会话;它不接受 `--version` 或 `--tag`。
45
+
46
+ ### 本地插件
47
+
48
+ 源码放在 `<AgentDir>/extensions/<name>/`。首次接入用 `install`;修改已安装插件后用 `reload`。有浏览器构建入口时先完成必要编译;原生 TypeScript 与 Pi Desk Node Entry 可由宿主直接加载,不必为本地验证发布 npm 或生成安装包。
49
+
50
+ 命令使用清单或固定目录中的名字,不传自定义路径;单文件扩展使用清单中的名字。本地来源不接受版本或渠道。同名来源冲突时先确认目标,不猜测。
51
+
52
+ ### npm 插件
53
+
54
+ 使用完整 npm 包名,例如 `@example/plugin`,不把版本拼在名字里。多个插件共用一次命令选择的标签或版本。
55
+
56
+ - 省略版本和标签:新插件默认 `latest`,已有插件沿保存的更新渠道。
57
+ - `--tag dev`:安装该标签指向的版本,并保存后续跟随渠道。
58
+ - `--version 1.2.3`:只指定本次完整版本,不改变已有渠道;与 `--tag` 互斥,不从版本后缀猜渠道。
59
+ - `list --check-updates` 不安装;附加 `--tag dev` 只筛选跟随 dev 的插件,不改变渠道。标签缺失或查询失败报告对应项错误,不回退 `latest`。
60
+ - 只调整渠道而不安装:在插件管理界面多选后使用“设置渠道”。
61
+
62
+ 包文件与 Pi `settings.json` 由官方包管理器维护,不手动覆盖安装目录或包声明。`remove` 只卸载管理入口支持的包来源,不附带业务数据清理;裸扩展卸载不在此命令能力中,不以删除源码或新增禁用规则代替包卸载。
63
+
64
+ ## 让已有会话采用当前配置
65
+
66
+ ```json
67
+ { "args": ["session", "reload"] }
68
+ ```
69
+
70
+ 等同输入框“更多 → 重载 Pi 配置”:保留聊天历史与当前分支,重新加载 Pi 配置、Skills 和本机已安装插件的当前代码,不下载新版本。需要 npm 新版时先 `plugins install`,再按用户需要重载目标会话。
71
+
72
+ 接纳后结束当前轮;宿主等本会话完全空闲后重载,再通知并唤醒本会话。重载可能重建插件内存状态、使缓存失效,配置和工具声明变化也可能影响提示词缓存。
73
+
74
+ 插件管理只等待目标来源必要的在途调用,不冻结常规聊天。完成通知表示包和宿主能力已应用;新建会话使用最新工具配置,已有原生 Pi 会话不自动重载。Node/Browser 替换会重新挂载目标插件界面,旧调用可能失效;结果未知的副作用请求不自动重发。
75
+
76
+ 需要重启时按插件管理界面的维护流程处理,以最终结果为准。
@@ -0,0 +1,106 @@
1
+ import { createElement as h } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import {
4
+ PluginButton,
5
+ PluginErrorBoundary,
6
+ PluginHostProvider,
7
+ PluginLoadingState,
8
+ PluginSection,
9
+ PluginSurface
10
+ } from '@jetcrab/pi-desk-sdk/react/base'
11
+
12
+ const application = {
13
+ mount({ container, host, signal }) {
14
+ const root = createRoot(container)
15
+ let disposed = false
16
+ let loading = false
17
+ let error = null
18
+ let message = null
19
+
20
+ async function readStatus() {
21
+ if (disposed || signal.aborted || loading) return
22
+ loading = true
23
+ error = null
24
+ render()
25
+
26
+ try {
27
+ const result = await host.piDesk.invokeGlobal('status-get', {})
28
+ // 视图关闭不取消服务端方法;旧视图不能使用迟到结果。
29
+ if (disposed || signal.aborted) return
30
+ if (typeof result.message !== 'string') {
31
+ throw new Error('服务信息格式不正确')
32
+ }
33
+ message = result.message
34
+ } catch (cause) {
35
+ if (disposed || signal.aborted) return
36
+ console.error('[本地应用示例] 读取服务信息失败', cause)
37
+ error = cause instanceof Error ? cause.message : String(cause)
38
+ } finally {
39
+ if (!disposed && !signal.aborted) {
40
+ loading = false
41
+ render()
42
+ }
43
+ }
44
+ }
45
+
46
+ function render() {
47
+ root.render(
48
+ h(
49
+ PluginHostProvider,
50
+ { host },
51
+ h(
52
+ PluginErrorBoundary,
53
+ null,
54
+ h(
55
+ PluginSurface,
56
+ {
57
+ style: {
58
+ width: 'min(24rem, calc(100vw - 2rem))',
59
+ maxWidth: '100%',
60
+ boxSizing: 'border-box',
61
+ padding: '1rem',
62
+ display: 'grid',
63
+ gap: '1rem',
64
+ overflowWrap: 'anywhere'
65
+ }
66
+ },
67
+ h(
68
+ PluginSection,
69
+ { title: '服务信息' },
70
+ h(
71
+ PluginButton,
72
+ {
73
+ variant: 'primary',
74
+ disabled: loading,
75
+ onClick: () => void readStatus()
76
+ },
77
+ '读取服务信息'
78
+ )
79
+ ),
80
+ h(
81
+ PluginLoadingState,
82
+ {
83
+ loading,
84
+ error,
85
+ loadingLabel: '正在读取…',
86
+ retryLabel: '重试',
87
+ onRetry: () => void readStatus()
88
+ },
89
+ message === null ? null : h('p', { role: 'status', style: { margin: 0 } }, message)
90
+ )
91
+ )
92
+ )
93
+ )
94
+ )
95
+ }
96
+
97
+ render()
98
+ return () => {
99
+ if (disposed) return
100
+ disposed = true
101
+ root.unmount()
102
+ }
103
+ }
104
+ }
105
+
106
+ export default application
@@ -0,0 +1,9 @@
1
+ export default function registerApplication(plugin) {
2
+ plugin.registerContribution('application', 'status', {
3
+ label: '本地应用示例',
4
+ title: '本地应用示例',
5
+ chrome: 'host',
6
+ icon: { type: 'builtin', name: 'plugin' },
7
+ load: () => import('./application.js').then((module) => module.default)
8
+ })
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "local-application",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "piDesk": {
7
+ "entry": "./pi-desk.ts"
8
+ }
9
+ }
@@ -0,0 +1,11 @@
1
+ import type { PiDeskPluginDefinition } from '@jetcrab/pi-desk-sdk/entry'
2
+
3
+ const definition: PiDeskPluginDefinition = {
4
+ name: 'local-application',
5
+ setup(plugin) {
6
+ plugin.registerMethod('status-get', async () => ({ message: '插件服务已连接' }))
7
+ plugin.registerBrowserEntry('./browser/entry.js')
8
+ }
9
+ }
10
+
11
+ export default definition
@@ -0,0 +1,119 @@
1
+ # Pi Desk 插件
2
+
3
+ 管理操作见[管理命令](./commands.md)。Tool、Command、Pi Event 与 TUI 使用原生 Pi API;Pi Desk SDK 只补充宿主服务、会话通信和 Web 界面。
4
+
5
+ ## 选择运行位置
6
+
7
+ | 运行位置 | 入口与能力 | 生命周期 |
8
+ | --------------- | ------------------------------------------------------------------------------ | --------------------------------------- |
9
+ | 原生 Pi | Pi 包的扩展入口;Tool、Command、Event | 当前 Pi 会话 |
10
+ | Pi Desk Global | `package.json` 的 `piDesk.entry`;全局 Method、State、Push、通知、消息声明 | 服务进程中的插件实例 |
11
+ | Pi Desk Session | 原生扩展中 `bindSessionPlugin(pi, pluginName)`;会话 Method、State、Push、Task | 当前 AgentSession,分支数据随树导航重建 |
12
+ | Browser Owner | 轻量 Browser Entry;贡献注册、Push 与通知事件 | 当前浏览器页面中的插件实例 |
13
+ | Browser View | Contribution 的 `load()` → `{ mount }` | 每次挂载,关闭或 Source 变化后释放 |
14
+
15
+ 同一个插件各入口使用同一稳定 `pluginName`;名字、方法、事件和 contributionName 使用 1~64 字符小写 kebab-case。Global 不依赖当前聊天窗口,Browser Owner 不代表后端任务 Owner。
16
+
17
+ Session 精确绑定 `{ workId, sessionId, branchId }`。Source 来自 Target 或 `host.workSessions`,不自行拼装;旧 Source 返回 `409`,分支变化后重新查询。Application 的 `target.initialContext` 只是打开时的 Source/CWD,不是内部选择的权威状态。
18
+
19
+ ## 按需读类型
20
+
21
+ 系统提示提供当前 SDK 类型目录,其上一级是 SDK 根目录;从根目录 `package.json` 的 `exports` 找对应声明,不依赖仓库源码路径。常用类型文件相对 SDK 根目录如下:
22
+
23
+ | 子入口 | 类型文件 | 使用时机 |
24
+ | --------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ |
25
+ | `/entry` | `dist/entry.d.ts` | `PiDeskPluginDefinition`、Node Host、Global Method、通知、消息声明 |
26
+ | `/session` | `dist/session.d.ts` | `bindSessionPlugin`、Session Method、State、Push、Task |
27
+ | `/browser` | `dist/browser.d.ts` | Browser Factory、Contribution 定义、mount Target、Host 通信 |
28
+ | `/react/base` | `dist/react-base.d.ts`,继续读其导出的 `react-ui.d.ts`、`react-log.d.ts` | 公共组件、Host Provider、ErrorBoundary、Hooks |
29
+ | `/react/markdown` | `dist/react-markdown.d.ts` | Markdown 消息界面 |
30
+ | `/errors`、`/settings`、`/capabilities` | 对应 `dist/*.d.ts` | 方法错误、共享地区设置、能力规则 |
31
+
32
+ SDK 不重复定义原生 Pi Tool。只导入 Node 类型可用 `import type`,宿主 Jiti 会剥除类型;实际运行时导入 SDK 或其他 Node 包时,在该插件声明运行依赖,不借用另一插件的依赖。类型检查所需 SDK 可放开发依赖。
33
+
34
+ ## 最小本地应用
35
+
36
+ 四个文件组成一个可直接加载的本地演示,不独立发布:
37
+
38
+ - [package.json](./examples/local-application/package.json):`private: true`、ES Module、`piDesk.entry`,不需要安装依赖或编译。
39
+ - [pi-desk.ts](./examples/local-application/pi-desk.ts):普通对象定义一个 Global Method 并注册 Browser Entry。
40
+ - [browser/entry.js](./examples/local-application/browser/entry.js):同步注册应用元数据与动态 loader。
41
+ - [browser/application.js](./examples/local-application/browser/application.js):公共 React/UI、人工只读查询、加载与重试、mount/dispose。
42
+
43
+ 从本文所在目录取得示例文件,复制到 `pidesk info` 返回的 `<AgentDir>/extensions/local-application/`,保留层级。调用 `{ "args": ["plugins", "install", "local-application", "--scope", "global"] }`,等待最终通知后从应用入口打开“本地应用示例”,点击“读取服务信息”。修改后使用 `plugins reload local-application --scope global` 对应的 args。
44
+
45
+ 示例只查询一个真实 Global Method,不自动调用模型、写文件、访问外部网络或轮询。它没有原生 Pi 扩展,不添加空的 Native 入口。
46
+
47
+ ## Node 与会话能力
48
+
49
+ Node Entry 默认导出普通 `PiDeskPluginDefinition` 对象即可,`definePiDeskPlugin()` 是可选包装。入口由宿主加载;异步初始化放 `async setup()`,不使用模块顶层 `await`。
50
+
51
+ `registerBrowserEntry()` 最多一次,路径位于包内,文件名为 `entry.js`。无需自有资源时 `setup()` 不必返回清理;创建 Timer、Watcher、连接或子进程时返回 disposer,释放旧实例资源。Host 自动撤销注册;旧异步清理不得写入新实例。
52
+
53
+ 会话能力在真实原生扩展中绑定,不用 Node Global 冒充 Session:
54
+
55
+ ```ts
56
+ import { bindSessionPlugin } from '@jetcrab/pi-desk-sdk/session'
57
+
58
+ const plugin = bindSessionPlugin(pi, 'example-plugin') // pi 是原生 ExtensionAPI
59
+ plugin.registerMethod('cwd-get', async (_input, context) => ({
60
+ cwd: context.extensionContext.cwd
61
+ }))
62
+ plugin.setState({ status: 'ready' })
63
+ plugin.push('changed', {})
64
+ ```
65
+
66
+ 此处 `/session` 是运行时导入,插件需声明 SDK 运行依赖。Session State 仅当前会话内存,`setState()` 整体替换自身槽位,`null` 删除;不写 Pi JSONL。树导航从当前 `getBranch()` 重建分支数据,不重复注册会话方法。Task 用于报告真实后台任务,停止能力由任务 Owner 提供。
67
+
68
+ Node `plugin.setState()` 则整体替换自身全局状态;Browser 通过 `host.globalState.getSnapshot()/subscribe()` 只读。两者不是同一槽位,均不得读写其他插件状态。宿主地区设置由 `host.settings.getSnapshot()/subscribe()` 只读,不另存权威副本。
69
+
70
+ ## Browser Entry 与懒界面
71
+
72
+ Entry 默认导出普通同步函数即可;在函数返回前完成全部 `registerContribution(kind, name, definition)`。kind 支持 `application`、`composer-panel`、`settings-page`、`session-sidebar-tab`、`message-view`。Entry 只放元数据、loader、轻量页面状态和事件订阅;返回 disposer 清理自有订阅与资源。
73
+
74
+ Entry 不静态引入 React、ReactDOM、UI、CSS 或大型实现,也不等网络或动态 import 后再注册。UI 放在 `load()` 的动态模块,导出 `{ mount({ container, target, host, signal }) }`;Lazy Module 顶层不启动副作用。
75
+
76
+ 浏览器资源只托管 Entry 目录内的 `.js` 模块图;相对 import 留在该目录内。宿主不托管独立 CSS、图片或字体。局部样式通过 Lazy JS 注入并限定自身容器,不修改全局 DOM 或依赖宿主内部目录。
77
+
78
+ Lazy UI 可直接使用的 Host Runtime 白名单:
79
+
80
+ ```text
81
+ react
82
+ react/jsx-runtime
83
+ react/jsx-dev-runtime
84
+ react-dom
85
+ react-dom/client
86
+ @jetcrab/pi-desk-sdk/react/base
87
+ @jetcrab/pi-desk-sdk/react/markdown
88
+ ```
89
+
90
+ 复用宿主 React 与 UI,不复制或打包第二套公共运行时。`/browser` 不在白名单中:普通函数无需包装;`copyBrowserText` 等 SDK Browser 运行函数需要构建时打入插件模块,不能原样留下裸 import。其他业务依赖也需构建为相对模块。无构建示例用 JS `createElement`;TSX、TypeScript 浏览器文件需要先编译为 JS。
91
+
92
+ Application 使用 `chrome: 'host'` 让宿主负责 Dialog、标题、关闭、焦点、Escape、Loading/Error 和视口约束;内容自然决定尺寸,不替换工作台。React mount 使用 `createRoot`、`PluginHostProvider`、`PluginErrorBoundary` 和公共控件;disposer 调用 `root.unmount()`,释放自建 Listener、Timer、Observer、Object URL。宿主先 abort `signal`,迟到结果必须忽略。
93
+
94
+ 界面继承宿主明暗主题与字号,布局使用 `rem`、合理内边距和自身滚动;窄屏与触控可用,不以裁切或只靠 hover 隐藏必要操作。按钮、表单、加载/错误/重试优先复用 `/react/base`;需要 Markdown 时才加载 `/react/markdown`。
95
+
96
+ ## 通信、通知与后台
97
+
98
+ Browser Method 示例:
99
+
100
+ ```ts
101
+ const status = await host.piDesk.invokeGlobal('status-get', {})
102
+ const cwd = await host.piDesk.invokeSession(source, 'cwd-get', {})
103
+ await host.pi.prompt(source, { mode: 'auto', text: '请总结当前任务' })
104
+ ```
105
+
106
+ 这些调用限定当前插件或完整 Source。Method 输入/输出为 JSON Object,各不超过 2MB;Push 与状态槽位各不超过 64KB。`host.pi.prompt()` 返回接纳用的 `tempId`,不是模型完成或 durable 消息;不按正文、时间或位置猜测是否发送成功,结果未知不自动重播。Browser 不能直接调用 Pi Tool;通过 `host.pi` 使用原生 Prompt、Command、Bash、Interrupt 能力,不直接 fetch 宿主接口或自建 WebSocket。
107
+
108
+ Method 调用没有 Browser 视图级取消参数;Global handler 的 `context.signal` 是 Node Owner 失效信号。关闭 UI 只释放展示,不停止后台任务。需要停止时调用真实 Owner 的明确停止方法;断线、超时和关闭不回滚副作用。
109
+
110
+ Push 可丢失、不持久化、不 ACK、不重放,也不创建聊天消息。Entry ready、连接恢复 ready、视图 mount 时按需要查询 Method 建立权威基线;不要用页面轮询替代已有的宿主状态同步。
111
+
112
+ - 当前页面轻提示:`plugin.notify({ level: 'success', title: '保存成功' })` 或 `host.notify()`,只在当前页面短暂展示,离线可错过。
113
+ - 服务端全局通知:Node `plugin.notifications.publish({ level: 'success', title: '处理完成', event: { name: 'open-result', data: {} } })`;用返回 ID 调用 `update/delete`。Browser Entry 通过 `plugin.notifications.onEvent('open-result', handler)` 处理点击,成功后宿主删除通知。Node 未重启时重连可恢复;Node 重启或插件释放后清空,不持久化。
114
+
115
+ 消息适配需要时读 `/entry` 的 `declareMessage` 与 `/browser` 的 Message View 类型:Node 投影原始 Pi 消息,Browser 按最终 `viewKey + priority` 选择 View。首屏必需数据放 Summary,大内容按 Detail 获取;View 失败只影响相应消息,不自行解析 Pi 原始类型或切换到另一个低优先级 View。
116
+
117
+ ## 更新生效
118
+
119
+ `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.1",
3
+ "version": "1.1.5-dev",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "bin",
22
+ "docs/pi-desk",
22
23
  "next.config.ts",
23
24
  "package.json",
24
25
  "src",
@@ -110,7 +111,7 @@
110
111
  "build:plugin-host-runtime": "pnpm --filter @jetcrab/pi-desk-sdk build",
111
112
  "start": "node bin/pi-desk.js --no-open",
112
113
  "test:package": "node --test tests/l4-pi-desk-package.test.mjs",
113
- "test:pidesk": "node --import tsx --test 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-installed-runners.test.mjs tests/l4-pidesk-package-maintenance.test.ts tests/l4-pidesk-release-gate.test.mjs tests/l4-pi-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs",
114
+ "test:pidesk": "node --import tsx --test 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-global-runtime.test.mjs tests/l4-pi-desk-supervisor-cli.test.mjs",
114
115
  "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",
115
116
  "lint": "eslint .",
116
117
  "lint:production": "eslint . --ignore-pattern \"apps/desktop/**\" --ignore-pattern \"**/tests/**\" --ignore-pattern \"**/__tests__/**\" --ignore-pattern \"**/*.test.*\" --ignore-pattern \"**/*.spec.*\"",
@@ -40,6 +40,8 @@ function errorMessage(cause: unknown): string {
40
40
 
41
41
  export interface L2PiSettingsController {
42
42
  cwd: string | null
43
+ displayCwd: string | null
44
+ ready: boolean
43
45
  mcp: L2McpSettings | null
44
46
  editor: L2McpEditor | null
45
47
  setEditor: Dispatch<SetStateAction<L2McpEditor | null>>
@@ -54,7 +56,7 @@ export interface L2PiSettingsController {
54
56
  dialog: React.JSX.Element | null
55
57
  retry(): void
56
58
  navigate(action: () => void): Promise<void>
57
- setCwd(cwd: string | null): void
59
+ setCwd(cwd: string | null): Promise<void>
58
60
  startManual(name: string | null, config?: L2McpServerConfig): void
59
61
  startInherited(name: string, config: L2McpServerConfig): void
60
62
  updateDraft(patch: Partial<L2McpDraft>): void
@@ -74,7 +76,9 @@ export function useL2PiSettings(
74
76
  const biz = useMemo(() => createL2PiSettingsBiz(clientId), [clientId])
75
77
  const { confirm, dialog } = useL4ConfirmDialog()
76
78
  const [cwd, setCwdState] = useState<string | null>(null)
77
- const [mcp, setMcp] = useState<L2McpSettings | null>(null)
79
+ const [snapshot, setSnapshot] = useState<{ cwd: string | null; data: L2McpSettings } | null>(null)
80
+ const mcp = snapshot?.data ?? null
81
+ const displayCwd = snapshot ? snapshot.cwd : cwd
78
82
  const [editor, setEditor] = useState<L2McpEditor | null>(null)
79
83
  const [loading, setLoading] = useState(true)
80
84
  const [pending, setPending] = useState(false)
@@ -96,9 +100,11 @@ export function useL2PiSettings(
96
100
  confirmRef.current = confirm
97
101
  textRef.current = t
98
102
  }, [confirm, t])
103
+ const ready = snapshot !== null && snapshot.cwd === cwd && !loading && !loadError
99
104
  const dirty =
100
- (editor?.kind === 'manual' && JSON.stringify(editor.draft) !== editor.initial) ||
101
- (editor?.kind === 'json' && Boolean(editor.text))
105
+ ready &&
106
+ ((editor?.kind === 'manual' && JSON.stringify(editor.draft) !== editor.initial) ||
107
+ (editor?.kind === 'json' && Boolean(editor.text)))
102
108
 
103
109
  const discard = useCallback((): void => {
104
110
  setEditor(null)
@@ -113,9 +119,8 @@ export function useL2PiSettings(
113
119
  description: textRef.current('当前修改尚未保存。放弃后将保留已保存的配置。'),
114
120
  confirmLabel: textRef.current('放弃修改')
115
121
  })
116
- if (leave) discard()
117
122
  return leave
118
- }, [dirty, discard, pending, check?.loading])
123
+ }, [dirty, pending, check?.loading])
119
124
 
120
125
  useEffect(() => {
121
126
  onBeforeLeaveChange(beforeLeave)
@@ -123,14 +128,21 @@ export function useL2PiSettings(
123
128
  }, [beforeLeave, onBeforeLeaveChange])
124
129
 
125
130
  const load = useCallback(
126
- async (signal: AbortSignal, currentEpoch: number): Promise<void> => {
131
+ async (signal: AbortSignal, currentEpoch: number, resetEditor = false): Promise<boolean> => {
127
132
  setLoading(true)
128
133
  setLoadError(null)
129
134
  try {
130
135
  const result = await biz.getMcp(cwd, signal)
131
- if (!signal.aborted && epoch.current === currentEpoch) setMcp(result)
136
+ if (signal.aborted || epoch.current !== currentEpoch) return false
137
+ setSnapshot({ cwd, data: result })
138
+ if (resetEditor) setEditor(null)
139
+ return true
132
140
  } catch (cause) {
133
- if (!signal.aborted && epoch.current === currentEpoch) setLoadError(errorMessage(cause))
141
+ if (!signal.aborted && epoch.current === currentEpoch) {
142
+ console.warn('[Pi Desk][MCP] 读取配置失败', { cwd, message: errorMessage(cause) })
143
+ setLoadError(errorMessage(cause))
144
+ }
145
+ return false
134
146
  } finally {
135
147
  if (!signal.aborted && epoch.current === currentEpoch) setLoading(false)
136
148
  }
@@ -142,12 +154,13 @@ export function useL2PiSettings(
142
154
  const current = new AbortController()
143
155
  controller.current = current
144
156
  const currentEpoch = ++epoch.current
145
- void load(current.signal, currentEpoch)
157
+ void load(current.signal, currentEpoch, true)
146
158
  return () => current.abort()
147
159
  }, [load])
148
160
 
149
161
  const retry = (): void => {
150
- if (controller.current) void load(controller.current.signal, epoch.current)
162
+ if (controller.current)
163
+ void load(controller.current.signal, epoch.current, snapshot?.cwd !== cwd)
151
164
  }
152
165
 
153
166
  const navigate = async (action: () => void): Promise<void> => {
@@ -158,8 +171,11 @@ export function useL2PiSettings(
158
171
  }
159
172
  }
160
173
 
161
- const setCwd = (next: string | null): void => {
162
- setMcp(null)
174
+ const setCwd = async (next: string | null): Promise<void> => {
175
+ if (next === cwd || !(await beforeLeave())) return
176
+ controller.current?.abort()
177
+ setError(null)
178
+ setNotice(null)
163
179
  setCheck(null)
164
180
  setLoadError(null)
165
181
  setLoading(true)
@@ -209,7 +225,7 @@ export function useL2PiSettings(
209
225
  onSaved: () => void
210
226
  ): Promise<boolean> => {
211
227
  const signal = controller.current?.signal
212
- if (!signal || pending || check?.loading) return false
228
+ if (!signal || !ready || pending || check?.loading) return false
213
229
  const currentEpoch = epoch.current
214
230
  setPending(true)
215
231
  setError(null)
@@ -220,10 +236,8 @@ export function useL2PiSettings(
220
236
  onSaved()
221
237
  setCheck(null)
222
238
  setNotice('已保存。新会话使用新配置;已有会话需重载 Pi 配置。')
223
- // 写入已经成功;重新读取失败时禁止继续以旧列表覆盖配置。
224
- setMcp(null)
225
- await load(signal, currentEpoch)
226
- return !signal.aborted && epoch.current === currentEpoch
239
+ // 保留展示快照;重新读取完成前由 ready 阻止旧配置参与下一次写入。
240
+ return await load(signal, currentEpoch)
227
241
  } catch (cause) {
228
242
  if (!signal.aborted && epoch.current === currentEpoch)
229
243
  setError({
@@ -237,7 +251,7 @@ export function useL2PiSettings(
237
251
  }
238
252
 
239
253
  const saveEditor = async (testConnection = false): Promise<void> => {
240
- if (!editor || !mcp || pending || check?.loading) return
254
+ if (!editor || !mcp || !ready || pending || check?.loading) return
241
255
  if (
242
256
  testConnection &&
243
257
  (editor.kind !== 'manual' || !editor.draft.enabled || mcp.projectTrusted === false)
@@ -288,7 +302,7 @@ export function useL2PiSettings(
288
302
  }
289
303
 
290
304
  const remove = async (name: string): Promise<void> => {
291
- if (!mcp || !Object.hasOwn(mcp.local, name)) return
305
+ if (!mcp || !ready || pending || !Object.hasOwn(mcp.local, name)) return
292
306
  const restore = cwd !== null && Object.hasOwn(mcp.inherited, name)
293
307
  if (
294
308
  !(await confirmRef.current({
@@ -333,7 +347,7 @@ export function useL2PiSettings(
333
347
  config: shorthand ? { ...inherited, ...local } : (local ?? inherited),
334
348
  local: local ?? null,
335
349
  source:
336
- cwd === null
350
+ displayCwd === null
337
351
  ? '全局'
338
352
  : local
339
353
  ? shorthand
@@ -350,6 +364,8 @@ export function useL2PiSettings(
350
364
 
351
365
  return {
352
366
  cwd,
367
+ displayCwd,
368
+ ready,
353
369
  mcp,
354
370
  editor,
355
371
  setEditor,