@bastani/atomic 0.9.5-alpha.8 → 0.9.5-alpha.9

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 (207) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/CHANGELOG.md +11 -0
  5. package/dist/builtin/mcp/README.md +6 -6
  6. package/dist/builtin/mcp/direct-tools.ts +41 -23
  7. package/dist/builtin/mcp/index.ts +31 -4
  8. package/dist/builtin/mcp/init.ts +4 -43
  9. package/dist/builtin/mcp/metadata-hydration.ts +37 -0
  10. package/dist/builtin/mcp/package.json +1 -1
  11. package/dist/builtin/mcp/proxy-info-modes.ts +67 -14
  12. package/dist/builtin/mcp/startup-warmup.ts +98 -0
  13. package/dist/builtin/mcp/tool-result-renderer.ts +3 -3
  14. package/dist/builtin/subagents/CHANGELOG.md +12 -0
  15. package/dist/builtin/subagents/package.json +1 -1
  16. package/dist/builtin/subagents/src/extension/index.ts +14 -25
  17. package/dist/builtin/subagents/src/extension/startup-maintenance.ts +89 -0
  18. package/dist/builtin/subagents/src/runs/background/async-execution-common.ts +23 -18
  19. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +20 -10
  20. package/dist/builtin/subagents/src/runs/background/subagent-runner-streaming.ts +13 -3
  21. package/dist/builtin/subagents/src/runs/foreground/execution-attempt.ts +10 -2
  22. package/dist/builtin/subagents/src/runs/shared/pi-spawn.ts +28 -1
  23. package/dist/builtin/subagents/src/tui/render-widget.ts +29 -0
  24. package/dist/builtin/web-access/package.json +1 -1
  25. package/dist/builtin/workflows/CHANGELOG.md +18 -0
  26. package/dist/builtin/workflows/README.md +1 -1
  27. package/dist/builtin/workflows/builtin/goal-runner.ts +39 -1
  28. package/dist/builtin/workflows/package.json +1 -1
  29. package/dist/builtin/workflows/skills/impeccable/SKILL.md +16 -13
  30. package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +1 -0
  31. package/dist/builtin/workflows/skills/impeccable/reference/adapt.native.md +58 -0
  32. package/dist/builtin/workflows/skills/impeccable/reference/android.md +40 -0
  33. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +2 -0
  34. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +2 -0
  35. package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +139 -0
  36. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +69 -62
  37. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +21 -7
  38. package/dist/builtin/workflows/skills/impeccable/reference/document.md +1 -1
  39. package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +92 -0
  40. package/dist/builtin/workflows/skills/impeccable/reference/init.md +29 -7
  41. package/dist/builtin/workflows/skills/impeccable/reference/ios.md +45 -0
  42. package/dist/builtin/workflows/skills/impeccable/reference/layout.md +25 -1
  43. package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +23 -1
  44. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +2 -1
  45. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +75 -14
  46. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1 -1
  47. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +31 -0
  48. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +64 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +118 -11
  50. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +16 -11
  51. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +66 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +42 -10
  53. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/fonts.mjs +30 -0
  54. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +4 -6
  55. package/dist/builtin/workflows/skills/impeccable/scripts/hook-admin.mjs +660 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/hook-before-edit.mjs +516 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/hook-lib.mjs +1764 -0
  58. package/dist/builtin/workflows/skills/impeccable/scripts/hook.mjs +61 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +4 -2
  60. package/dist/builtin/workflows/skills/impeccable/scripts/lib/is-generated.mjs +2 -2
  61. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +13 -16
  62. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +17 -11
  63. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +104 -37
  64. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +9 -0
  65. package/dist/builtin/workflows/src/durable/backend.ts +12 -0
  66. package/dist/builtin/workflows/src/durable/dbos-backend.ts +17 -1
  67. package/dist/builtin/workflows/src/durable/resume-catalog.ts +12 -0
  68. package/dist/builtin/workflows/src/durable/resume-runtime.ts +5 -2
  69. package/dist/builtin/workflows/src/durable/types.ts +16 -0
  70. package/dist/builtin/workflows/src/engine/primitives/task.ts +1 -0
  71. package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +1 -2
  72. package/dist/builtin/workflows/src/engine/run-returned-status.ts +50 -16
  73. package/dist/builtin/workflows/src/engine/run.ts +20 -19
  74. package/dist/builtin/workflows/src/engine/runtime.ts +5 -0
  75. package/dist/builtin/workflows/src/extension/extension-factory.ts +3 -0
  76. package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +9 -2
  77. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +137 -27
  78. package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +22 -6
  79. package/dist/builtin/workflows/src/extension/public-types.ts +1 -1
  80. package/dist/builtin/workflows/src/extension/workflow-command-completions.ts +13 -1
  81. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +19 -4
  82. package/dist/builtin/workflows/src/extension/workflow-command-surfaces.ts +9 -0
  83. package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +48 -23
  84. package/dist/builtin/workflows/src/extension/workflow-tool-control.ts +20 -8
  85. package/dist/builtin/workflows/src/extension/workflow-tool.ts +24 -8
  86. package/dist/builtin/workflows/src/runs/background/status.ts +4 -3
  87. package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +29 -8
  88. package/dist/builtin/workflows/src/runs/foreground/executor-hil.ts +2 -1
  89. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +3 -2
  90. package/dist/builtin/workflows/src/runs/shared/worktree-git.ts +155 -20
  91. package/dist/builtin/workflows/src/runs/shared/worktree-types.ts +6 -0
  92. package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -1
  93. package/dist/builtin/workflows/src/shared/returned-run-status.ts +116 -0
  94. package/dist/builtin/workflows/src/shared/workflow-failures-decisions.ts +2 -0
  95. package/dist/builtin/workflows/src/tui/graph-view-render-helpers.ts +21 -3
  96. package/dist/builtin/workflows/src/tui/graph-view-state.ts +3 -0
  97. package/dist/builtin/workflows/src/tui/graph-view-types.ts +3 -0
  98. package/dist/builtin/workflows/src/tui/status-list.ts +22 -15
  99. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +4 -4
  100. package/dist/core/agent-session-auto-compaction.d.ts +23 -3
  101. package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
  102. package/dist/core/agent-session-auto-compaction.js +129 -17
  103. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  104. package/dist/core/agent-session-events.d.ts.map +1 -1
  105. package/dist/core/agent-session-events.js +7 -0
  106. package/dist/core/agent-session-events.js.map +1 -1
  107. package/dist/core/agent-session-methods.d.ts +6 -3
  108. package/dist/core/agent-session-methods.d.ts.map +1 -1
  109. package/dist/core/agent-session-methods.js.map +1 -1
  110. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  111. package/dist/core/agent-session-prompt.js +1 -1
  112. package/dist/core/agent-session-prompt.js.map +1 -1
  113. package/dist/core/agent-session.d.ts +4 -2
  114. package/dist/core/agent-session.d.ts.map +1 -1
  115. package/dist/core/agent-session.js +4 -2
  116. package/dist/core/agent-session.js.map +1 -1
  117. package/dist/core/extensions/loader-virtual-modules.d.ts +8 -1
  118. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  119. package/dist/core/extensions/loader-virtual-modules.js +2 -1
  120. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  121. package/dist/core/footer-data-provider.d.ts +6 -0
  122. package/dist/core/footer-data-provider.d.ts.map +1 -1
  123. package/dist/core/footer-data-provider.js +72 -29
  124. package/dist/core/footer-data-provider.js.map +1 -1
  125. package/dist/core/openai-responses-payload-sanitizer.d.ts +1 -0
  126. package/dist/core/openai-responses-payload-sanitizer.d.ts.map +1 -1
  127. package/dist/core/openai-responses-payload-sanitizer.js +24 -9
  128. package/dist/core/openai-responses-payload-sanitizer.js.map +1 -1
  129. package/dist/core/slash-commands.d.ts +2 -0
  130. package/dist/core/slash-commands.d.ts.map +1 -1
  131. package/dist/core/slash-commands.js +141 -0
  132. package/dist/core/slash-commands.js.map +1 -1
  133. package/dist/index.d.ts +1 -0
  134. package/dist/index.d.ts.map +1 -1
  135. package/dist/index.js +1 -0
  136. package/dist/index.js.map +1 -1
  137. package/dist/main-deferred-startup.d.ts.map +1 -1
  138. package/dist/main-deferred-startup.js +2 -2
  139. package/dist/main-deferred-startup.js.map +1 -1
  140. package/dist/main-early-input.d.ts.map +1 -1
  141. package/dist/main-early-input.js +7 -0
  142. package/dist/main-early-input.js.map +1 -1
  143. package/dist/main.d.ts.map +1 -1
  144. package/dist/main.js +1 -1
  145. package/dist/main.js.map +1 -1
  146. package/dist/modes/interactive/components/chat-session-host-actions.d.ts.map +1 -1
  147. package/dist/modes/interactive/components/chat-session-host-actions.js +0 -1
  148. package/dist/modes/interactive/components/chat-session-host-actions.js.map +1 -1
  149. package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
  150. package/dist/modes/interactive/interactive-agent-events.js +1 -0
  151. package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
  152. package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  153. package/dist/modes/interactive/interactive-autocomplete.js +21 -9
  154. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  155. package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
  156. package/dist/modes/interactive/interactive-deferred-startup.js +41 -28
  157. package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
  158. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  159. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -0
  160. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  161. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  162. package/dist/modes/interactive/interactive-input-handling.js +41 -6
  163. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  164. package/dist/modes/interactive/interactive-mode-base.d.ts +3 -0
  165. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  166. package/dist/modes/interactive/interactive-mode-base.js +3 -0
  167. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  168. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  169. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  170. package/dist/modes/interactive/interactive-mode-deps.js +1 -1
  171. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  172. package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -2
  173. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  174. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  175. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  176. package/dist/modes/interactive/interactive-model-routing.js +3 -1
  177. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  178. package/dist/modes/interactive/interactive-process-lifecycle.d.ts.map +1 -1
  179. package/dist/modes/interactive/interactive-process-lifecycle.js +0 -3
  180. package/dist/modes/interactive/interactive-process-lifecycle.js.map +1 -1
  181. package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
  182. package/dist/modes/interactive/interactive-render-chat.js +15 -1
  183. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  184. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  185. package/dist/modes/interactive/interactive-session-routing.js +5 -1
  186. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  187. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  188. package/dist/modes/interactive/interactive-startup.js +20 -21
  189. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  190. package/dist/modes/interactive/theme/global-theme.d.ts.map +1 -1
  191. package/dist/modes/interactive/theme/global-theme.js +26 -2
  192. package/dist/modes/interactive/theme/global-theme.js.map +1 -1
  193. package/dist/utils/fs-watch.d.ts +24 -1
  194. package/dist/utils/fs-watch.d.ts.map +1 -1
  195. package/dist/utils/fs-watch.js +60 -5
  196. package/dist/utils/fs-watch.js.map +1 -1
  197. package/docs/compaction.md +5 -1
  198. package/docs/development.md +4 -0
  199. package/docs/extensions.md +5 -0
  200. package/docs/json.md +1 -1
  201. package/docs/quickstart.md +1 -1
  202. package/docs/settings.md +1 -1
  203. package/docs/subagents.md +4 -0
  204. package/docs/windows.md +4 -0
  205. package/docs/workflows.md +8 -6
  206. package/npm-shrinkwrap.json +23 -23
  207. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.5-alpha.9] - 2026-07-09
6
+
7
+ ### Changed
8
+
9
+ - Improved interactive startup responsiveness by keeping footer git watcher setup out of first paint and deferring it until the first input handler is ready, while preserving lazy footer branch reads and post-frame branch-change re-rendering. Deferred extension/resource loading no longer starts on a blind post-paint timer that can freeze live typing; instead, Atomic starts it in the background once input is ready and uses a readiness gate before the first normal prompt/model turn if the background load has not settled. This keeps first-paint/typeahead responsive while ensuring the first interactive prompt sees extension tools, skills, prompt templates, resources, and extension-registered provider/model updates. Built-in slash commands stay available immediately, and bundled extension slash commands expose lightweight metadata for autocomplete before their heavy implementations load; explicitly submitted extension slash commands load the implementation on demand and then route the same command. Explicit provider/model selections stay on the synchronous startup path because they must resolve against extension-registered providers before the session is created. Changelog and first-run notice materialization now happen after the first frame, and startup/input timing probes cover raw-mode enablement, first captured raw key, first frame, input-handler callback installation, and first submit for benchmark/probe runs.
10
+ - Documented the built-in extension lazy-startup model: lightweight tools/commands register immediately while MCP connection warmup, workflow discovery, subagent maintenance, and web-access heavy provider loading run in the background or on explicit use.
11
+
12
+ ### Fixed
13
+
14
+ - Fixed Windows native filesystem watchers to canonicalize watched paths before calling `fs.watch`, reject unresolved 8.3 short-name paths, and fall back to polling for unsafe watcher paths. This prevents libuv fs-event assertion crashes when temp, session, footer, or theme paths contain short-name components such as `USERNA~1`.
15
+ - Fixed responses truncated at the output-token cap (`stopReason: "length"`) dead-ending the model's work with a "maximum output token limit" error when the context was still below the auto-compaction threshold. Previously auto-continuation only happened as a side effect of threshold compaction, so a length truncation with input room to spare left the task half-finished. Length-truncated turns now continue directly without compacting: the incomplete assistant is removed from retry context and the generation resumes automatically so the model finishes where it left off, bounded by a small consecutive-continuation cap so a turn that keeps exceeding the per-turn output cap still terminates. Compaction-driven `willRetry: true` continuations are now consistently tracked through the normal prompt lifecycle, so `AgentSession.prompt()` waits for threshold length-stop continuation before resolving instead of treating it as fire-and-forget.
16
+ - Fixed auto-compaction after OpenAI Responses output-budget underflow errors such as `Invalid 'max_output_tokens': integer below minimum value. Expected a value >= 16, but got 1 instead.` Atomic now classifies that specific context-pressure failure as retry-worthy when the live context crosses the compaction threshold, removes the empty error assistant from retry context after compaction, and automatically continues from the preceding work anchor instead of waiting for the user to type `Continue`. Generic `invalid_request_body` errors such as malformed tool schemas are still not auto-retried, OpenAI Responses payload sanitization now prevents `max_output_tokens` values below the provider minimum of 16 from being sent, and repeated output-budget underflow continuation is capped at one compact-and-retry attempt so an unrecoverable context terminates visibly instead of looping or stalling.
17
+ - Fixed bundled workflow durable resume for reusable `git_worktree_dir` worktrees so resumed runs reuse the original invocation repository/cwd and report slow Git subprocess timeouts as Git timeouts instead of repository-detection failures.
18
+ - Removed the queued-message restore success status from the interactive UI so restoring queued messages silently returns them to the editor without showing transient restore copy.
19
+ - Fixed bundled MCP proxy metadata paths so explicit cold-cache `search`, `describe`, and server-list requests hydrate lazy server metadata on demand without reconnecting all lazy servers during startup.
20
+
5
21
  ## [0.9.5-alpha.8] - 2026-07-08
6
22
 
7
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/cursor",
3
- "version": "0.9.5-alpha.8",
3
+ "version": "0.9.5-alpha.9",
4
4
  "private": true,
5
5
  "description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
6
6
  "contributors": [
@@ -40,7 +40,7 @@
40
40
  }
41
41
  },
42
42
  "dependencies": {
43
- "@bastani/atomic-natives": "0.9.5-alpha.8",
43
+ "@bastani/atomic-natives": "0.9.5-alpha.9",
44
44
  "@bufbuild/protobuf": "^2.12.1",
45
45
  "@earendil-works/pi-ai": "^0.80.3"
46
46
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.5-alpha.8",
3
+ "version": "0.9.5-alpha.9",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.5-alpha.9] - 2026-07-09
11
+
12
+ ### Changed
13
+
14
+ - Improved startup responsiveness by keeping first-run/default lazy MCP metadata bootstrap out of `initializeMcp()`'s synchronous path. Cached direct tools and the MCP proxy still register immediately, explicit `eager`/`keep-alive` servers still connect during initialization, missing configured or `MCP_DIRECT_TOOLS`-selected direct-tool metadata is warmed in the background, and explicit proxy `search`, `describe`, and server-list requests hydrate cold-cache lazy server metadata on demand instead of broadening startup warmup. Warmed direct tools are registered live in the current session after metadata refresh rather than requiring restart. Cold-cache `describe` first narrows hydration to prefix-matched or explicitly requested servers, treats hyphen/underscore prefixes as aliases, and does not fan out to unrelated lazy servers after a prefix-directed miss; cold-cache proxy `search` intentionally hydrates all matching lazy servers so unscoped searches can see every configured tool.
15
+
16
+ ### Fixed
17
+
18
+ - Aligned MCP tool result expansion hints with the CLI-wide `Ctrl+o` keybinding copy.
19
+ - Fixed MCP background direct-tool warmup cancellation so stale in-flight connects are discarded before metadata/cache mutation, direct-tool refresh callbacks are guarded to the active session, completed warmups clear their active handle safely, and env-selected direct tool servers hydrate without forcing every lazy server eager.
20
+
10
21
  ## [0.9.4] - 2026-07-03
11
22
 
12
23
  ### Changed
@@ -65,7 +65,7 @@ Precedence is:
65
65
  3. `.mcp.json`
66
66
  4. `.pi/mcp.json`
67
67
 
68
- Servers are **lazy by default** — they won't connect until you actually call one of their tools. The adapter caches tool metadata so search and describe work without live connections.
68
+ Servers are **lazy by default** — they won't connect until you actually call one of their tools. The adapter caches tool metadata so search and describe work without live connections when a valid cache exists; on a cold cache, explicit proxy search/describe/server-list requests may connect lazy server(s) once to hydrate metadata on demand. `describe` narrows cold-cache hydration to an explicitly requested server or the server identified by the configured tool-name prefix before falling back to broader discovery. Unscoped `search` intentionally hydrates every configured lazy server that lacks cached metadata so the search can see all available tools.
69
69
 
70
70
  ```
71
71
  mcp({ search: "screenshot" })
@@ -136,7 +136,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
136
136
 
137
137
  ### Lifecycle Modes
138
138
 
139
- - **`lazy`** (default) — Don't connect at startup. Connect on first tool call. Disconnect after idle timeout. Cached metadata keeps search/list working without connections.
139
+ - **`lazy`** (default) — Don't connect at startup. Connect on first tool call or explicit cold-cache proxy metadata request (`search`, `describe`, or `server` list). Disconnect after idle timeout. Cached metadata keeps search/list working without connections.
140
140
  - **`eager`** — Connect at startup but don't auto-reconnect if the connection drops. No idle timeout by default (set `idleTimeout` explicitly to enable).
141
141
  - **`keep-alive`** — Connect at startup. Auto-reconnect via health checks. No idle timeout. Use for servers you always need available.
142
142
 
@@ -232,7 +232,7 @@ To exclude specific tools while still using `directTools: true`, add `excludeToo
232
232
 
233
233
  Each direct tool costs ~150-300 tokens in the system prompt (name + description + schema). Good for targeted sets of 5-20 tools. For servers with 75+ tools, stick with the proxy or pick specific tools with a `string[]`.
234
234
 
235
- Direct tools register from the metadata cache in the Pi agent dir (`~/.pi/agent/mcp-cache.json` by default, or `$PI_CODING_AGENT_DIR/mcp-cache.json` when set), so no server connections are needed at startup. On the first session after adding `directTools` to a new server, the cache won't exist yet — tools fall back to proxy-only and the cache populates in the background. To force it: `/mcp reconnect <server>`.
235
+ Direct tools register from the metadata cache in the Pi agent dir (`~/.pi/agent/mcp-cache.json` by default, or `$PI_CODING_AGENT_DIR/mcp-cache.json` when set), so no server connections are needed at startup when the cache is warm. On the first session after adding `directTools` to a new server, or when a child/subagent selects tools through `MCP_DIRECT_TOOLS`, the cache may not exist yet — tools fall back to proxy-only while the selected/configured direct-tool servers populate in the background, then the extension refreshes tool registration so the warmed direct tools become available in the current session. To force it immediately: `/mcp reconnect <server>`.
236
236
 
237
237
  When you change direct-tool toggles in `/mcp` or write new config through `/mcp setup`, the extension triggers Pi's normal reload flow automatically. That refreshes extensions, prompts, skills, and MCP tool registration in one shot, so newly configured direct tools can appear without a manual restart.
238
238
 
@@ -332,7 +332,7 @@ Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only
332
332
  | Connect | `mcp({ connect: "server-name" })` |
333
333
  | UI messages | `mcp({ action: "ui-messages" })` |
334
334
 
335
- MCP proxy and direct-tool results render compactly by default: long text shows the first three lines plus a `CTRL+O Expand` hint, while the full result remains available when expanded and is still returned unchanged to the model.
335
+ MCP proxy and direct-tool results render compactly by default: long text shows the first three lines plus a `Ctrl+o Expand` hint, while the full result remains available when expanded and is still returned unchanged to the model.
336
336
 
337
337
  Search includes both MCP tools and Pi tools (from extensions). Pi tools appear first with `[pi tool]` prefix. Space-separated words are OR'd.
338
338
 
@@ -358,8 +358,8 @@ In interactive sessions, you can also authenticate from `/mcp` with `CTRL+A` or
358
358
  ## How It Works
359
359
 
360
360
  - One `mcp` tool in context (~200 tokens) instead of hundreds
361
- - Servers are lazy by default — they connect on first tool call, not at startup
362
- - Tool metadata is cached to disk so search/list/describe work without live connections
361
+ - Servers are lazy by default — they connect on first tool call or explicit cold-cache proxy metadata request, not at startup
362
+ - Tool metadata is cached to disk so search/list/describe work without live connections when the cache is valid; cold-cache explicit search/describe/server-list requests hydrate metadata on demand
363
363
  - Idle servers disconnect after 10 minutes (configurable), reconnect automatically on next use
364
364
  - npx-based servers resolve to direct binary paths, skipping the ~143 MB npm parent process
365
365
  - MCP server validates arguments, not the adapter
@@ -72,6 +72,37 @@ async function attemptDirectAutoAuth(
72
72
  }
73
73
  }
74
74
 
75
+ interface DirectToolSelection {
76
+ readonly servers: ReadonlySet<string>;
77
+ readonly toolsByServer: ReadonlyMap<string, ReadonlySet<string>>;
78
+ }
79
+
80
+ function parseDirectToolSelection(items: readonly string[] | undefined): DirectToolSelection {
81
+ const servers = new Set<string>();
82
+ const toolsByServer = new Map<string, Set<string>>();
83
+ for (const rawItem of items ?? []) {
84
+ const item = rawItem.replace(/\/+$/, "");
85
+ if (!item) continue;
86
+ if (!item.includes("/")) {
87
+ servers.add(item);
88
+ continue;
89
+ }
90
+ const [server, tool] = item.split("/", 2);
91
+ if (!server) continue;
92
+ if (!tool) {
93
+ servers.add(server);
94
+ continue;
95
+ }
96
+ if (!toolsByServer.has(server)) toolsByServer.set(server, new Set());
97
+ toolsByServer.get(server)!.add(tool);
98
+ }
99
+ return { servers, toolsByServer };
100
+ }
101
+
102
+ function directToolSelectionIncludes(selection: DirectToolSelection, serverName: string): boolean {
103
+ return selection.servers.has(serverName) || selection.toolsByServer.has(serverName);
104
+ }
105
+
75
106
  export function resolveDirectTools(
76
107
  config: McpConfig,
77
108
  cache: MetadataCache | null,
@@ -83,24 +114,7 @@ export function resolveDirectTools(
83
114
 
84
115
  const seenNames = new Set<string>();
85
116
 
86
- const envServers = new Set<string>();
87
- const envTools = new Map<string, Set<string>>();
88
- if (envOverride) {
89
- for (let item of envOverride) {
90
- item = item.replace(/\/+$/, "");
91
- if (item.includes("/")) {
92
- const [server, tool] = item.split("/", 2);
93
- if (server && tool) {
94
- if (!envTools.has(server)) envTools.set(server, new Set());
95
- envTools.get(server)!.add(tool);
96
- } else if (server) {
97
- envServers.add(server);
98
- }
99
- } else if (item) {
100
- envServers.add(item);
101
- }
102
- }
103
- }
117
+ const envSelection = parseDirectToolSelection(envOverride);
104
118
 
105
119
  const globalDirect = config.settings?.directTools;
106
120
 
@@ -111,10 +125,10 @@ export function resolveDirectTools(
111
125
  let toolFilter: true | string[] | false = false;
112
126
 
113
127
  if (envOverride) {
114
- if (envServers.has(serverName)) {
128
+ if (envSelection.servers.has(serverName)) {
115
129
  toolFilter = true;
116
- } else if (envTools.has(serverName)) {
117
- toolFilter = [...envTools.get(serverName)!];
130
+ } else if (envSelection.toolsByServer.has(serverName)) {
131
+ toolFilter = [...envSelection.toolsByServer.get(serverName)!];
118
132
  }
119
133
  } else {
120
134
  if (definition.directTools !== undefined) {
@@ -182,14 +196,18 @@ export function resolveDirectTools(
182
196
  export function getMissingConfiguredDirectToolServers(
183
197
  config: McpConfig,
184
198
  cache: MetadataCache | null,
199
+ envOverride?: readonly string[],
185
200
  ): string[] {
186
201
  const missing: string[] = [];
187
202
  const globalDirect = config.settings?.directTools;
188
-
203
+ const envSelection = parseDirectToolSelection(envOverride);
189
204
  for (const [serverName, definition] of Object.entries(config.mcpServers)) {
190
- const hasDirectTools = definition.directTools !== undefined
205
+ const hasConfiguredDirectTools = definition.directTools !== undefined
191
206
  ? !!definition.directTools
192
207
  : !!globalDirect;
208
+ const hasDirectTools = envOverride
209
+ ? directToolSelectionIncludes(envSelection, serverName)
210
+ : hasConfiguredDirectTools;
193
211
 
194
212
  if (!hasDirectTools) continue;
195
213
 
@@ -39,6 +39,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
39
39
  let lifecycleGeneration = 0;
40
40
  let registeredDirectToolNames = new Set<string>();
41
41
  let registeredProxyTool = false;
42
+ let startupWarmupCancel: (() => void) | null = null;
42
43
 
43
44
  async function registerDirectToolsFromConfig(
44
45
  config: McpConfig,
@@ -50,13 +51,14 @@ export default function mcpAdapter(pi: ExtensionAPI) {
50
51
  ]);
51
52
  const prefix = config.settings?.toolPrefix ?? "server";
52
53
  const envRaw = process.env.MCP_DIRECT_TOOLS;
54
+ const envDirectTools = envRaw?.split(",").map(s => s.trim()).filter(Boolean);
53
55
  const directSpecs = envRaw === "__none__"
54
56
  ? []
55
57
  : resolveDirectTools(
56
58
  config,
57
59
  cache,
58
60
  prefix,
59
- envRaw?.split(",").map(s => s.trim()).filter(Boolean),
61
+ envDirectTools,
60
62
  );
61
63
  for (const spec of directSpecs) {
62
64
  if (registeredDirectToolNames.has(spec.prefixedName)) continue;
@@ -75,7 +77,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
75
77
  refreshTools?.();
76
78
  return {
77
79
  directToolCount: directSpecs.length,
78
- missingConfiguredDirectToolServers: getMissingConfiguredDirectToolServers(config, cache),
80
+ missingConfiguredDirectToolServers: getMissingConfiguredDirectToolServers(config, cache, envDirectTools),
79
81
  };
80
82
  }
81
83
 
@@ -129,12 +131,18 @@ export default function mcpAdapter(pi: ExtensionAPI) {
129
131
  type: "string",
130
132
  });
131
133
 
134
+ function cancelStartupWarmup(): void {
135
+ startupWarmupCancel?.();
136
+ startupWarmupCancel = null;
137
+ }
138
+
132
139
  pi.on("session_start", async (_event, ctx) => {
133
140
  const generation = ++lifecycleGeneration;
134
141
  const previousState = state;
135
142
  state = null;
136
143
  initPromise = null;
137
144
  registeredDirectToolNames = new Set<string>();
145
+ cancelStartupWarmup();
138
146
 
139
147
  try {
140
148
  const config = loadMcpConfig(earlyConfigPath, ctx.cwd);
@@ -168,7 +176,10 @@ export default function mcpAdapter(pi: ExtensionAPI) {
168
176
  throw new Error("Stale MCP session initialization cancelled before startup");
169
177
  }
170
178
 
171
- const { initializeMcp, updateStatusBar } = await import("./init.ts");
179
+ const [{ initializeMcp, updateStatusBar }, { scheduleMcpStartupWarmup }] = await Promise.all([
180
+ import("./init.ts"),
181
+ import("./startup-warmup.ts"),
182
+ ]);
172
183
  if (generation !== lifecycleGeneration || !isContextActive(ctx)) {
173
184
  throw new Error("Stale MCP session initialization cancelled before startup");
174
185
  }
@@ -193,6 +204,21 @@ export default function mcpAdapter(pi: ExtensionAPI) {
193
204
  ) {
194
205
  registerProxyTool();
195
206
  }
207
+ let cancelWarmup: (() => void) | null = null;
208
+ const warmup = scheduleMcpStartupWarmup(nextState, {
209
+ shouldContinue: () => generation === lifecycleGeneration && state === nextState,
210
+ onDirectToolsChanged: async () => {
211
+ if (generation !== lifecycleGeneration || state !== nextState) return;
212
+ await registerDirectTools(nextState);
213
+ },
214
+ onSettled: () => {
215
+ if (generation === lifecycleGeneration && state === nextState && startupWarmupCancel === cancelWarmup) {
216
+ startupWarmupCancel = null;
217
+ }
218
+ },
219
+ });
220
+ cancelWarmup = () => warmup.cancel();
221
+ startupWarmupCancel = cancelWarmup;
196
222
  if (initPromise === promiseRef.current) {
197
223
  initPromise = null;
198
224
  }
@@ -226,6 +252,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
226
252
  state = null;
227
253
  initPromise = null;
228
254
  registeredDirectToolNames = new Set<string>();
255
+ cancelStartupWarmup();
229
256
 
230
257
  try {
231
258
  await Promise.all([
@@ -409,7 +436,7 @@ export default function mcpAdapter(pi: ExtensionAPI) {
409
436
  return executeConnect(state, params.connect);
410
437
  }
411
438
  if (params.describe) {
412
- return executeDescribe(state, params.describe);
439
+ return executeDescribe(state, params.describe, params.server);
413
440
  }
414
441
  if (params.search) {
415
442
  return executeSearch(state, params.search, params.regex, params.server, params.includeSchemas);
@@ -21,7 +21,6 @@ import { buildToolMetadata, totalToolCount } from "./tool-metadata.ts";
21
21
  import { UiResourceHandler } from "./ui-resource-handler.ts";
22
22
  import { openUrl, parallelLimit } from "./utils.ts";
23
23
  import { logger } from "./logger.ts";
24
- import { getMissingConfiguredDirectToolServers } from "./direct-tools.ts";
25
24
 
26
25
  const FAILURE_BACKOFF_MS = 60 * 1000;
27
26
 
@@ -75,10 +74,7 @@ export async function initializeMcp(
75
74
  const cachePath = getMetadataCachePath();
76
75
  const cacheFileExists = existsSync(cachePath);
77
76
  let cache = loadMetadataCache();
78
- let bootstrapAll = false;
79
-
80
77
  if (!cacheFileExists) {
81
- bootstrapAll = true;
82
78
  saveMetadataCache({ version: 1, servers: {} });
83
79
  } else if (!cache) {
84
80
  cache = { version: 1, servers: {} };
@@ -105,12 +101,10 @@ export async function initializeMcp(
105
101
  }
106
102
  }
107
103
 
108
- const startupServers = bootstrapAll
109
- ? serverEntries
110
- : serverEntries.filter(([, definition]) => {
111
- const mode = definition.lifecycle ?? "lazy";
112
- return mode === "keep-alive" || mode === "eager";
113
- });
104
+ const startupServers = serverEntries.filter(([, definition]) => {
105
+ const mode = definition.lifecycle ?? "lazy";
106
+ return mode === "keep-alive" || mode === "eager";
107
+ });
114
108
 
115
109
  if (ctx.hasUI && startupServers.length > 0) {
116
110
  ctx.ui.setStatus("mcp", `MCP: connecting to ${startupServers.length} servers...`);
@@ -160,39 +154,6 @@ export async function initializeMcp(
160
154
  ctx.ui.notify(msg, "info");
161
155
  }
162
156
 
163
- const envDirect = process.env.MCP_DIRECT_TOOLS;
164
- if (envDirect !== "__none__") {
165
- const currentCache = loadMetadataCache();
166
- const missingCacheServers = getMissingConfiguredDirectToolServers(config, currentCache);
167
-
168
- if (missingCacheServers.length > 0) {
169
- const bootstrapResults = await parallelLimit(
170
- missingCacheServers.filter(name => !results.some(r => r.name === name && r.connection)),
171
- 10,
172
- async (name) => {
173
- const definition = config.mcpServers[name];
174
- try {
175
- const connection = await manager.connect(name, definition);
176
- if (connection.status === "needs-auth") {
177
- return { name, ok: false };
178
- }
179
- const { metadata } = buildToolMetadata(connection.tools, connection.resources, definition, name, prefix);
180
- toolMetadata.set(name, metadata);
181
- updateMetadataCache(state, name);
182
- return { name, ok: true };
183
- } catch (error) {
184
- const message = error instanceof Error ? error.message : String(error);
185
- logger.debug(`MCP: direct-tools bootstrap failed for ${name}: ${message}`);
186
- return { name, ok: false };
187
- }
188
- },
189
- );
190
- const bootstrapped = bootstrapResults.filter(r => r.ok).map(r => r.name);
191
- if (bootstrapped.length > 0 && ctx.hasUI) {
192
- ctx.ui.notify(`MCP: direct tools for ${bootstrapped.join(", ")} will be available after restart`, "info");
193
- }
194
- }
195
- }
196
157
 
197
158
  lifecycle.setReconnectCallback((serverName) => {
198
159
  updateServerMetadata(state, serverName);
@@ -0,0 +1,37 @@
1
+ import type { McpExtensionState } from "./state.ts";
2
+ import { lazyConnect, updateMetadataCache, updateServerMetadata } from "./init.ts";
3
+ import { parallelLimit } from "./utils.ts";
4
+
5
+ export async function hydrateServerMetadata(
6
+ state: McpExtensionState,
7
+ serverName: string,
8
+ ): Promise<boolean> {
9
+ if (state.toolMetadata.has(serverName)) return true;
10
+ if (!state.config.mcpServers[serverName]) return false;
11
+
12
+ const connection = state.manager.getConnection(serverName);
13
+ if (connection?.status === "connected") {
14
+ updateServerMetadata(state, serverName);
15
+ updateMetadataCache(state, serverName);
16
+ return state.toolMetadata.has(serverName);
17
+ }
18
+
19
+ return lazyConnect(state, serverName);
20
+ }
21
+
22
+ export async function hydrateMissingMetadata(
23
+ state: McpExtensionState,
24
+ options?: { server?: string },
25
+ ): Promise<void> {
26
+ if (options?.server) {
27
+ await hydrateServerMetadata(state, options.server);
28
+ return;
29
+ }
30
+
31
+ const missingServers = Object.keys(state.config.mcpServers).filter(
32
+ (serverName) => !state.toolMetadata.has(serverName),
33
+ );
34
+ await parallelLimit(missingServers, 10, async (serverName) => {
35
+ await hydrateServerMetadata(state, serverName);
36
+ });
37
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.5-alpha.8",
3
+ "version": "0.9.5-alpha.9",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
6
6
  "contributors": [
@@ -1,9 +1,10 @@
1
1
  import type { McpExtensionState } from "./state.ts";
2
2
  import type { ToolMetadata } from "./types.ts";
3
- import { parseUiPromptHandoff } from "./types.ts";
3
+ import { getServerPrefix, parseUiPromptHandoff } from "./types.ts";
4
4
  import { getFailureAgeSeconds } from "./init.ts";
5
5
  import { findToolByName, formatSchema } from "./tool-metadata.ts";
6
6
  import { truncateAtWord } from "./utils.ts";
7
+ import { hydrateMissingMetadata } from "./metadata-hydration.ts";
7
8
  import type { ProxyToolResult } from "./proxy-types.ts";
8
9
 
9
10
  export function executeUiMessages(state: McpExtensionState): ProxyToolResult {
@@ -140,26 +141,73 @@ export function executeStatus(state: McpExtensionState): ProxyToolResult {
140
141
  };
141
142
  }
142
143
 
143
- export function executeDescribe(state: McpExtensionState, toolName: string): ProxyToolResult {
144
- let serverName: string | undefined;
145
- let toolMeta: ToolMetadata | undefined;
144
+ function findToolMetadata(
145
+ state: McpExtensionState,
146
+ toolName: string,
147
+ server?: string,
148
+ ): { serverName: string; toolMeta: ToolMetadata } | null {
149
+ for (const [serverName, metadata] of state.toolMetadata.entries()) {
150
+ if (server && serverName !== server) continue;
151
+ const toolMeta = findToolByName(metadata, toolName);
152
+ if (toolMeta) return { serverName, toolMeta };
153
+ }
154
+ return null;
155
+ }
146
156
 
147
- for (const [server, metadata] of state.toolMetadata.entries()) {
148
- const found = findToolByName(metadata, toolName);
149
- if (found) {
150
- serverName = server;
151
- toolMeta = found;
152
- break;
157
+ function normalizeToolAlias(value: string): string {
158
+ return value.replace(/-/g, "_");
159
+ }
160
+
161
+ function prefixHydrationCandidates(state: McpExtensionState, toolName: string): string[] {
162
+ const prefixMode = state.config.settings?.toolPrefix ?? "server";
163
+ if (prefixMode === "none") return [];
164
+ const normalizedToolName = normalizeToolAlias(toolName);
165
+ return Object.keys(state.config.mcpServers)
166
+ .map((serverName) => ({ serverName, prefix: normalizeToolAlias(getServerPrefix(serverName, prefixMode)) }))
167
+ .filter(({ prefix }) => prefix.length > 0 && normalizedToolName.startsWith(`${prefix}_`))
168
+ .sort((a, b) => b.prefix.length - a.prefix.length)
169
+ .map(({ serverName }) => serverName);
170
+ }
171
+
172
+ async function hydrateDescribeMetadata(state: McpExtensionState, toolName: string, server?: string): Promise<void> {
173
+ if (server) {
174
+ await hydrateMissingMetadata(state, { server });
175
+ return;
176
+ }
177
+
178
+ const candidates = prefixHydrationCandidates(state, toolName);
179
+ if (candidates.length > 0) {
180
+ for (const candidate of candidates) {
181
+ await hydrateMissingMetadata(state, { server: candidate });
182
+ if (findToolMetadata(state, toolName, candidate)) return;
153
183
  }
184
+ return;
185
+ }
186
+
187
+ if (findToolMetadata(state, toolName)) return;
188
+ await hydrateMissingMetadata(state);
189
+ }
190
+
191
+ export async function executeDescribe(
192
+ state: McpExtensionState,
193
+ toolName: string,
194
+ server?: string,
195
+ ): Promise<ProxyToolResult> {
196
+ let found = findToolMetadata(state, toolName, server);
197
+ if (!found) {
198
+ await hydrateDescribeMetadata(state, toolName, server);
199
+ found = findToolMetadata(state, toolName, server);
154
200
  }
155
201
 
156
- if (!serverName || !toolMeta) {
202
+ if (!found) {
157
203
  return {
158
204
  content: [{ type: "text" as const, text: `Tool "${toolName}" not found. Use mcp({ search: "..." }) to search.` }],
159
205
  details: { mode: "describe", error: "tool_not_found", requestedTool: toolName },
160
206
  };
161
207
  }
162
208
 
209
+ const { serverName, toolMeta } = found;
210
+
163
211
  let text = `${toolMeta.name}\n`;
164
212
  text += `Server: ${serverName}\n`;
165
213
  if (toolMeta.resourceUri) {
@@ -181,13 +229,13 @@ export function executeDescribe(state: McpExtensionState, toolName: string): Pro
181
229
  };
182
230
  }
183
231
 
184
- export function executeSearch(
232
+ export async function executeSearch(
185
233
  state: McpExtensionState,
186
234
  query: string,
187
235
  regex?: boolean,
188
236
  server?: string,
189
237
  includeSchemas?: boolean,
190
- ): ProxyToolResult {
238
+ ): Promise<ProxyToolResult> {
191
239
  const showSchemas = includeSchemas !== false;
192
240
 
193
241
  const matches: Array<{ server: string; tool: ToolMetadata }> = [];
@@ -213,6 +261,7 @@ export function executeSearch(
213
261
  details: { mode: "search", error: "invalid_pattern", query },
214
262
  };
215
263
  }
264
+ await hydrateMissingMetadata(state, { server });
216
265
 
217
266
  for (const [serverName, metadata] of state.toolMetadata.entries()) {
218
267
  if (server && serverName !== server) continue;
@@ -270,7 +319,7 @@ export function executeSearch(
270
319
  };
271
320
  }
272
321
 
273
- export function executeList(state: McpExtensionState, server: string): ProxyToolResult {
322
+ export async function executeList(state: McpExtensionState, server: string): Promise<ProxyToolResult> {
274
323
  if (!state.config.mcpServers[server]) {
275
324
  return {
276
325
  content: [{ type: "text" as const, text: `Server "${server}" not found. Use mcp({}) to see available servers.` }],
@@ -278,6 +327,10 @@ export function executeList(state: McpExtensionState, server: string): ProxyTool
278
327
  };
279
328
  }
280
329
 
330
+ if (!state.toolMetadata.has(server)) {
331
+ await hydrateMissingMetadata(state, { server });
332
+ }
333
+
281
334
  const metadata = state.toolMetadata.get(server);
282
335
  const toolNames = metadata?.map(m => m.name) ?? [];
283
336
  const connection = state.manager.getConnection(server);