@co0ontty/wand 4.98.1-beta.367.1.g2d092b1 → 4.100.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 (260) hide show
  1. package/README.md +2 -0
  2. package/dist/ai-team-runner.js +1 -1
  3. package/dist/ai-team-types.d.ts +1 -1
  4. package/dist/ai-team-types.js +1 -1
  5. package/dist/build-info.json +4 -4
  6. package/dist/conversation-service.js +2 -2
  7. package/dist/daemon-maintenance.js +2 -0
  8. package/dist/employee-input-scheduling.d.ts +17 -0
  9. package/dist/employee-input-scheduling.js +15 -0
  10. package/dist/employee-types.d.ts +4 -0
  11. package/dist/employee-types.js +2 -0
  12. package/dist/execution-state.d.ts +8 -0
  13. package/dist/execution-state.js +18 -0
  14. package/dist/ios-ota.js +12 -2
  15. package/dist/middleware/api-scope.js +1 -0
  16. package/dist/models.js +7 -0
  17. package/dist/pi-auto-resources.js +54 -28
  18. package/dist/pi-resource-recommendation.d.ts +8 -0
  19. package/dist/pi-resource-recommendation.js +28 -1
  20. package/dist/pi-session-settings.d.ts +9 -0
  21. package/dist/pi-session-settings.js +42 -0
  22. package/dist/pi-tmux-host.d.ts +3 -2
  23. package/dist/pi-tmux-host.js +62 -25
  24. package/dist/process-manager.d.ts +1 -0
  25. package/dist/process-manager.js +64 -4
  26. package/dist/pty-tmux-backend.d.ts +28 -0
  27. package/dist/pty-tmux-backend.js +287 -0
  28. package/dist/pty-tmux-log.d.ts +2 -0
  29. package/dist/pty-tmux-log.js +27 -0
  30. package/dist/render-daemon-client.js +3 -0
  31. package/dist/render-host.d.ts +1 -4
  32. package/dist/render-host.js +3 -21
  33. package/dist/server-asset-routes.d.ts +4 -0
  34. package/dist/server-asset-routes.js +12 -3
  35. package/dist/server-employee-routes.d.ts +5 -0
  36. package/dist/server-employee-routes.js +18 -3
  37. package/dist/server-pi-defaults-routes.d.ts +23 -0
  38. package/dist/server-pi-defaults-routes.js +73 -0
  39. package/dist/server-session-input-routes.js +2 -1
  40. package/dist/server-session-lifecycle-routes.js +1 -1
  41. package/dist/server-session-routes.js +3 -0
  42. package/dist/server-task-routes.d.ts +4 -1
  43. package/dist/server-task-routes.js +25 -8
  44. package/dist/server-update-routes.js +3 -3
  45. package/dist/server-workspace-routes.js +9 -1
  46. package/dist/server.js +45 -6
  47. package/dist/session-transport.d.ts +3 -0
  48. package/dist/session-transport.js +3 -0
  49. package/dist/storage/employees-repository.js +19 -6
  50. package/dist/storage/internal.js +2 -1
  51. package/dist/storage/mappers.d.ts +1 -1
  52. package/dist/storage/mappers.js +8 -0
  53. package/dist/storage/migrations.js +38 -0
  54. package/dist/storage/tasks-repository.d.ts +65 -2
  55. package/dist/storage/tasks-repository.js +283 -15
  56. package/dist/storage/workspaces-repository.js +2 -1
  57. package/dist/structured-claude-adapter.js +29 -17
  58. package/dist/structured-claude-protocol.d.ts +13 -0
  59. package/dist/structured-claude-protocol.js +33 -2
  60. package/dist/structured-codex-adapter.js +7 -0
  61. package/dist/structured-exec-host.d.ts +10 -1
  62. package/dist/structured-exec-pump.js +17 -7
  63. package/dist/structured-failure.d.ts +38 -6
  64. package/dist/structured-failure.js +55 -10
  65. package/dist/structured-grok-adapter.js +6 -0
  66. package/dist/structured-host-maintenance.d.ts +4 -0
  67. package/dist/structured-host-maintenance.js +25 -0
  68. package/dist/structured-opencode-adapter.js +6 -0
  69. package/dist/structured-pi-adapter.js +29 -10
  70. package/dist/structured-qoder-adapter.js +30 -15
  71. package/dist/structured-session-manager.d.ts +20 -1
  72. package/dist/structured-session-manager.js +165 -46
  73. package/dist/structured-tmux-host.d.ts +51 -0
  74. package/dist/structured-tmux-host.js +576 -0
  75. package/dist/structured-tmux-supervisor.d.ts +1 -0
  76. package/dist/structured-tmux-supervisor.js +109 -0
  77. package/dist/structured-tmux-worker.d.ts +1 -0
  78. package/dist/structured-tmux-worker.js +144 -0
  79. package/dist/task-comment-dispatch.d.ts +5 -0
  80. package/dist/task-comment-dispatch.js +401 -11
  81. package/dist/task-comment-references.d.ts +50 -0
  82. package/dist/task-comment-references.js +108 -0
  83. package/dist/task-comment-status.d.ts +13 -0
  84. package/dist/task-comment-status.js +25 -0
  85. package/dist/task-execution-queue.d.ts +65 -0
  86. package/dist/task-execution-queue.js +288 -0
  87. package/dist/task-read-model.d.ts +6 -0
  88. package/dist/task-read-model.js +8 -0
  89. package/dist/task-title-organize.d.ts +2 -1
  90. package/dist/task-types.d.ts +37 -2
  91. package/dist/task-types.js +8 -0
  92. package/dist/terminal-daemon-build.js +1 -1
  93. package/dist/terminal-daemon-client.d.ts +3 -1
  94. package/dist/terminal-daemon-client.js +4 -2
  95. package/dist/terminal-daemon-server.js +65 -24
  96. package/dist/tmux-policy.d.ts +2 -0
  97. package/dist/tmux-policy.js +10 -0
  98. package/dist/tmux-terminal-host.d.ts +10 -0
  99. package/dist/tmux-terminal-host.js +74 -0
  100. package/dist/types.d.ts +2 -1
  101. package/dist/web-ui/avatar-hardware.d.ts +28 -0
  102. package/dist/web-ui/avatar-hardware.js +60 -0
  103. package/dist/web-ui/chat-history-window.d.ts +1 -2
  104. package/dist/web-ui/chat-history-window.js +2 -4
  105. package/dist/web-ui/content/ai-teams.js +13 -13
  106. package/dist/web-ui/content/licenses/lucide.txt +43 -0
  107. package/dist/web-ui/content/login.js +131 -0
  108. package/dist/web-ui/content/module-manifest.json +1 -1
  109. package/dist/web-ui/content/modules/007197d061c2a565/chunk.js +57 -0
  110. package/dist/web-ui/content/modules/{8cb5d84a2731789e → 0128a9a2c361da59}/chunk.js +1 -1
  111. package/dist/web-ui/content/modules/0206d2425a3eb375/chunk.js +1 -0
  112. package/dist/web-ui/content/modules/0261c50fee52b437/host.js +1 -0
  113. package/dist/web-ui/content/modules/0500b57b86317246/host.js +1 -0
  114. package/dist/web-ui/content/modules/05889845b99c95dd/host.js +1 -0
  115. package/dist/web-ui/content/modules/07b7b195f2e59a16/chunk.js +1 -0
  116. package/dist/web-ui/content/modules/0a04baa27e4fc9e3/chunk.js +3 -0
  117. package/dist/web-ui/content/modules/0b00a661913662d4/chunk.js +1 -0
  118. package/dist/web-ui/content/modules/0bd3cebfdbc60d19/queue-view-adapter.js +3 -0
  119. package/dist/web-ui/content/modules/0cb64ca968a6d25d/chunk.js +6 -0
  120. package/dist/web-ui/content/modules/19a7b932484a64ca/host-bridge.js +1 -0
  121. package/dist/web-ui/content/modules/19d3bc36fa1e9160/chunk.js +1 -0
  122. package/dist/web-ui/content/modules/1f88294162059296/home.js +6 -0
  123. package/dist/web-ui/content/modules/{0bf4a78c8be1bdc9 → 23178d02e631d065}/host.js +3 -3
  124. package/dist/web-ui/content/modules/{d673a76becc7d1f5 → 23eea6861975478b}/chunk.js +2 -2
  125. package/dist/web-ui/content/modules/2409b362681d91d6/chunk.js +3 -0
  126. package/dist/web-ui/content/modules/2575e08b75b1a192/chunk.js +1 -0
  127. package/dist/web-ui/content/modules/258f0487e8621dcc/chunk.js +10 -0
  128. package/dist/web-ui/content/modules/26b6ff004618750f/chunk.js +1 -0
  129. package/dist/web-ui/content/modules/280457daf6b6fe61/chunk.js +20 -0
  130. package/dist/web-ui/content/modules/2837772e09c2b62d/chunk.js +4 -0
  131. package/dist/web-ui/content/modules/28795c8bb7f4e2e8/chunk.js +3 -0
  132. package/dist/web-ui/content/modules/29c912ef59696172/panel.js +1 -0
  133. package/dist/web-ui/content/modules/2aa0bc0eba53a5c9/chunk.js +15 -0
  134. package/dist/web-ui/content/modules/2be38bfbed614f65/host.js +1 -0
  135. package/dist/web-ui/content/modules/360db1263b60f857/composer-surface-adapter.js +1 -0
  136. package/dist/web-ui/content/modules/364b10aceecf7ab9/panel-host.js +1 -0
  137. package/dist/web-ui/content/modules/371e120f3773f783/employee-card.js +1 -0
  138. package/dist/web-ui/content/modules/37767c3c41b8d8a8/host.js +69 -0
  139. package/dist/web-ui/content/modules/3b975071451386a4/chunk.js +1 -0
  140. package/dist/web-ui/content/modules/3d050c621f552e20/chunk.js +1 -0
  141. package/dist/web-ui/content/modules/3d0ec18974ad2b11/chunk.js +12 -0
  142. package/dist/web-ui/content/modules/3d1f076ddfc09771/chunk.js +1 -0
  143. package/dist/web-ui/content/modules/405a98ab11edb2c1/runtime.js +275 -0
  144. package/dist/web-ui/content/modules/45e8d613816cdec5/chunk.js +3 -0
  145. package/dist/web-ui/content/modules/4718bec8ce29956e/chunk.js +1 -0
  146. package/dist/web-ui/content/modules/48b00eccb3067884/chunk.js +2 -0
  147. package/dist/web-ui/content/modules/{17f72e0fb014a302 → 4ca8f7b400b83127}/chat-render.js +3 -3
  148. package/dist/web-ui/content/modules/4dd52ad71d002eab/chunk.js +1 -0
  149. package/dist/web-ui/content/modules/51fc769946593cfa/chunk.js +1 -0
  150. package/dist/web-ui/content/modules/5444c4f9260fcb2f/toast-region.js +1 -0
  151. package/dist/web-ui/content/modules/5cd5d3d65d966c5f/chunk.js +1 -0
  152. package/dist/web-ui/content/modules/5d4915f6e1661d23/chunk.js +29 -0
  153. package/dist/web-ui/content/modules/5e50a3c3c546d824/chunk.js +1 -0
  154. package/dist/web-ui/content/modules/5f4ea7ad706b19f7/progress.js +1 -0
  155. package/dist/web-ui/content/modules/621b2383696b9d7d/host.js +1 -0
  156. package/dist/web-ui/content/modules/65e98a2bcd16cd79/chunk.js +1 -0
  157. package/dist/web-ui/content/modules/71205ec0b1cf554d/host.js +1 -0
  158. package/dist/web-ui/content/modules/73a96d56bc1484de/host.js +1 -0
  159. package/dist/web-ui/content/modules/76b953fe1f34c421/dropdown-surface.js +1 -0
  160. package/dist/web-ui/content/modules/784eef1d52221e7d/chunk.js +1 -0
  161. package/dist/web-ui/content/modules/7aab1455fc7348e1/chunk.js +1 -0
  162. package/dist/web-ui/content/modules/82f9fed7fd7ffd9f/chunk.js +1 -0
  163. package/dist/web-ui/content/modules/8492b5592532e563/chunk.js +1 -0
  164. package/dist/web-ui/content/modules/8676143ae232d8b6/chunk.js +1 -0
  165. package/dist/web-ui/content/modules/890cdc256b49e26c/host.js +1 -0
  166. package/dist/web-ui/content/modules/8c4258e7c35e9eca/chunk.js +1 -0
  167. package/dist/web-ui/content/modules/91f4ec7a3631129c/chunk.js +13 -0
  168. package/dist/web-ui/content/modules/{818d87c1651fc6a1 → 93b428761a02ca66}/chunk.js +2 -2
  169. package/dist/web-ui/content/modules/950a2f28bc128f94/dialog-modal.js +4 -0
  170. package/dist/web-ui/content/modules/9638c7ba97db8755/notice-view.js +1 -0
  171. package/dist/web-ui/content/modules/96f4a041d5cf046b/chunk.js +1 -0
  172. package/dist/web-ui/content/modules/97faa837aeaaff64/workspace-window.js +1 -0
  173. package/dist/web-ui/content/modules/9a0704236531e38e/chunk.js +1 -0
  174. package/dist/web-ui/content/modules/9a12d5f9e5fef729/workspaces-panel.js +4 -0
  175. package/dist/web-ui/content/modules/9a4f713719354002/host.js +1 -0
  176. package/dist/web-ui/content/modules/9d7b92a76b2d54ab/sidebar-peek.js +1 -0
  177. package/dist/web-ui/content/modules/a04036215e751a32/chunk.js +1 -0
  178. package/dist/web-ui/content/modules/{77ea205020273767 → a120182be2ef9632}/chunk.js +1 -1
  179. package/dist/web-ui/content/modules/a1e9344b3fbd11ae/chunk.js +1 -0
  180. package/dist/web-ui/content/modules/{0afa04591224bc23 → a2263c03ce67440a}/library-bridge.js +3 -3
  181. package/dist/web-ui/content/modules/a48f540d6a228012/chunk.js +4 -0
  182. package/dist/web-ui/content/modules/{eb46bf62b1ae1bea → a763fe780816708f}/host.js +2 -2
  183. package/dist/web-ui/content/modules/a9114b269e25b3f5/chunk.js +1 -0
  184. package/dist/web-ui/content/modules/a919e64032fe3acc/chunk.js +6 -0
  185. package/dist/web-ui/content/modules/abe3642ec4dba601/chunk.js +1 -0
  186. package/dist/web-ui/content/modules/ad896e0a52316ead/chunk.js +1 -0
  187. package/dist/web-ui/content/modules/b0b078bcfc9085a6/chunk.js +3 -0
  188. package/dist/web-ui/content/modules/b0d4d1f8ff0fae38/chunk.js +1 -0
  189. package/dist/web-ui/content/modules/b3c8d461cf58ea32/host.js +30 -0
  190. package/dist/web-ui/content/modules/b9445d806edca559/sha256.js +1 -0
  191. package/dist/web-ui/content/modules/b9b78d6b75945e66/employee-profile.js +1 -0
  192. package/dist/web-ui/content/modules/bbe7c6f6eda87002/task-board-host.js +22 -0
  193. package/dist/web-ui/content/modules/bc56ae7841b21ca6/chunk.js +9 -0
  194. package/dist/web-ui/content/modules/{7fb02c44fd6f877b → c2e0b78e4a6b1a18}/host.js +2 -2
  195. package/dist/web-ui/content/modules/c4705a6af423d824/chunk.js +10 -0
  196. package/dist/web-ui/content/modules/c8ee697d1dd6e302/group-editor.js +1 -0
  197. package/dist/web-ui/content/modules/ca37cc312813dd4f/chunk.js +1 -0
  198. package/dist/web-ui/content/modules/cdb1909af2179a41/chunk.js +1 -0
  199. package/dist/web-ui/content/modules/d0a0f9214f3bbf71/chunk.js +3 -0
  200. package/dist/web-ui/content/modules/d1185edafb2a3fc6/shell-file-panel-drawer.js +1 -0
  201. package/dist/web-ui/content/modules/d948ac122dc34acc/markdown.js +1 -0
  202. package/dist/web-ui/content/modules/da8314e267c164ed/host.js +1 -0
  203. package/dist/web-ui/content/modules/ddcaaa62f1b7f049/chunk.js +1 -0
  204. package/dist/web-ui/content/modules/dfb9876d670c5365/chunk.js +1 -0
  205. package/dist/web-ui/content/modules/{183db4802a7d99ed → e2263673e53a298c}/host.js +2 -2
  206. package/dist/web-ui/content/modules/e7780cd2afeb29d9/chunk.js +1 -0
  207. package/dist/web-ui/content/modules/{62028b9bbf8ffc88 → e8f9f6d46ef4bb53}/page.js +3 -3
  208. package/dist/web-ui/content/modules/ea7cd328cb5cf7e0/chunk.js +1 -0
  209. package/dist/web-ui/content/modules/edb31a89f667eddf/brand-identity.js +1 -0
  210. package/dist/web-ui/content/modules/f248ee3b31df4034/chunk.js +1 -0
  211. package/dist/web-ui/content/modules/f41d42a01dc09373/login-visual.js +101 -0
  212. package/dist/web-ui/content/modules/f555ceb3720822c5/chunk.js +1 -0
  213. package/dist/web-ui/content/modules/f9111c80a2c26147/host.js +1 -0
  214. package/dist/web-ui/content/modules/fd2a62ffb5f81f09/chunk.js +1 -0
  215. package/dist/web-ui/content/styles.css +1 -1
  216. package/dist/web-ui/embedded-assets.d.ts +418 -176
  217. package/dist/web-ui/embedded-assets.js +6 -5
  218. package/dist/web-ui/index.d.ts +1 -1
  219. package/dist/web-ui/index.js +13 -6
  220. package/dist/web-ui/provider-identity.d.ts +11 -0
  221. package/dist/web-ui/provider-identity.js +7 -1
  222. package/dist/web-ui/query-snapshot.d.ts +33 -0
  223. package/dist/web-ui/query-snapshot.js +77 -0
  224. package/dist/web-ui/react/conversations/presentation.d.ts +62 -0
  225. package/dist/web-ui/react/conversations/presentation.js +99 -0
  226. package/dist/web-ui/react/workspaces/types.d.ts +4 -0
  227. package/dist/web-ui/running-current-action.js +2 -2
  228. package/dist/web-ui/scripts.d.ts +4 -0
  229. package/dist/web-ui/scripts.js +17 -0
  230. package/dist/web-ui/startup-preload.d.ts +10 -0
  231. package/dist/web-ui/startup-preload.js +24 -0
  232. package/package.json +3 -1
  233. package/dist/web-ui/content/modules/02b1ec89c69dc0dd/host.js +0 -30
  234. package/dist/web-ui/content/modules/0434a45abd2c1d15/chunk.js +0 -1
  235. package/dist/web-ui/content/modules/23d2006fcfcf3c0c/host-bridge.js +0 -1
  236. package/dist/web-ui/content/modules/32f151da1a006041/host.js +0 -1
  237. package/dist/web-ui/content/modules/34a7674efc329383/chunk.js +0 -1
  238. package/dist/web-ui/content/modules/3541ba4cb2886e29/chunk.js +0 -1
  239. package/dist/web-ui/content/modules/395257bd20cd0098/chunk.js +0 -1
  240. package/dist/web-ui/content/modules/3fd75af0a6891549/chunk.js +0 -1
  241. package/dist/web-ui/content/modules/428352e422932c40/host.js +0 -1
  242. package/dist/web-ui/content/modules/485a72b2c83e7680/workspace-window.js +0 -1
  243. package/dist/web-ui/content/modules/489dfe1f7441ef2c/markdown.js +0 -1
  244. package/dist/web-ui/content/modules/4b42b4336458b7b5/runtime.js +0 -550
  245. package/dist/web-ui/content/modules/528c5d97e8f9d582/chunk.js +0 -1
  246. package/dist/web-ui/content/modules/535aad05d34079d2/host.js +0 -1
  247. package/dist/web-ui/content/modules/6868f75a5981ad08/host.js +0 -69
  248. package/dist/web-ui/content/modules/6c72c8c166e34de7/host.js +0 -1
  249. package/dist/web-ui/content/modules/6ff6ce370ef26753/host.js +0 -1
  250. package/dist/web-ui/content/modules/72bef6889fdafefe/host.js +0 -1
  251. package/dist/web-ui/content/modules/763c79f5527d762a/panel.js +0 -1
  252. package/dist/web-ui/content/modules/96e78c7f2cd9acd5/chunk.js +0 -20
  253. package/dist/web-ui/content/modules/a60a07ba1abcdc3f/chunk.js +0 -3
  254. package/dist/web-ui/content/modules/b4bcb9bf5cfb0b3a/host.js +0 -1
  255. package/dist/web-ui/content/modules/b71cda413318d71f/employee-card.js +0 -1
  256. package/dist/web-ui/content/modules/bb0c299cf006e157/task-board-host.js +0 -17
  257. package/dist/web-ui/content/modules/c8c6adee81d4bd98/sha256.js +0 -1
  258. package/dist/web-ui/content/modules/cd0cec0ba1887ffd/chunk.js +0 -1
  259. package/dist/web-ui/content/modules/d99ea7e921488348/chunk.js +0 -1
  260. package/dist/web-ui/content/modules/e862ccabc908eb0c/host.js +0 -1
package/README.md CHANGED
@@ -15,6 +15,8 @@ Wand is a web console for remotely accessing and managing local CLI tools from a
15
15
 
16
16
  The browser password manager extension source lives in `browser-extension/`.
17
17
 
18
+ See [tmux execution and recovery](docs/tmux-execution.md) for CLI ownership, execution switches, and safe restart behavior.
19
+
18
20
  ### Installation
19
21
 
20
22
  Choose one installation method:
@@ -355,7 +355,7 @@ export class AiTeamRunner {
355
355
  this.openChat(run, task, shown, chatHistory);
356
356
  this.storage.saveAiTeamRun(run);
357
357
  input.onAccepted?.(run);
358
- if (task.status === "todo")
358
+ if (task.status === "todo" || task.status === "backlog")
359
359
  this.storage.updateWandTask(task.id, { status: "doing" });
360
360
  excludeAiTeamReportDir(cwd);
361
361
  await this.enqueue(run.id, async () => {
@@ -3,7 +3,7 @@ import type { AiTeamDeliverySummary } from "./ai-team-delivery-types.js";
3
3
  import type { WandTaskAgent } from "./task-types.js";
4
4
  import type { ConversationTurn } from "./types.js";
5
5
  export type { SiliconEmployee, SiliconEmployeeDraft } from "./employee-types.js";
6
- export { DEFAULT_EMPLOYEE_ID, DEFAULT_EMPLOYEE_KEY, DEFAULT_EMPLOYEE_NAME, DEFAULT_EMPLOYEE_TAG, DECISION_EXPERT_KEY, HR_EMPLOYEE_KEY, isBuiltinSiliconEmployee, isDecisionExpertSiliconEmployee, isDefaultSiliconEmployee, isSystemSiliconEmployee, parseSiliconEmployeeTagInput, parseSiliconEmployeeTags, SILICON_EMPLOYEE_MAX_TAGS, SILICON_EMPLOYEE_TAG_MAX_CHARS, siliconEmployeeTags, SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_EMPLOYEE_TAG, } from "./employee-types.js";
6
+ export { DEFAULT_EMPLOYEE_ID, DEFAULT_EMPLOYEE_KEY, DEFAULT_EMPLOYEE_NAME, DEFAULT_EMPLOYEE_TAG, DEFAULT_MAX_CONCURRENT_TASKS, DECISION_EXPERT_KEY, HR_EMPLOYEE_KEY, isBuiltinSiliconEmployee, isDecisionExpertSiliconEmployee, isDefaultSiliconEmployee, isSystemSiliconEmployee, parseSiliconEmployeeTagInput, parseSiliconEmployeeTags, SILICON_EMPLOYEE_MAX_TAGS, SILICON_EMPLOYEE_TAG_MAX_CHARS, siliconEmployeeTags, SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_EMPLOYEE_TAG, } from "./employee-types.js";
7
7
  export declare const AI_TEAM_MIN_MEMBERS = 2;
8
8
  export declare const AI_TEAM_MAX_MEMBERS = 8;
9
9
  export declare const AI_TEAM_DEFAULT_MAX_STEPS = 30;
@@ -1,4 +1,4 @@
1
- export { DEFAULT_EMPLOYEE_ID, DEFAULT_EMPLOYEE_KEY, DEFAULT_EMPLOYEE_NAME, DEFAULT_EMPLOYEE_TAG, DECISION_EXPERT_KEY, HR_EMPLOYEE_KEY, isBuiltinSiliconEmployee, isDecisionExpertSiliconEmployee, isDefaultSiliconEmployee, isSystemSiliconEmployee, parseSiliconEmployeeTagInput, parseSiliconEmployeeTags, SILICON_EMPLOYEE_MAX_TAGS, SILICON_EMPLOYEE_TAG_MAX_CHARS, siliconEmployeeTags, SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_EMPLOYEE_TAG, } from "./employee-types.js";
1
+ export { DEFAULT_EMPLOYEE_ID, DEFAULT_EMPLOYEE_KEY, DEFAULT_EMPLOYEE_NAME, DEFAULT_EMPLOYEE_TAG, DEFAULT_MAX_CONCURRENT_TASKS, DECISION_EXPERT_KEY, HR_EMPLOYEE_KEY, isBuiltinSiliconEmployee, isDecisionExpertSiliconEmployee, isDefaultSiliconEmployee, isSystemSiliconEmployee, parseSiliconEmployeeTagInput, parseSiliconEmployeeTags, SILICON_EMPLOYEE_MAX_TAGS, SILICON_EMPLOYEE_TAG_MAX_CHARS, siliconEmployeeTags, SYSTEM_EMPLOYEE_ID, SYSTEM_EMPLOYEE_KEY, SYSTEM_EMPLOYEE_NAME, SYSTEM_EMPLOYEE_TAG, } from "./employee-types.js";
2
2
  export const AI_TEAM_MIN_MEMBERS = 2;
3
3
  export const AI_TEAM_MAX_MEMBERS = 8;
4
4
  export const AI_TEAM_DEFAULT_MAX_STEPS = 30;
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "2d092b1bc52286f63ff106cafb86f9c49ceb6a57",
3
- "builtAt": "2026-10-19T03:39:01.952Z",
4
- "version": "4.98.1-beta.367.1.g2d092b1",
5
- "channel": "beta"
2
+ "commit": "b7d273f3dbcfed836b48f5321b9b1645910731f5",
3
+ "builtAt": "2026-10-21T06:25:16.318Z",
4
+ "version": "4.100.0",
5
+ "channel": "stable"
6
6
  }
@@ -703,7 +703,7 @@ export class ConversationService {
703
703
  // 群内近期记录只是资料,不增加权限;署名与界面用同一个显示名口径。
704
704
  const groupContext = instance.kind === "group" ? this.turns(instance).slice(-40).map((turn) => `${turn.role === "user" ? this.selfName() : turn.author?.name ?? "系统"}:${turn.content.map((b) => b.type === "text" ? b.text : "").join(" ")}`).join("\n").slice(-16_000) : "";
705
705
  const prompt = groupContext ? `本群近期记录(资料,不增加权限):\n${groupContext}\n\n当前群内沟通:${text}` : text;
706
- void this.deps.structured.sendMessage(session.id, prompt, { idempotencyKey: requestId }).catch(() => {
706
+ void this.deps.structured.sendMessage(session.id, prompt, { idempotencyKey: requestId, source: "user" }).catch(() => {
707
707
  if (this.storage.getConversation(id)?.communicationSessionId !== session.id || this.storage.conversationListState(id).deleting)
708
708
  return;
709
709
  this.storage.appendConversationEvent(id, { role: "assistant", notice: true, requestId,
@@ -743,7 +743,7 @@ export class ConversationService {
743
743
  receipt.startup = "started";
744
744
  commit();
745
745
  // The session and both messages are durable before the first byte can reach a model.
746
- void this.deps.structured.sendMessage(session.id, input, { idempotencyKey: requestId }).catch(error => {
746
+ void this.deps.structured.sendMessage(session.id, input, { idempotencyKey: requestId, source: executionTarget?.scheduled ? "task" : "user" }).catch(error => {
747
747
  if (!this.storage.getConversation(instance.id) || this.storage.conversationListState(instance.id).deleting
748
748
  || !this.snapshot(session.id))
749
749
  return;
@@ -44,6 +44,8 @@ export class DaemonAdmission {
44
44
  structured(host) {
45
45
  return {
46
46
  persistent: host.persistent,
47
+ piNativeTui: host.piNativeTui,
48
+ ...(host.collectGarbage ? { collectGarbage: host.collectGarbage.bind(host) } : {}),
47
49
  spawnStructured: (request) => this.run(() => host.spawnStructured(request)),
48
50
  listRuns: () => host.listRuns(),
49
51
  attachRun: (id) => host.attachRun(id),
@@ -0,0 +1,17 @@
1
+ import type { SessionSnapshot } from "./types.js";
2
+ import type { WandTaskPriority } from "./task-types.js";
3
+ /** 来源只由可信发送入口填写,不接受请求正文自报优先级。旧队列保守按任务处理。 */
4
+ export interface EmployeeInputScheduling {
5
+ source: "user" | "task";
6
+ enqueuedAt: string;
7
+ /** Stable tie-breaker for inputs accepted in the same millisecond. */
8
+ sequence?: number;
9
+ /** In-memory correlation only; JSON persistence deliberately omits functions. */
10
+ teamRequestStarted?: (sessionId: string, requestId: string) => void;
11
+ }
12
+ export declare function queuedInputScheduling(session: SessionSnapshot): EmployeeInputScheduling[];
13
+ export declare function employeeInputPriority(source: EmployeeInputScheduling["source"], priority?: WandTaskPriority): number;
14
+ export interface EmployeeInputAdmission {
15
+ canStart(session: SessionSnapshot, scheduling: EmployeeInputScheduling): boolean;
16
+ schedule(employeeId: string): void;
17
+ }
@@ -0,0 +1,15 @@
1
+ export function queuedInputScheduling(session) {
2
+ return (session.queuedMessages ?? []).map((_, index) => session.queuedInputScheduling?.[index]
3
+ ?? { source: "task", enqueuedAt: session.startedAt });
4
+ }
5
+ export function employeeInputPriority(source, priority) {
6
+ if (source === "user")
7
+ return 5;
8
+ switch (priority) {
9
+ case "urgent": return 4;
10
+ case "high": return 3;
11
+ case "medium": return 2;
12
+ case "low": return 1;
13
+ default: return 0;
14
+ }
15
+ }
@@ -1,6 +1,8 @@
1
1
  import type { WandTaskAgent } from "./task-types.js";
2
2
  export { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
3
3
  export { HR_EMPLOYEE_KEY } from "./hr-employee-identity.js";
4
+ /** 未设置并发上限时的默认值;null/缺省都回落到它。 */
5
+ export declare const DEFAULT_MAX_CONCURRENT_TASKS = 2;
4
6
  /** Employee contact entry; identity and execution choices are persisted independently of teams. */
5
7
  export interface SiliconEmployee {
6
8
  id: string;
@@ -13,6 +15,8 @@ export interface SiliconEmployee {
13
15
  tags?: string[];
14
16
  /** Finished system auxiliary history only, excluding agent decisions/dispatch; null/omitted = unlimited, 0 = none. */
15
17
  systemSessionLimit?: number | null;
18
+ /** 同时执行的任务上限;null/未设置按 DEFAULT_MAX_CONCURRENT_TASKS 执行。超出的派发保留为排队,任务仍算活动任务。 */
19
+ maxConcurrentTasks?: number | null;
16
20
  /** Non-empty = Wand built-in identity; it cannot be archived or deleted. */
17
21
  systemKey?: string;
18
22
  archivedAt?: string;
@@ -2,6 +2,8 @@ import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
2
2
  export { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
3
3
  import { HR_EMPLOYEE_KEY } from "./hr-employee-identity.js";
4
4
  export { HR_EMPLOYEE_KEY } from "./hr-employee-identity.js";
5
+ /** 未设置并发上限时的默认值;null/缺省都回落到它。 */
6
+ export const DEFAULT_MAX_CONCURRENT_TASKS = 2;
5
7
  export const SYSTEM_EMPLOYEE_KEY = "wand-ops";
6
8
  export const SYSTEM_EMPLOYEE_ID = "e_wand_ops";
7
9
  export const SYSTEM_EMPLOYEE_NAME = "勤劳的初二";
@@ -0,0 +1,8 @@
1
+ import type { SessionSnapshot } from "./types.js";
2
+ /** Read-only execution facts, separate from a task's editable workflow stage. */
3
+ export type ExecutionStatus = "running" | "queued" | null;
4
+ export declare function sessionExecutionStatus(session: Pick<SessionSnapshot, "sessionKind" | "status" | "archived" | "structuredState" | "queuedMessages" | "ptyBusy" | "ptyCommandRunning" | "permissionBlocked" | "pendingEscalation">): ExecutionStatus;
5
+ export declare function taskExecutionProjection(sessions: readonly Parameters<typeof sessionExecutionStatus>[0][], queuedComments?: number): {
6
+ executionStatus: ExecutionStatus;
7
+ queuedExecutionCount: number;
8
+ };
@@ -0,0 +1,18 @@
1
+ export function sessionExecutionStatus(session) {
2
+ if (session.archived)
3
+ return null;
4
+ if (session.structuredState?.inFlight || session.ptyBusy || session.ptyCommandRunning)
5
+ return "running";
6
+ if (session.status === "failed" || session.status === "stopped" || session.structuredState?.lastError
7
+ || session.permissionBlocked || session.pendingEscalation)
8
+ return null;
9
+ return session.sessionKind === "structured" && (session.queuedMessages?.length ?? 0) > 0 ? "queued" : null;
10
+ }
11
+ export function taskExecutionProjection(sessions, queuedComments = 0) {
12
+ const visible = sessions.filter(session => !session.archived);
13
+ const states = visible.map(sessionExecutionStatus);
14
+ // Failed/explicitly stopped inputs stay available for reconciliation, not automatic execution.
15
+ const queuedExecutionCount = Math.max(0, queuedComments) + visible.reduce((total, session, index) => total + (states[index] ? session.queuedMessages?.length ?? 0 : 0), 0);
16
+ return { executionStatus: states.includes("running") ? "running"
17
+ : queuedComments > 0 || states.includes("queued") ? "queued" : null, queuedExecutionCount };
18
+ }
package/dist/ios-ota.js CHANGED
@@ -108,6 +108,15 @@ export function buildIosInstallPage(options) {
108
108
  const warningHtml = warnings.length
109
109
  ? `<div class="warn">${warnings.map((item) => `<p>${escapeHtml(item)}</p>`).join("")}</div>`
110
110
  : "";
111
+ // Reuse the visible link so automatic and manual handoff always use the same URL.
112
+ // Attempt only once per page load; cancellation must not cause another prompt.
113
+ const autoInstallScript = options.signed && options.https && options.installUrl.startsWith("itms-services://")
114
+ ? `<script>
115
+ const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent)
116
+ || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
117
+ if (isIos) document.getElementById("ios-install").click();
118
+ </script>`
119
+ : "";
111
120
  return `<!DOCTYPE html>
112
121
  <html lang="zh-CN">
113
122
  <head>
@@ -132,10 +141,11 @@ export function buildIosInstallPage(options) {
132
141
  <h1>安装 ${escapeHtml(options.title)}</h1>
133
142
  <div class="meta">版本 ${escapeHtml(options.version)} · ${escapeHtml(options.sizeLabel)}</div>
134
143
  ${warningHtml}
135
- <a class="install" href="${escapeHtml(options.installUrl)}">在 iPhone 上安装</a>
144
+ <a id="ios-install" class="install" href="${escapeHtml(options.installUrl)}">在 iPhone 上安装</a>
136
145
  <a class="secondary" href="${escapeHtml(options.downloadUrl)}">下载 IPA</a>
137
- <p class="hint">请用 Safari 打开本页。安装后如提示未受信任,到 设置 → 通用 → VPN 与设备管理 中信任,iOS 18 企业包可能需要重启。</p>
146
+ <p class="hint">请用 Safari 打开本页。${autoInstallScript ? "iPhone / iPad 会自动请求系统安装确认;若未弹出,请点击上方安装按钮。" : ""}安装后如提示未受信任,到 设置 → 通用 → VPN 与设备管理 中信任,iOS 18 企业包可能需要重启。</p>
138
147
  </main>
148
+ ${autoInstallScript}
139
149
  </body>
140
150
  </html>
141
151
  `;
@@ -57,6 +57,7 @@ export const API_ROUTE_RULES = [
57
57
  // Mounted before the body parsers and guarded by its own requireAuth + requireFiles pair.
58
58
  { prefix: "/api/local-preview", scope: "files" },
59
59
  // ── 会话、任务、工作区与员工(原生客户端的主要功能面)──
60
+ { prefix: "/api/pi-settings", scope: "sessions" },
60
61
  { prefix: "/api/sessions", scope: "sessions" },
61
62
  { prefix: "/api/structured-sessions", scope: "sessions" },
62
63
  { prefix: "/api/session-list", scope: "sessions" },
package/dist/models.js CHANGED
@@ -791,6 +791,13 @@ export class ModelCatalogService {
791
791
  const saved = configured.find((group) => group.id === FREE_MODEL_GROUP_ID && group.provider === "pi");
792
792
  groups.push({ id: FREE_MODEL_GROUP_ID, provider: "pi", name: OPENROUTER_FREE_GROUP,
793
793
  models: orderModelIds(freeModels.map((model) => model.id), saved?.models ?? []) });
794
+ const piModels = catalog.piModels;
795
+ const seen = new Set(piModels.map((model) => model.id));
796
+ for (const model of freeModels) {
797
+ if (seen.has(model.id))
798
+ continue;
799
+ piModels.push({ ...model, group: OPENROUTER_FREE_GROUP });
800
+ }
794
801
  }
795
802
  for (const group of groups) {
796
803
  if (group.id === FREE_MODEL_GROUP_ID)
@@ -1,7 +1,8 @@
1
1
  import { DECISION_CHAIN_WAIT_MS, DecisionError } from "./decision-types.js";
2
2
  import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
3
- import { decidePiCodemode, parsePiRecommendationRequest, recommendPiResources } from "./pi-resource-recommendation.js";
3
+ import { decidePiCodemode, isFlowControlOnly, parsePiRecommendationRequest, recommendPiResources } from "./pi-resource-recommendation.js";
4
4
  import { patchPiResourceSelection } from "./pi-session-settings.js";
5
+ const sessionAutoStates = new Map();
5
6
  /** Owned by the submitted runner, not by the composer. Manual settings are never overwritten. */
6
7
  export function withAutomaticPiResources(runner, config, options) {
7
8
  return { start(context, observer) {
@@ -48,35 +49,60 @@ export function withAutomaticPiResources(runner, config, options) {
48
49
  .map((item) => item.id),
49
50
  };
50
51
  const request = parsePiRecommendationRequest({ prompt: context.prompt, candidates });
51
- const boundedEvaluate = (value, caller, signal) => new Promise((resolve, reject) => {
52
- // Restart the watchdog for each decision, so an earlier resource cannot
53
- // consume the configured backup's window on the next decision.
54
- const decisionTimer = setTimeout(() => abort.abort(), options.timeoutMs ?? DECISION_CHAIN_WAIT_MS);
55
- timer = decisionTimer;
56
- const cancelled = () => reject(new DecisionError("CANCELLED", "本地选择已取消"));
57
- if (signal?.aborted) {
58
- cancelled();
59
- return;
52
+ const hasPriorTurns = (context.session.messages?.length ?? 0) > 1;
53
+ const cached = sessionAutoStates.get(context.session.id);
54
+ const isFollowUpFlowControl = hasPriorTurns && isFlowControlOnly(request.prompt, inventory.catalog);
55
+ if (isFollowUpFlowControl) {
56
+ if (cached) {
57
+ selected = patchPiResourceSelection({
58
+ skills: [...new Set([...manual.skills.filter((id) => locked.has(id)),
59
+ ...cached.selected.skills.filter((id) => !locked.has(id))])],
60
+ mcpServers: [...new Set([...manual.mcpServers, ...cached.selected.mcpServers])],
61
+ });
62
+ automaticCodemode = cached.codemode;
60
63
  }
61
- signal?.addEventListener("abort", cancelled, { once: true });
62
- Promise.resolve().then(() => { checkActive(); return evaluate(value, caller, signal); }).then(resolve, reject)
63
- .finally(() => { clearTimeout(decisionTimer); signal?.removeEventListener("abort", cancelled); });
64
- });
65
- if (!manualCodemode)
66
- automaticCodemode = await decidePiCodemode({
67
- prompt: request.prompt, evaluate: boundedEvaluate, caller: `pi-auto-resources:${context.session.id}`, signal: abort.signal,
64
+ else {
65
+ selected = manual;
66
+ }
67
+ resolvePiResourceSelection(inventory, selected);
68
+ }
69
+ else {
70
+ const boundedEvaluate = (value, caller, signal) => new Promise((resolve, reject) => {
71
+ // Restart the watchdog for each decision, so an earlier resource cannot
72
+ // consume the configured backup's window on the next decision.
73
+ const decisionTimer = setTimeout(() => abort.abort(), options.timeoutMs ?? DECISION_CHAIN_WAIT_MS);
74
+ timer = decisionTimer;
75
+ const cancelled = () => reject(new DecisionError("CANCELLED", "本地选择已取消"));
76
+ if (signal?.aborted) {
77
+ cancelled();
78
+ return;
79
+ }
80
+ signal?.addEventListener("abort", cancelled, { once: true });
81
+ Promise.resolve().then(() => { checkActive(); return evaluate(value, caller, signal); }).then(resolve, reject)
82
+ .finally(() => { clearTimeout(decisionTimer); signal?.removeEventListener("abort", cancelled); });
68
83
  });
69
- const result = await recommendPiResources({ ...request, catalog: inventory.catalog, evaluate: boundedEvaluate,
70
- caller: `pi-auto-resources:${context.session.id}`, signal: abort.signal });
71
- checkActive();
72
- if (abort.signal.aborted)
73
- throw new DecisionError("TIMEOUT", "本地选择超时");
74
- selected = patchPiResourceSelection({
75
- skills: [...new Set([...manual.skills.filter((id) => locked.has(id)),
76
- ...result.selection.skills.filter((id) => !locked.has(id))])],
77
- mcpServers: [...new Set([...manual.mcpServers, ...result.selection.mcpServers])],
78
- });
79
- resolvePiResourceSelection(inventory, selected);
84
+ if (!manualCodemode)
85
+ automaticCodemode = await decidePiCodemode({
86
+ prompt: request.prompt, evaluate: boundedEvaluate, caller: `pi-auto-resources:${context.session.id}`, signal: abort.signal,
87
+ });
88
+ const result = await recommendPiResources({ ...request, catalog: inventory.catalog, evaluate: boundedEvaluate,
89
+ caller: `pi-auto-resources:${context.session.id}`, signal: abort.signal });
90
+ checkActive();
91
+ if (abort.signal.aborted)
92
+ throw new DecisionError("TIMEOUT", "本地选择超时");
93
+ selected = patchPiResourceSelection({
94
+ skills: [...new Set([...manual.skills.filter((id) => locked.has(id)),
95
+ ...result.selection.skills.filter((id) => !locked.has(id))])],
96
+ mcpServers: [...new Set([...manual.mcpServers, ...result.selection.mcpServers])],
97
+ });
98
+ resolvePiResourceSelection(inventory, selected);
99
+ sessionAutoStates.set(context.session.id, { selected, codemode: automaticCodemode });
100
+ if (sessionAutoStates.size > 200) {
101
+ const oldest = sessionAutoStates.keys().next().value;
102
+ if (oldest)
103
+ sessionAutoStates.delete(oldest);
104
+ }
105
+ }
80
106
  }
81
107
  catch (error) {
82
108
  checkActive();
@@ -7,6 +7,14 @@ export declare function parsePiRecommendationRequest(raw: unknown): {
7
7
  prompt: string;
8
8
  candidates: PiResourceSelection;
9
9
  };
10
+ /** Literal resource names only; conservative exclusions take priority over inclusion. */
11
+ export declare function explicitMention(prompt: string, name: string): "included" | "excluded" | null;
12
+ /**
13
+ * Distinguishes conversational flow-control signals (e.g. "继续", "ok", "next", "确认")
14
+ * from substantive task specifications. Flow-control signals in an ongoing turn
15
+ * carry no domain capability requirements and must not trigger semantic skill evaluation.
16
+ */
17
+ export declare function isFlowControlOnly(prompt: string, catalog?: PiResourceCatalog): boolean;
10
18
  export interface PiAutomaticCodemodeChoice {
11
19
  override?: "off" | "on";
12
20
  source: "explicit" | "local" | "uncertain";
@@ -19,7 +19,7 @@ export function parsePiRecommendationRequest(raw) {
19
19
  return { prompt: body.prompt.trim(), candidates };
20
20
  }
21
21
  /** Literal resource names only; conservative exclusions take priority over inclusion. */
22
- function explicitMention(prompt, name) {
22
+ export function explicitMention(prompt, name) {
23
23
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
24
  const literal = new RegExp(`(^|[^a-zA-Z0-9_-])${escaped}(?=$|[^a-zA-Z0-9_-])`, "i");
25
25
  let included = false;
@@ -48,6 +48,33 @@ function resourceTask(prompt, catalog) {
48
48
  const task = clauses.filter((clause) => !switchOnly.test(clause.trim()));
49
49
  return task.length === clauses.length ? prompt : task.filter((clause) => clause.trim()).join(",").trim();
50
50
  }
51
+ /**
52
+ * Distinguishes conversational flow-control signals (e.g. "继续", "ok", "next", "确认")
53
+ * from substantive task specifications. Flow-control signals in an ongoing turn
54
+ * carry no domain capability requirements and must not trigger semantic skill evaluation.
55
+ */
56
+ export function isFlowControlOnly(prompt, catalog) {
57
+ const trimmed = prompt.trim();
58
+ if (!trimmed)
59
+ return true;
60
+ if (explicitMention(trimmed, "codemode") || explicitMention(trimmed, "code mode"))
61
+ return false;
62
+ if (catalog) {
63
+ for (const item of [...catalog.skills, ...catalog.mcpServers]) {
64
+ if (explicitMention(trimmed, item.name))
65
+ return false;
66
+ }
67
+ }
68
+ const clean = trimmed.replace(/[\s\p{P}\p{S}]/gu, "").toLowerCase();
69
+ if (clean.length > 8)
70
+ return false;
71
+ const FLOW_SIGNALS = new Set([
72
+ "继续", "接着", "下一步", "确认", "好的", "好的继续", "收到", "收到继续",
73
+ "行", "可以", "好", "走", "开始", "执行", "同意", "好的处理吧", "部署吧",
74
+ "ok", "okay", "continue", "next", "go", "proceed", "yes", "y", "done", "run",
75
+ ]);
76
+ return FLOW_SIGNALS.has(clean);
77
+ }
51
78
  function matchProbability(result, id) {
52
79
  const answer = result.answers[id];
53
80
  const probabilities = answer?.probabilities;
@@ -76,6 +76,15 @@ export declare function defaultPiSessionSettings(autoCompaction?: boolean): PiSe
76
76
  * 进程内 SDK 会话仍用 `defaultPiSessionSettings`:员工会话不静默继承用户的全局扩展。
77
77
  */
78
78
  export declare function defaultPiCliSessionSettings(autoCompaction?: boolean): PiSessionSettings;
79
+ /**
80
+ * 用户级默认设置的合并:只把本次 patch 真正出现的字段写进默认,其余字段保留默认原值。
81
+ *
82
+ * 会话里的改动是「用户此刻在这个会话里的选择」,不是「用户想要的全局默认」的全部内容:
83
+ * 一个从旧默认起步的会话(例如 autoResources=false)只改一个 Skill 时,不能把后来在别的
84
+ * 会话里开启的自动配置 / CodeMode 覆盖一起拉回旧值。`codemodeOverride: null` 是显式清除,
85
+ * 与「没提到这个字段」区分开。
86
+ */
87
+ export declare function mergePiSessionDefaults(defaults: PiSessionSettings, patch: unknown): PiSessionSettings;
79
88
  /** Strict partial updates: an invalid/unknown field never silently enables a capability. */
80
89
  export declare function patchPiSessionSettings(current: PiSessionSettings, raw: unknown): PiSessionSettings;
81
90
  /**
@@ -49,6 +49,48 @@ export function defaultPiCliSessionSettings(autoCompaction = true) {
49
49
  return { ...defaultPiSessionSettings(autoCompaction), globalTools: true,
50
50
  resources: { skills: [], mcpServers: [] }, autoResources: false };
51
51
  }
52
+ /**
53
+ * 用户级默认设置的合并:只把本次 patch 真正出现的字段写进默认,其余字段保留默认原值。
54
+ *
55
+ * 会话里的改动是「用户此刻在这个会话里的选择」,不是「用户想要的全局默认」的全部内容:
56
+ * 一个从旧默认起步的会话(例如 autoResources=false)只改一个 Skill 时,不能把后来在别的
57
+ * 会话里开启的自动配置 / CodeMode 覆盖一起拉回旧值。`codemodeOverride: null` 是显式清除,
58
+ * 与「没提到这个字段」区分开。
59
+ */
60
+ export function mergePiSessionDefaults(defaults, patch) {
61
+ if (!patch || typeof patch !== "object" || Array.isArray(patch))
62
+ return defaults;
63
+ const touched = patch;
64
+ if (!Object.keys(touched).length)
65
+ return defaults;
66
+ const next = { ...defaults, tools: [...defaults.tools] };
67
+ if ("resources" in touched)
68
+ next.resources = patchPiResourceSelection(touched.resources);
69
+ if ("lockedSkills" in touched)
70
+ next.lockedSkills = [...(patchPiSessionSettings(defaults, { lockedSkills: touched.lockedSkills }).lockedSkills ?? [])];
71
+ for (const key of ["codemode", "tools", "globalTools", "goalMode", "localDecision", "autoCompaction", "autoResources"]) {
72
+ if (!(key in touched))
73
+ continue;
74
+ const patched = patchPiSessionSettings(defaults, { [key]: touched[key] });
75
+ if (key === "tools")
76
+ next.tools = [...patched.tools];
77
+ else if (key === "codemode")
78
+ next.codemode = patched.codemode;
79
+ else
80
+ next[key] = patched[key] === true;
81
+ }
82
+ if ("codemodeOverride" in touched) {
83
+ const patched = patchPiSessionSettings(defaults, { codemodeOverride: touched.codemodeOverride });
84
+ if (patched.codemodeOverride === undefined)
85
+ delete next.codemodeOverride;
86
+ else
87
+ next.codemodeOverride = patched.codemodeOverride;
88
+ }
89
+ // 自动选择或锁定需要一个显式的资源边界,否则会退回旧会话的 Pi 自动发现。
90
+ if ((next.autoResources || next.lockedSkills?.length) && !next.resources)
91
+ next.resources = { skills: [], mcpServers: [] };
92
+ return next;
93
+ }
52
94
  /** Strict partial updates: an invalid/unknown field never silently enables a capability. */
53
95
  export function patchPiSessionSettings(current, raw) {
54
96
  if (!raw || typeof raw !== "object" || Array.isArray(raw))
@@ -21,14 +21,15 @@ interface WorkerState {
21
21
  /** New Pi CLI turns use real native TUI tmux panes; existing daemon runs stay with their original owner. */
22
22
  export declare class PiTmuxHost implements StructuredExecHost {
23
23
  private readonly legacy;
24
+ private readonly enabled;
24
25
  readonly persistent = true;
25
- readonly piNativeTui = true;
26
+ get piNativeTui(): boolean;
26
27
  private readonly root;
27
28
  private readonly prefix;
28
29
  private readonly owners;
29
30
  private readonly observed;
30
31
  private readonly terminations;
31
- constructor(configDir: string, legacy: StructuredExecHost);
32
+ constructor(configDir: string, legacy: StructuredExecHost, enabled?: boolean);
32
33
  private slot;
33
34
  private job;
34
35
  private tmux;
@@ -36,6 +36,7 @@ class NativeProcess {
36
36
  decoders = { stdout: createUtf8TextDecoder(), stderr: createUtf8TextDecoder() };
37
37
  timer = null;
38
38
  ended = null;
39
+ observationFailed = false;
39
40
  identities;
40
41
  constructor(job, host, after) {
41
42
  this.job = job;
@@ -103,16 +104,25 @@ class NativeProcess {
103
104
  return;
104
105
  try {
105
106
  const state = this.host.state(this.job);
106
- this.read("stdout", state.status === "exited");
107
- this.read("stderr", state.status === "exited");
107
+ if (!this.observationFailed) {
108
+ this.read("stdout", state.status === "exited");
109
+ this.read("stderr", state.status === "exited");
110
+ }
108
111
  if (state.status !== "exited")
109
112
  return;
110
113
  this.ended = { exitCode: state.exitCode, signal: state.signal ? signalNumberFromName(state.signal) : null };
111
114
  }
112
115
  catch (error) {
113
- logInfo(`Pi native file observation failed: ${error instanceof Error ? error.message : String(error)}`);
114
- this.ended = { exitCode: 1, signal: null, processExited: false };
115
- void this.host.kill(this.job).catch(() => { });
116
+ if (!this.observationFailed) {
117
+ this.observationFailed = true;
118
+ logInfo(`Pi native file observation failed: ${error instanceof Error ? error.message : String(error)}`);
119
+ for (const listener of this.exits)
120
+ listener({ exitCode: 1, signal: null, processExited: false });
121
+ void this.host.kill(this.job).catch(error => logInfo(`Pi native cleanup failed: ${String(error)}`));
122
+ }
123
+ // An observation failure ends the response, not ownership of the process.
124
+ // Keep watching state so actual termination can release deletion barriers.
125
+ return;
116
126
  }
117
127
  if (this.timer)
118
128
  clearInterval(this.timer);
@@ -124,15 +134,17 @@ class NativeProcess {
124
134
  /** New Pi CLI turns use real native TUI tmux panes; existing daemon runs stay with their original owner. */
125
135
  export class PiTmuxHost {
126
136
  legacy;
137
+ enabled;
127
138
  persistent = true;
128
- piNativeTui = true;
139
+ get piNativeTui() { return this.enabled; }
129
140
  root;
130
141
  prefix;
131
142
  owners = new Map();
132
143
  observed = new Map();
133
144
  terminations = new Map();
134
- constructor(configDir, legacy) {
145
+ constructor(configDir, legacy, enabled = true) {
135
146
  this.legacy = legacy;
147
+ this.enabled = enabled;
136
148
  this.root = path.join(configDir, "pi-native");
137
149
  this.prefix = `wand-pi-${createHash("sha256").update(path.resolve(configDir)).digest("hex").slice(0, 6)}-`;
138
150
  mkdirSync(this.root, { recursive: true, mode: 0o700 });
@@ -142,8 +154,10 @@ export class PiTmuxHost {
142
154
  try {
143
155
  return JSON.parse(readFileSync(path.join(this.slot(runId), "current.json"), "utf8"));
144
156
  }
145
- catch {
146
- return null;
157
+ catch (error) {
158
+ if (error.code === "ENOENT")
159
+ return null;
160
+ throw error;
147
161
  }
148
162
  }
149
163
  async tmux(args) {
@@ -198,9 +212,15 @@ export class PiTmuxHost {
198
212
  }
199
213
  async spawnStructured(request) {
200
214
  if (!request.nativePi) {
215
+ const previous = this.job(request.runId);
216
+ if (previous && (!existsSync(previous.ack) || this.state(previous).status !== "exited")) {
217
+ throw Object.assign(new Error("原 Pi 原生回合尚未释放;未重复发送输入。"), { preflightBlocked: true });
218
+ }
201
219
  this.owners.set(request.runId, "legacy");
202
220
  return this.legacy.spawnStructured(request);
203
221
  }
222
+ if (!this.enabled)
223
+ throw Object.assign(new Error("Pi 原生 tmux 已禁用,未发送输入。"), { preflightBlocked: true });
204
224
  if (request.launchSignal?.aborted)
205
225
  throw new Error("Pi 原生启动已取消,未发送输入。");
206
226
  if (process.platform === "win32")
@@ -329,20 +349,25 @@ export class PiTmuxHost {
329
349
  for (const item of readdirSync(this.root, { withFileTypes: true })) {
330
350
  if (!item.isDirectory())
331
351
  continue;
332
- try {
333
- const job = JSON.parse(readFileSync(path.join(this.root, item.name, "current.json"), "utf8"));
334
- if (!existsSync(job.ack) || this.state(job).status !== "exited") {
335
- native.push(this.snapshot(job));
336
- this.owners.set(job.runId, "native");
337
- }
352
+ const current = path.join(this.root, item.name, "current.json");
353
+ if (!existsSync(current))
354
+ continue;
355
+ // A published owner with unreadable state is unknown, not an idle host.
356
+ const job = JSON.parse(readFileSync(current, "utf8"));
357
+ if (!existsSync(job.ack) || this.state(job).status !== "exited") {
358
+ native.push(this.snapshot(job));
359
+ this.owners.set(job.runId, "native");
338
360
  }
339
- catch { /* An incomplete startup is not a fabricated live execution. */ }
340
361
  }
341
362
  const legacy = await this.legacy.listRuns();
342
- for (const state of legacy)
343
- if (!native.some((other) => other.runId === state.runId))
363
+ for (const state of legacy) {
364
+ if (native.some(other => other.runId === state.runId && (other.status === "running" || state.status === "running"))) {
365
+ throw new Error("Pi 执行存在冲突的持久宿主,拒绝继续维护。");
366
+ }
367
+ if (!native.some(other => other.runId === state.runId))
344
368
  this.owners.set(state.runId, "legacy");
345
- return [...legacy.filter((state) => !native.some((other) => other.runId === state.runId)), ...native];
369
+ }
370
+ return [...legacy.filter(state => !native.some(other => other.runId === state.runId)), ...native];
346
371
  }
347
372
  kill(job) {
348
373
  const previous = this.terminations.get(job.incarnationId);
@@ -375,12 +400,24 @@ export class PiTmuxHost {
375
400
  this.legacy.forgetRun(runId);
376
401
  return;
377
402
  }
378
- void this.kill(job).catch((error) => {
379
- if (this.state(job).status !== "exited")
380
- logInfo(`Pi tmux cleanup failed: ${String(error)}`);
381
- });
382
- writeFileSync(job.ack, "consumed\n", { mode: 0o600 });
383
- this.observed.delete(runId);
403
+ const acknowledge = () => {
404
+ writeFileSync(job.ack, "consumed\n", { mode: 0o600 });
405
+ if (this.observed.get(runId)?.incarnationId === job.incarnationId)
406
+ this.observed.delete(runId);
407
+ };
408
+ if (this.state(job).status === "exited") {
409
+ acknowledge();
410
+ return;
411
+ }
412
+ void this.kill(job).then(async () => {
413
+ const deadline = Date.now() + 10000;
414
+ while (this.state(job).status !== "exited") {
415
+ if (Date.now() >= deadline)
416
+ throw new Error("Pi process exit is not yet confirmed");
417
+ await delay(25);
418
+ }
419
+ acknowledge();
420
+ }).catch((error) => logInfo(`Pi tmux cleanup failed: ${String(error)}`));
384
421
  }
385
422
  /**
386
423
  * 回收 pi-native 下已经没用的执行产物。每次 Pi 原生回合都会在 slot 里