@jmfederico/pi-web 1.202608.2 → 1.202609.1

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 (233) hide show
  1. package/README.md +3 -3
  2. package/dist/.plugins-ready +1 -0
  3. package/dist/cli.js +5 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/client/apple-touch-icon-dev.png +0 -0
  6. package/dist/client/assets/index-AroYaCLx.js +4130 -0
  7. package/dist/client/assets/vendor-editor-core-YWog6mAR.js +10 -0
  8. package/dist/client/assets/vendor-editor-languages-DOCw3vc9.js +28 -0
  9. package/dist/client/favicon-dev.svg +8 -0
  10. package/dist/client/index.html +5 -5
  11. package/dist/client/pwa-icon-dev-192.png +0 -0
  12. package/dist/client/pwa-icon-dev-512.png +0 -0
  13. package/dist/config.js +20 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/docker/piWebDockerCommandPlan.js +9 -1
  16. package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
  17. package/dist/nativeServices/installedServiceDefinitions.js +25 -19
  18. package/dist/nativeServices/installedServiceDefinitions.js.map +1 -1
  19. package/dist/nativeServices/serviceAction.js +38 -26
  20. package/dist/nativeServices/serviceAction.js.map +1 -1
  21. package/dist/nativeServices/servicePlan.js +3 -3
  22. package/dist/nativeServices/servicePlan.js.map +1 -1
  23. package/dist/pi-packages/captains-log/README.md +11 -0
  24. package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
  25. package/dist/pi-packages/captains-log/dist/browser/index.js +144 -0
  26. package/dist/pi-packages/captains-log/dist/browser/markdown.js +67 -0
  27. package/dist/pi-packages/captains-log/dist/browser/panel.js +105 -0
  28. package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
  29. package/dist/pi-packages/captains-log/dist/companion.js +126 -0
  30. package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
  31. package/dist/pi-packages/captains-log/dist/server.js +210 -0
  32. package/dist/pi-packages/captains-log/dist/store.js +75 -0
  33. package/dist/pi-packages/captains-log/docs/usage.md +30 -0
  34. package/dist/pi-packages/captains-log/package.json +35 -0
  35. package/dist/pi-packages/captains-log/tsconfig.json +21 -0
  36. package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
  37. package/dist/pi-packages/relays/package.json +1 -1
  38. package/dist/pi-packages/relays/pi-web-plugin.js +1 -1
  39. package/dist/pi-packages/relays/prompts/relay-worktree.md +13 -142
  40. package/dist/pi-packages/relays/prompts/relay.md +25 -132
  41. package/dist/pi-packages/relays/relayDiscovery.js +2 -2
  42. package/dist/pi-packages/relays/relaysPanelElement.js +1 -1
  43. package/dist/pi-packages/relays/skills/relay/SKILL.md +59 -88
  44. package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +253 -0
  45. package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
  46. package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +439 -0
  47. package/dist/pi-web-plugins/files/package.json +15 -0
  48. package/dist/pi-web-plugins/git/browser/git-panel.js +54 -24
  49. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +1 -1
  50. package/dist/pi-web-plugins/git/git-backend.js +5 -5
  51. package/dist/pi-web-plugins/git/server-plugin.js +5 -3
  52. package/dist/pi-web-plugins/info/pi-web-plugin.js +1 -1
  53. package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +3590 -0
  54. package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +6 -0
  55. package/dist/pi-web-plugins/mermaid/package.json +13 -0
  56. package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +210 -0
  57. package/dist/pi-web-plugins/terminal/package.json +16 -0
  58. package/dist/pi-web-plugins/terminal/server-plugin.js +365 -0
  59. package/dist/{server/terminals → pi-web-plugins/terminal}/terminalService.js +162 -65
  60. package/dist/pi-web-plugins/updates/pi-web-plugin.js +45 -35
  61. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +1 -1
  62. package/dist/piWebVersionReport.js +12 -4
  63. package/dist/piWebVersionReport.js.map +1 -1
  64. package/dist/plugin-api.d.ts +222 -23
  65. package/dist/plugin-api.js +1 -0
  66. package/dist/pluginRecoveryCli.js +3 -2
  67. package/dist/pluginRecoveryCli.js.map +1 -1
  68. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  69. package/dist/server/app.js +33 -12
  70. package/dist/server/app.js.map +1 -1
  71. package/dist/server/configRoutes.js +6 -1
  72. package/dist/server/configRoutes.js.map +1 -1
  73. package/dist/server/deploymentIdentity.js +67 -0
  74. package/dist/server/deploymentIdentity.js.map +1 -0
  75. package/dist/server/deploymentIdentityRoutes.js +22 -0
  76. package/dist/server/deploymentIdentityRoutes.js.map +1 -0
  77. package/dist/server/knownAutoInstallPiPackages.js +1 -1
  78. package/dist/server/knownAutoInstallPiPackages.js.map +1 -1
  79. package/dist/server/knownPiPackages.js +12 -0
  80. package/dist/server/knownPiPackages.js.map +1 -0
  81. package/dist/server/machines/machineClient.js +2 -2
  82. package/dist/server/machines/machineClient.js.map +1 -1
  83. package/dist/server/machines/machinePluginProxyRoutes.js +53 -11
  84. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  85. package/dist/server/machines/machineProxyRoutes.js +70 -7
  86. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  87. package/dist/server/notices/serverNoticeRoutes.js +34 -0
  88. package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
  89. package/dist/server/notices/serverNoticeService.js +26 -0
  90. package/dist/server/notices/serverNoticeService.js.map +1 -0
  91. package/dist/server/notices/serverNoticeStore.js +119 -0
  92. package/dist/server/notices/serverNoticeStore.js.map +1 -0
  93. package/dist/server/piPackageService.js +5 -5
  94. package/dist/server/piPackageService.js.map +1 -1
  95. package/dist/server/piWebPluginCatalog.js +46 -12
  96. package/dist/server/piWebPluginCatalog.js.map +1 -1
  97. package/dist/server/piWebPluginLifecycle.js +26 -3
  98. package/dist/server/piWebPluginLifecycle.js.map +1 -1
  99. package/dist/server/piWebPluginService.js +37 -2
  100. package/dist/server/piWebPluginService.js.map +1 -1
  101. package/dist/server/piWebStatus.js +1 -1
  102. package/dist/server/piWebStatus.js.map +1 -1
  103. package/dist/server/pluginCallbackDrain.js +35 -0
  104. package/dist/server/pluginCallbackDrain.js.map +1 -0
  105. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
  106. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
  107. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
  108. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
  109. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +52 -0
  110. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
  111. package/dist/server/plugins/pluginBackendProxyRoutes.js +38 -31
  112. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
  113. package/dist/server/plugins/pluginBackendRegistry.js +740 -0
  114. package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
  115. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
  116. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
  117. package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
  118. package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
  119. package/dist/server/plugins/serverPluginRuntime.js +1028 -107
  120. package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
  121. package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
  122. package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
  123. package/dist/server/sessiond/pluginBackendChannelRoutes.js +393 -0
  124. package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
  125. package/dist/server/sessiond/pluginBackendRoutes.js +10 -7
  126. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
  127. package/dist/server/sessiond/sessionDaemonShutdown.js +9 -2
  128. package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -1
  129. package/dist/server/sessiond/sessionProxyRoutes.js +2 -0
  130. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  131. package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
  132. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
  133. package/dist/server/sessiond.js +82 -21
  134. package/dist/server/sessiond.js.map +1 -1
  135. package/dist/server/sessions/attachmentService.js +1 -5
  136. package/dist/server/sessions/attachmentService.js.map +1 -1
  137. package/dist/server/sessions/builtinCommands.js +2 -2
  138. package/dist/server/sessions/builtinCommands.js.map +1 -1
  139. package/dist/server/sessions/clientSessionPreview.js +14 -0
  140. package/dist/server/sessions/clientSessionPreview.js.map +1 -0
  141. package/dist/server/sessions/piSessionEventConnections.js +65 -0
  142. package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
  143. package/dist/server/sessions/piSessionManagerGateway.js +171 -1
  144. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  145. package/dist/server/sessions/piSessionService.js +442 -90
  146. package/dist/server/sessions/piSessionService.js.map +1 -1
  147. package/dist/server/sessions/sessionActivityMarker.js +105 -0
  148. package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
  149. package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
  150. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
  151. package/dist/server/sessions/sessionNameGenerator.js +3 -2
  152. package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
  153. package/dist/server/sessions/sessionRoutes.js +18 -0
  154. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  155. package/dist/server/sessions/spawnSessionTool.js +1 -1
  156. package/dist/server/sessions/spawnSessionTool.js.map +1 -1
  157. package/dist/server/sessions/spawnSubsessionTool.js +1 -1
  158. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  159. package/dist/server/sessions/transcriptBranchCache.js +81 -0
  160. package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
  161. package/dist/server/sessions/transcriptMessages.js +54 -0
  162. package/dist/server/sessions/transcriptMessages.js.map +1 -0
  163. package/dist/server/terminals/requiredTerminalService.js +103 -0
  164. package/dist/server/terminals/requiredTerminalService.js.map +1 -0
  165. package/dist/server/webSocketBridge.js +339 -0
  166. package/dist/server/webSocketBridge.js.map +1 -1
  167. package/dist/server/workspaces/projectPiWebConfig.js +6 -1
  168. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
  169. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +23 -3
  170. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
  171. package/dist/server/workspaces/workspaceCatalog.js +3 -1
  172. package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
  173. package/dist/server/workspaces/workspaceProviderRegistry.js +64 -149
  174. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
  175. package/dist/server/workspaces/workspaceRemovalService.js +38 -5
  176. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
  177. package/dist/server-plugin-api.d.ts +216 -22
  178. package/dist/server-plugin-api.js +319 -2
  179. package/dist/server-plugin-api.js.map +1 -1
  180. package/dist/serverPluginRecovery.js +4 -0
  181. package/dist/serverPluginRecovery.js.map +1 -1
  182. package/dist/sessiond/sessionDaemonClient.js +3 -3
  183. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  184. package/dist/shared/apiTypes.js +1 -1
  185. package/dist/shared/apiTypes.js.map +1 -1
  186. package/dist/shared/federatedRoutes.js +47 -17
  187. package/dist/shared/federatedRoutes.js.map +1 -1
  188. package/dist/shared/machinePluginIds.js +28 -7
  189. package/dist/shared/machinePluginIds.js.map +1 -1
  190. package/dist/shared/pluginApiTypes.d.ts +21 -1
  191. package/dist/shared/pluginApiTypes.js +0 -1
  192. package/dist/shared/pluginBackendProtocol.js +163 -2
  193. package/dist/shared/pluginBackendProtocol.js.map +1 -1
  194. package/dist/shared/pluginIds.js +8 -1
  195. package/dist/shared/pluginIds.js.map +1 -1
  196. package/dist/shared/requiredTerminalPlugin.js +6 -0
  197. package/dist/shared/requiredTerminalPlugin.js.map +1 -0
  198. package/dist/shared/serverNoticeContract.js +88 -0
  199. package/dist/shared/serverNoticeContract.js.map +1 -0
  200. package/dist/shared/sessionDefaults.js +41 -0
  201. package/dist/shared/sessionDefaults.js.map +1 -0
  202. package/docs/config.md +77 -37
  203. package/docs/plugins.md +146 -1213
  204. package/examples/session-bridge-plugin/README.md +20 -0
  205. package/examples/session-bridge-plugin/docs/usage.md +54 -0
  206. package/examples/session-bridge-plugin/package.json +26 -0
  207. package/examples/session-bridge-plugin/src/browser/index.ts +84 -0
  208. package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
  209. package/examples/session-bridge-plugin/src/companion.ts +65 -0
  210. package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
  211. package/examples/session-bridge-plugin/src/server.ts +85 -0
  212. package/examples/session-bridge-plugin/src/store.ts +57 -0
  213. package/examples/session-bridge-plugin/tsconfig.json +21 -0
  214. package/examples/workspace-provider-plugin/README.md +4 -4
  215. package/examples/workspace-provider-plugin/package.json +1 -1
  216. package/examples/workspace-provider-plugin/src/browser/index.ts +9 -9
  217. package/examples/workspace-provider-plugin/src/server.ts +15 -11
  218. package/package.json +17 -11
  219. package/dist/client/assets/CodeViewer-BMWwxG7q.js +0 -4
  220. package/dist/client/assets/TerminalPanel-CacQDIYn.js +0 -187
  221. package/dist/client/assets/index-DUW2xnoV.js +0 -4279
  222. package/dist/client/assets/vendor-editor-core-CXO8gGab.js +0 -12
  223. package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +0 -46
  224. package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
  225. package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
  226. package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
  227. package/dist/server/terminalProxyRoutes.js +0 -141
  228. package/dist/server/terminalProxyRoutes.js.map +0 -1
  229. package/dist/server/terminals/terminalRoutes.js +0 -155
  230. package/dist/server/terminals/terminalRoutes.js.map +0 -1
  231. package/dist/server/terminals/terminalService.js.map +0 -1
  232. package/dist/server/terminals/terminalSize.js +0 -17
  233. package/dist/server/terminals/terminalSize.js.map +0 -1
package/docs/config.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  PI WEB configuration covers the machine-local and project-local settings you usually need: the web/API bind address, trusted development-host settings, UI preferences, desired plugin enablement/settings, server-plugin recovery, file-explorer path access, manual upload defaults, upload limits, the Pi agent state directory, and session-daemon tools.
4
4
 
5
- This file is the markdown reference for agents and package consumers. The website page is <https://pi-web.dev/config>.
5
+ Use this reference for detailed configuration and operational behavior. For scannable settings tables with defaults, scopes, and restart requirements, see <https://pi-web.dev/config>.
6
6
 
7
7
  ## Config files
8
8
 
@@ -15,7 +15,25 @@ Each PI WEB machine has its own config. When using Fleet/machine federation, Set
15
15
 
16
16
  Pi package settings are separate from PI WEB config. They live in Pi's package-manager settings on the target machine and are managed by Pi (`pi install`, `pi remove`, `pi update`) or **Settings → Pi packages**. In a federated setup, **Settings → Pi packages** targets the currently selected machine. The PI WEB `plugins` config key controls desired enablement/settings for discovered browser-only, server-only, and dual-entry PI WEB plugins on that machine; it does not install, remove, or update Pi packages.
17
17
 
18
- If you installed services with a custom config path, `pi-web start`, `pi-web restart`, and `pi-web doctor` automatically use the `PI_WEB_CONFIG` saved in those service definitions for their readiness checks. A nonempty `PI_WEB_CONFIG` supplied when invoking one of those commands overrides the installed path for that command. On systemd, these commands fail rather than guess if active drop-ins, `EnvironmentFile=` inputs, stale manager state, a different loaded fragment, or an effective environment mismatch make the loaded definition untrustworthy. On launchd, `start` and `doctor` likewise fail if an already-loaded label came from another plist or retains a different config path; `restart` reloads the installed plists and can repair that stale state. Rerun `pi-web install --config /path/to/config.json` after changing the managed path or after upgrading from a version that only applied it to the web service; this regenerates service files so the web/API and session daemon use the same config.
18
+ ### Custom config paths in installed services
19
+
20
+ `start`, `restart`, and `doctor` use the config path saved in the installed services for readiness checks unless the caller supplies a nonempty `PI_WEB_CONFIG` override. `doctor` checks the managed setup, not every custom runtime environment.
21
+
22
+ | Situation | Behavior / action |
23
+ | --- | --- |
24
+ | Use the installed config | Run `pi-web start`, `pi-web restart`, or `pi-web doctor` normally. |
25
+ | Override the path for one command | Supply a nonempty `PI_WEB_CONFIG` when invoking that command. This does not rewrite service definitions. |
26
+ | Change the managed service config path | Run `pi-web install --config /path/to/config.json` to regenerate both web/API and sessiond service definitions. |
27
+ | Upgrade from an installation that set the path only for the web service | Rerun that same install command so both services use the same config. |
28
+ | systemd cannot verify the loaded service environment | The command fails rather than guesses if manager state is stale, the loaded fragment differs, or a PI WEB-managed environment value cannot be verified. Managed `Environment=` values (currently `PI_WEB_CONFIG`) must match the installed definition; unrelated variables are ignored. Drop-ins that leave managed values unchanged are allowed. |
29
+ | systemd uses `EnvironmentFile=` | Accepted with a nonfatal warning. File contents are not included in systemctl's `Environment` property and PI WEB does not inspect them, so config overrides in those files cannot be verified. |
30
+ | launchd has an old config path or a label loaded from another plist | `start` and `doctor` fail. `restart` reloads installed plists and can repair stale loaded state. |
31
+
32
+ ## Startup model and thinking defaults
33
+
34
+ Open the model or thinking-level selector and click a row’s star under **New session default** to save it for new sessions. A filled star marks the saved default. Clicking the option itself changes only the current session; setting the default leaves the current session unchanged.
35
+
36
+ Defaults are saved in Pi’s global `settings.json` on the selected session’s machine (`~/.pi/agent/settings.json` by default), using `defaultProvider`, `defaultModel`, and `defaultThinkingLevel`. They apply to new sessions without restarting. Project `.pi/settings.json` overrides, explicit startup choices, and per-model thinking settings still take precedence. A default model must be enabled; otherwise startup falls back to the first enabled model. Resumed sessions keep their saved model and thinking level.
19
37
 
20
38
  ## Reverse-proxy deployment paths
21
39
 
@@ -31,7 +49,7 @@ Machine-global runtime values are resolved as:
31
49
  defaults → global config file → environment overrides
32
50
  ```
33
51
 
34
- Supported project-local settings are then applied for that project's workspaces. For upload defaults, `<project>/.pi-web/config.json` overrides the global value.
52
+ Supported project-local settings are then applied for that project's workspaces. For upload and prompt-attachment defaults, `<project>/.pi-web/config.json` overrides the global value.
35
53
 
36
54
  Environment overrides include `PI_WEB_HOST`, `PI_WEB_PORT` / `PORT`, `PI_WEB_ALLOWED_HOSTS`, `PI_WEB_MAX_UPLOAD_BYTES`, `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR`, `PI_WEB_SPAWN_SESSIONS`, `PI_WEB_SUBSESSIONS`, `PI_WEB_ASK_USER`, and `PI_WEB_ENVIRONMENT_FACTS`.
37
55
 
@@ -42,6 +60,7 @@ Process restarts depend on the key:
42
60
  - `spawnSessions` / `subsessions` / `askUser` / `extensionDialogsTimeoutMs` / `environmentFacts`: restart the session daemon on that machine.
43
61
  - `pathAccess`: applies on the next request; existing file views may need a browser refresh.
44
62
  - `uploads.defaultFolder`: applies to newly opened Files upload dialogs and new direct drag/drop batches after config/workspace refresh.
63
+ - `attachments.defaultFolder`: applies to new prompt-attachment saves after config/workspace refresh.
45
64
  - `plugins`: browser-only changes apply after a browser-tab reload. Any enablement, settings, package-source, or package-revision change affecting a `serverModule` requires a manual session-daemon restart, then a browser reload for its paired UI.
46
65
  - `serverPlugins.safeStart`: persistent offline recovery state applied before server-plugin discovery/import on the next sessiond start; use the `pi-web plugins safe-start ...` CLI rather than hand-editing it.
47
66
  - Pi package install/remove/update: not a PI WEB config key; after a mutation, type `/reload` in each idle PI WEB session on the target machine to refresh ordinary Pi resources such as extensions, skills, prompt templates, themes, and context/system prompt files. For a PI WEB package with `serverModule`, manually restart `pi-web-sessiond.service`, then reload the browser. If a global Pi extension adds or removes a model provider, or changes a provider's connection settings, the same manual sessiond restart is required; `/reload` cannot change either startup snapshot. A known Pi model provider refreshing only its own model list is applied without a restart. See [Pi extension provider baseline](#pi-extension-provider-baseline).
@@ -59,6 +78,9 @@ Process restarts depend on the key:
59
78
  "uploads": {
60
79
  "defaultFolder": ".pi-web/uploads"
61
80
  },
81
+ "attachments": {
82
+ "defaultFolder": ".pi-web/attachments"
83
+ },
62
84
  "maxUploadBytes": 67108864,
63
85
  "spawnSessions": true,
64
86
  "subsessions": true,
@@ -88,13 +110,16 @@ Project-local config lives at `<project>/.pi-web/config.json`. Use it for settin
88
110
  },
89
111
  "uploads": {
90
112
  "defaultFolder": "manual/uploads"
113
+ },
114
+ "attachments": {
115
+ "defaultFolder": "prompt-attachments"
91
116
  }
92
117
  }
93
118
  ```
94
119
 
95
120
  Project-local `pathAccess.allowedPaths` entries are merged after the global list and deduplicated. Paths must still be host-absolute or `~`-prefixed; relative roots are not supported.
96
121
 
97
- Project-local `uploads.defaultFolder` overrides the global upload destination for workspaces in that project. PI WEB servers always include this workspace-effective value on the workspace responses used locally and through machine federation.
122
+ Project-local `uploads.defaultFolder` overrides the global upload destination for workspaces in that project, and project-local `attachments.defaultFolder` overrides the global prompt-attachment destination the same way. These defaults also apply when accessing the project through Fleet.
98
123
 
99
124
  Plugins may own separate project files, such as `.pi-web/tasks.json` for the built-in Workspace Tasks plugin.
100
125
 
@@ -143,7 +168,7 @@ worktree_path="$1"
143
168
 
144
169
  ## Configuration matrix
145
170
 
146
- Rows with JSON key `—` are runtime-only environment variables, not config-file keys. `Global` means machine-global. In Settings, selected-machine-safe global keys (`pathAccess`, `uploads`, `maxUploadBytes`, `spawnSessions`, `subsessions`, `askUser`, and `plugins`) are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine registry/tokens stay local.
171
+ Rows with JSON key `—` are runtime-only environment variables, not config-file keys. `Global` means machine-global. In Settings, selected-machine-safe global keys (`pathAccess`, `uploads`, `attachments`, `maxUploadBytes`, `spawnSessions`, `subsessions`, `askUser`, and `plugins`) are edited for the selected machine; gateway host/port/allowed-hosts, keyboard shortcuts, and machine registry/tokens stay local.
147
172
 
148
173
  | Config | JSON key | Env var | Scope | Project-local behavior | Applies / restart |
149
174
  | --- | --- | --- | --- | --- | --- |
@@ -153,6 +178,7 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
153
178
  | Dev-server allowed hosts | `allowedHosts` | `PI_WEB_ALLOWED_HOSTS` | Global | Not supported locally | Restart dev web/UI |
154
179
  | External filesystem roots | `pathAccess.allowedPaths` | — | Global + project | **Merges**: global roots first, then project roots; duplicates removed | Next file request; refresh existing views if needed |
155
180
  | Manual file upload default folder | `uploads.defaultFolder` | — | Global + project | **Overrides**: project value wins for workspaces in that project; otherwise global/default applies | New Upload dialogs and direct drag/drop batches after config/workspace refresh |
181
+ | Prompt attachment default folder | `attachments.defaultFolder` | — | Global + project | **Overrides**: project value wins for workspaces in that project; otherwise global/default applies | New prompt-attachment saves after config/workspace refresh |
156
182
  | Upload/body limit | `maxUploadBytes` | `PI_WEB_MAX_UPLOAD_BYTES` | Global | Not supported locally | Restart web/API and session daemon on that machine |
157
183
  | Agent can spawn sessions | `spawnSessions` | `PI_WEB_SPAWN_SESSIONS` | Global/session daemon | Not supported locally | Restart session daemon on that machine |
158
184
  | Tracked subsessions | `subsessions` | `PI_WEB_SUBSESSIONS` | Global/session daemon | Not supported locally; also requires `spawnSessions` | Restart session daemon on that machine |
@@ -185,7 +211,7 @@ Rows with JSON key `—` are runtime-only environment variables, not config-file
185
211
 
186
212
  Each data directory is independent: after pointing PI WEB at a new root, it starts there with empty registries and no session archives. To carry session archives over, stop PI WEB, then copy `archived-sessions.json` and the `archived-sessions/` directory from the old data directory into the new one before starting it again.
187
213
 
188
- One live session daemon owns each data directory. At startup the daemon records its ownership in `sessiond-owner.json` inside the data directory; a second session daemon pointed at the same directory while the first is still running fails loudly at startup with an error naming the owning process and the distinct `PI_WEB_DATA_DIR`, `PI_WEB_SESSIOND_SOCKET` (or `PI_WEB_SESSIOND_PORT` / `PI_WEB_SESSIOND_HOST`), and `PI_WEB_PORT` values a second instance needs. The web/API process of the same instance shares the data directory without claiming it, and a short startup grace covers ordinary service restarts. A marker left behind by a daemon that is no longer running is taken over automatically; if startup still refuses because of a marker whose owner is gone, delete the stale `sessiond-owner.json` as the error message suggests.
214
+ Only one live session daemon may use a data directory. For a second instance, set a distinct `PI_WEB_DATA_DIR`, `PI_WEB_SESSIOND_SOCKET` (or `PI_WEB_SESSIOND_PORT` / `PI_WEB_SESSIOND_HOST`), and `PI_WEB_PORT`. Stale ownership markers are normally recovered automatically. If startup still refuses, verify the named owner is no longer running before deleting `sessiond-owner.json` as the error suggests.
189
215
 
190
216
  This setting does not change the PI WEB config file selected by `PI_WEB_CONFIG` or Pi-owned state such as the active session files selected by `PI_CODING_AGENT_SESSION_DIR`.
191
217
 
@@ -234,12 +260,30 @@ The Files panel can upload one or more files in two ways:
234
260
 
235
261
  The value must be a non-empty workspace-relative folder. PI WEB normalizes repeated separators and backslashes to `/`, and rejects absolute paths or `..` traversal. In the upload dialog only, clearing the destination field uploads that batch to the workspace root.
236
262
 
237
- Manual uploads use the workspace file-write path: paths stay workspace-relative, parent folder creation is enabled by default, and overwrite is disabled by default. Direct drag/drop always keeps `overwrite` off; the review dialog lets you explicitly enable overwrite when needed. Browser-owned XHR progress is shown per batch/file, conflicts and errors stay visible in the upload progress UI, and the final file-write response is the source of truth.
263
+ Uploads stay inside the workspace, create parent folders by default, and do not overwrite existing files unless you enable overwrite in the review dialog. Direct drag/drop never overwrites. Check the upload progress UI for completion, conflicts, and errors.
238
264
 
239
- For machine federation, Settings saves the global upload default on the selected machine. Remote PI WEB servers always return `workspace.effectiveConfig.uploads.defaultFolder` on the workspace-list response, and the Files panel uses it as the default upload destination.
265
+ In Fleet, Settings saves the global upload default on the selected machine; the Files panel uses that project's effective destination.
240
266
 
241
267
  The per-request size limit is still controlled by `maxUploadBytes` / `PI_WEB_MAX_UPLOAD_BYTES` on the machine serving the upload.
242
268
 
269
+ ### Prompt attachment defaults
270
+
271
+ When the chat composer has pending attachments, its delivery selector offers **Save to …**: the attachments are written into a workspace folder and the prompt references the saved paths, instead of attaching the content inline.
272
+
273
+ `attachments.defaultFolder` sets the workspace-effective save destination shown in that selector. The built-in default is `.pi-web/attachments`; a global config value applies to every project unless `<project>/.pi-web/config.json` sets a project-local override.
274
+
275
+ ```json
276
+ {
277
+ "attachments": {
278
+ "defaultFolder": "prompt-attachments"
279
+ }
280
+ }
281
+ ```
282
+
283
+ The value must be a non-empty workspace-relative folder. PI WEB normalizes repeated separators and backslashes to `/`, and rejects absolute paths or `..` traversal. Saved attachments always stay inside the workspace root, and an explicit per-request folder on the attachments API overrides the configured default.
284
+
285
+ In Fleet, Settings saves the global attachment default on the selected machine; the composer uses that project's effective destination.
286
+
243
287
  ### Agent state directory
244
288
 
245
289
  PI WEB runs every session on its bundled Pi SDK. `pi-web doctor` and the status/update flow probe the `pi` command on the machine's `PATH`.
@@ -260,28 +304,19 @@ If the session daemon cannot report a valid active directory, profile-dependent
260
304
 
261
305
  ### Pi extension provider baseline
262
306
 
263
- This policy applies to **Pi runtime extensions that register model providers**, not PI WEB workspace-provider plugins. Pi extensions can call `pi.registerProvider(...)` and follow Pi's extension API. A PI WEB plugin may have a browser `module` and/or a sessiond `serverModule`, but its server entry follows the separate `@jmfederico/pi-web/server-plugin-api` lifecycle and cannot register Pi model providers or arbitrary hooks. See the [PI WEB plugin guide](https://pi-web.dev/plugins).
307
+ Model providers are shared across all sessions on a machine. PI WEB loads them when the session daemon starts, using Pi's built-in providers, environment credentials, the active agent directory's `models.json`, and globally installed Pi extensions/packages. PI WEB workspace plugins are separate; see the [plugin guide](https://pi-web.dev/plugins).
264
308
 
265
- PI WEB shares one model runtime across all sessions. When the session daemon starts, before any project resources load, it initializes global Pi extensions from the active agent directory, including extensions supplied by globally configured Pi packages. Provider registrations made by synchronous or awaited asynchronous extension factories during this bootstrap join the shared baseline. PI WEB captures both config-form registrations (`pi.registerProvider("id", config)`) and native-provider registrations (`pi.registerProvider(provider)`), alongside Pi built-ins, environment credentials, and providers from the active agent directory's `models.json`.
266
-
267
- After startup capture, a provider's connection settings are fixed for the daemon lifetime. Later attempts to add a provider, replace an existing provider's configuration, register a native provider, or unregister a provider are no-ops, regardless of source or provider ID. This includes project extensions attempting to add or replace a provider, lifecycle callbacks such as `session_start`, and `/reload`. Non-provider Pi extension features continue to load and reload normally.
309
+ Provider connection settings stay fixed until the daemon restarts. Project extensions and `/reload` cannot add, replace, or remove providers. Other Pi extension features continue to load and reload normally.
268
310
 
269
311
  #### Model list refresh for a known provider
270
312
 
271
- One narrow update is applied after startup: a provider captured in the baseline may refresh **its own model list**. Extensions that fetch an updated catalog typically re-send their complete provider configuration, so PI WEB compares the incoming registration against the recorded baseline and applies it only when both hold:
272
-
273
- - the provider ID is already in the startup baseline, and
274
- - every field except the model list is unchanged — `name`, `baseUrl`, `apiKey`, `api`, `streamSimple`, `headers`, `authHeader`, `oauth`, and `refreshModels`.
275
-
276
- Anything else stays a no-op, including a provider that was not in the baseline and a known provider whose credentials, base URL, or API surface differ from startup. Function-valued fields cannot be compared by value, so a registration that supplies a new `streamSimple`, `refreshModels`, or `oauth` implementation is treated as a change and ignored.
277
-
278
- An applied refresh becomes the new comparison point, so a provider can refresh repeatedly. Re-sending an unchanged model list is a replay rather than an update and is ignored. Refreshed models are visible to sessions immediately; no restart and no network request is involved, because the extension has already produced the catalog.
313
+ An extension may refresh an existing provider's **model list** without a restart, provided all other provider settings remain unchanged. Changes to credentials, connection settings, or provider implementation require a daemon restart. Accepted model-list updates are available to sessions immediately.
279
314
 
280
315
  Model lists are shared daemon-wide state. If extensions in two workspaces register different model lists for the same provider ID, the last registration wins. A model entry may also carry its own `baseUrl` and `headers`, which take precedence over the provider-level values for that model, so an accepted refresh can change where requests for those models are sent. Both are accepted trade-offs: a catalog is treated as a property of the provider rather than of the project, and Pi extensions are trusted daemon code.
281
316
 
282
317
  #### Provider decisions in the daemon log
283
318
 
284
- Ignored mutations are written to the session-daemon log once per operation and provider ID, so a replaying extension cannot flood the log. Applied model list refreshes are logged every time, with the resulting model count, because each one changes shared runtime state. Neither entry contains provider configuration or credentials, and PI WEB does not show a session warning or notification.
319
+ Check the session-daemon log for ignored provider changes and applied model-list refreshes; these do not produce browser notifications. Log entries omit provider configuration and credentials.
285
320
 
286
321
  This prevents accidental provider, configuration, or credential contamination between projects; it is not a security boundary because Pi extensions remain trusted daemon code.
287
322
 
@@ -329,11 +364,7 @@ Tracked subsessions are join-oriented. Calling `spawn_subsession` returns immedi
329
364
 
330
365
  A tracked subsession always runs in the spawning session's working directory, so it stays in that workspace's session tree next to its parent. `spawn_subsession` takes no `cwd`. To get work done elsewhere, instruct the child to work there from this workspace, or use `spawn_session`, which still targets any workspace of the project, for an independent session there.
331
366
 
332
- At a join point, after finishing its independent work, the parent calls `yield_to_subsessions` alone as the final action in its tool batch. Pi ends a tool batch early only when every result in that batch is terminating. If any tracked child is still working, the action ends the current agent run so the parent becomes idle. If none are working, it does not end the run and clearly reports that there is nothing to wait for.
333
-
334
- A completion notice wakes an idle parent or queues behind in-flight work. Each notice lists any other tracked children still working, so the parent can continue work or call `yield_to_subsessions` again at the next join point. Further notices arrive automatically; do not poll. The notice includes the child's final output when it fits. If that output is too long, PI WEB omits it entirely instead of adding a truncated duplicate to the parent's context and directs the parent to retrieve it with `check_subsession`.
335
-
336
- `list_subsessions`, `check_subsession`, and `read_subsession` never yield or change control flow. They are for deliberate inspection or recovery, not completion polling. While a child works, agent-facing `check_subsession` and `read_subsession` withhold partial output and direct the parent to continue independent work or yield at the join point. Output becomes available when the child stops. Included output and transcripts follow a labeled marker and come last, after PI WEB guidance.
367
+ The parent can continue independent work or wait for its tracked children. Completion notices arrive automatically and wake an idle parent; no polling is needed. Child output is available to the parent when the child stops.
337
368
 
338
369
  Both `spawn_session` and `spawn_subsession` accept an optional `model` parameter, given as an exact `provider/model-id` such as `anthropic/claude-sonnet-4-5`. When set, the new session starts on that model instead of inheriting the dispatching session's model. The match is strict: an unknown or malformed value is rejected with an error. A `#provider/model-id` reference in the prompt (see [Prompt completions](#prompt-completions)) is how users ask for a specific model; agents forward that reference as this parameter. The new session also inherits the dispatching session's thinking level, clamped to its model's capabilities.
339
370
 
@@ -345,7 +376,7 @@ In **Settings → Session daemon**, these keys are saved on the selected machine
345
376
 
346
377
  Use **Settings → Session daemon → Allow agents to ask questions** to change `askUser` on the selected machine. An environment override makes the toggle read-only.
347
378
 
348
- The tool accepts one set of 1–20 questions. Each question has a unique `id`, its `question` text, optional supporting `detail`, up to 12 options with stable values and user-facing labels, and an optional `multiple` flag. The browser always adds a **Custom** free-text answer, including when the model supplies no options. No question is required: the user may leave any of them unanswered.
379
+ Agents can post a form with 1–20 questions, with free-text answers or up to 12 choices per question. Some questions allow multiple selections. A **Custom** free-text answer is always available, and you may leave any question unanswered.
349
380
 
350
381
  Calling `ask_user` posts the whole set as one browser form and ends the current agent run instead of waiting for the user. The open form is owned by the session daemon, so it survives a browser disconnect, browser reload, or web/API restart while that daemon keeps running. When the user submits, the answers arrive as a follow-up that wakes the session; each question is reported with its selected option values or free text, or explicitly as unanswered.
351
382
 
@@ -357,7 +388,7 @@ Restart the session daemon after changing `askUser` or after upgrading PI WEB to
357
388
 
358
389
  ### Extension dialogs
359
390
 
360
- Pi extensions can ask the user questions from `ctx.ui.confirm()`, `ctx.ui.select()`, and `ctx.ui.input()` — including from `session_start` hooks and in-flight `tool_call` hooks. PI WEB renders these dialogs inline in the session transcript and answers them through a dedicated session-daemon channel, never the prompt queue, so a dialog parked inside a `tool_call` hook cannot deadlock the run. Dialog support is always on; there is no enable flag. See [Pi extension dialogs in PI WEB](https://pi-web.dev/plugins#pi-extension-dialogs) for behavior details and author guidance.
391
+ Pi extensions can show confirmation, selection, and text-input dialogs inline in the session transcript, including while a session starts or a tool runs. Dialog support is always on; there is no enable flag. See [Pi extension dialogs in PI WEB](https://pi-web.dev/plugins#pi-extension-dialogs) for details.
361
392
 
362
393
  `extensionDialogsTimeoutMs` is the unattended-dialog safety valve: how long the session daemon waits for an answer before settling the dialog with its kind's cancel value (`false` for confirm, `undefined` for select and input). It defaults to `300000` (5 minutes); set it to `0` to wait forever. An extension's own `timeout` option still applies, and the effective deadline is the sooner of the two.
363
394
 
@@ -377,17 +408,17 @@ The `plugins` key controls desired enablement and JSON settings for PI WEB brows
377
408
  }
378
409
  ```
379
410
 
380
- Plugins are enabled by default. `plugins.<id>.enabled: false` hides a browser-only entry on the next page load. For a server-backed entry, desired disablement takes effect on the next sessiond start; its paired browser entry continues to follow the still-active backend until that restart. Server settings are copied into sessiond's startup snapshot, and diagnostics expose only a fingerprint, never the values.
411
+ Plugins are enabled by default unless their package metadata declares `defaultEnabled: false`, as Captain's Log does. Explicit `plugins.<id>.enabled` config overrides the package default. `plugins.<id>.enabled: false` hides a browser-only entry on the next page load. For a server-backed entry, desired disablement takes effect on the next sessiond start; its paired browser entry continues to follow the still-active server entry until that restart. The bundled `pi-web.terminal` plugin is required during normal startup: ordinary config cannot disable it, and Settings renders it non-editable. Server settings take effect at daemon startup; diagnostics do not expose their values.
381
412
 
382
413
  #### Desired versus active plugin state
383
414
 
384
- Sessiond is the single workspace authority and resolves one immutable server-plugin/provider snapshot when it starts. Saving `plugins` config or replacing package files changes **desired** state but does not hot-reload, unload, or replace active server code. The old provider and its paired browser entry can remain active until a restart after desired disablement. A paired browser entry is withheld when desired source, scope, settings fingerprint, browser revision, or server revision differs from the active snapshot, or when active health/lifecycle compatibility is unsuitable.
415
+ Saving `plugins` config or replacing package files changes **desired** state, not the running server code. A disabled server plugin and its paired UI may remain active until the session daemon restarts. PI WEB withholds a paired UI when its package/settings no longer match the running server code or the server plugin is unhealthy or incompatible.
385
416
 
386
- **Settings → PI WEB plugins** shows desired and active state separately, including active, failed, incompatible, disabled, not-active/missing, unknown, conflict, stale-revision, health, safe-mode, and restart-required state. Desired config remains editable when sessiond is unavailable as long as the selected machine's config endpoint works, but PI WEB reports active state as unavailable rather than constructing a second workspace authority.
417
+ **Settings → PI WEB plugins** distinguishes desired from active state and shows failures, compatibility problems, safe mode, and required restarts. If the daemon is unavailable, desired config may still be editable, but active state is unavailable.
387
418
 
388
- For machine federation, the panel targets the selected machine. Remote desired state is saved in that target's config and active state comes from that target's sessiond through the gateway. If the versioned plugin lifecycle, the remote manifest, or provider backend routes are unavailable/incompatible, PI WEB reports an explicit unsupported or compatibility error and does not silently use gateway config/code.
419
+ In Fleet, this panel targets the selected machine. Unsupported or incompatible remote plugin features report errors rather than using gateway config or code.
389
420
 
390
- Mixed-version plugin/provider operation is not supported in either upgrade order. A newer gateway rejects an older target's whole remote plugin manifest, including browser-only contributions, when the target lacks the current lifecycle contract; its Git panel is therefore unavailable. An older gateway still calls legacy core Git routes removed by an updated target, so remote Git status/diff returns `404`. Upgrade gateway and target together, restart their updated web/API processes and the target session daemon, then reload the browser. Other selected-machine settings and features report their own explicit errors.
421
+ Mixed-version plugin operation is unsupported in either upgrade order. Remote plugins, including Git, may be unavailable or return `404`. Upgrade gateway and target together, restart their updated web/API processes and the target session daemon, then reload the browser. Other selected-machine features may report their own compatibility errors.
391
422
 
392
423
  Apply changes in this order:
393
424
 
@@ -410,7 +441,7 @@ pi-web plugins safe-start set none --restart
410
441
  pi-web plugins safe-start clear --restart
411
442
  ```
412
443
 
413
- `disable` persists `plugins.<id>.enabled: false`. Safe-start state is stored under `serverPlugins.safeStart`: `bundled-only` filters external server packages before discovery/import, while `none` imports no server plugins and retains the kernel project-folder workspace. `clear` restores ordinary configured discovery on the next start. An unsupported `serverPlugins.safeStart` shape or value in otherwise valid JSON fails closed as effective `none`; use `safe-start show`, then `set` or `clear`, to repair it offline.
444
+ `disable` persists `plugins.<id>.enabled: false`, but rejects required `pi-web.terminal` with no-plugin safe-start recovery guidance. Safe-start state is stored under `serverPlugins.safeStart`: `bundled-only` filters external server packages before discovery/import while still requiring bundled Terminal, whereas `none` imports no server plugins and retains the kernel project-folder and diagnosis/settings surfaces without Terminal or Terminal-backed commands. `clear` restores ordinary configured discovery on the next start. An unsupported `serverPlugins.safeStart` shape or value in otherwise valid JSON fails closed as effective `none`; use `safe-start show`, then `set` or `clear`, to repair it offline.
414
445
 
415
446
  `--restart` performs a restart only for a recognized safe installed-service plan; otherwise it prints manual instructions. The config mutation is durable before PI WEB attempts the restart. If the service-manager command itself fails, restart sessiond manually.
416
447
 
@@ -418,18 +449,27 @@ Ordinary import/activation/start/health failures are quarantined when possible,
418
449
 
419
450
  ### Shortcut config
420
451
 
421
- Shortcut values are keyed by action id. Values are shortcut strings such as `mod+k` or `mod+g p`; `null` disables that action's shortcut.
452
+ Shortcut values are keyed by action id. Values are shortcut strings such as `mod+k`, `g p`, or `shift+enter`; `null` disables that action's shortcut.
422
453
 
423
454
  ```json
424
455
  {
425
456
  "shortcuts": {
426
457
  "core:view.chat": "mod+1",
427
- "core:session.stop": null
458
+ "core:session.stop": null,
459
+ "app.navigation.focus-projects": "g p",
460
+ "composer.send.desktop": "mod+enter",
461
+ "composer.send.mobile": "shift+enter"
428
462
  }
429
463
  }
430
464
  ```
431
465
 
432
- Prefer Settings → Keyboard for editing shortcuts interactively.
466
+ Prefer Settings → Keyboard for editing, recording, disabling, or resetting shortcuts. `mod` accepts Ctrl or ⌘. Browsers and operating systems may reserve some combinations.
467
+
468
+ App shortcuts can be single keys or sequences. Unmodified and Shift-only shortcuts do not start inside inputs, textareas, selects, or contenteditable editors. Sequences expire after 1.2 seconds; Escape or a focus change cancels them. Custom bindings win over defaults; ties resolve by action id. A shorter binding shadows sequences with that prefix (for example, `g` shadows `g p`).
469
+
470
+ The two **Chat composer** send bindings accept one key combination each, not sequences. **Send message — desktop** defaults to Enter; **Send message — touch or narrow screen** defaults to Shift+Enter. The latter applies when the browser reports a coarse primary pointer (typically touch) or a viewport at most 760px wide; otherwise, desktop applies. Its config key remains `composer.send.mobile`. Enter and Shift+Enter insert newlines when not assigned to send. Composer send bindings take priority over app shortcuts only inside the message editor, even when the draft is empty or sending is unavailable. Plain Enter accepts a selected completion first. `null` disables keyboard submission for that context; the send button remains available.
471
+
472
+ Existing browser-local Enter preferences remain the fallback until the corresponding composer binding is configured. Reset removes the override and returns to that fallback. New bindings are saved in the gateway config, like other shortcuts; the old preference is not copied into shared configuration. Automatic touch-keyboard capitalization is ignored when interpreting Shift+Enter.
433
473
 
434
474
  ## Prompt completions
435
475