one-code-extension 0.1.4 → 0.3.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 (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +222 -177
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +128 -0
  8. package/docs/guide/configuration.md +173 -0
  9. package/docs/guide/differences-from-claude-code.md +152 -0
  10. package/docs/guide/doctor.md +88 -0
  11. package/docs/guide/hooks.md +111 -0
  12. package/docs/guide/installation.md +126 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
  14. package/docs/guide/providers-and-models.md +196 -0
  15. package/docs/guide/reference.md +226 -0
  16. package/docs/guide/sessions-and-context.md +192 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +178 -0
  18. package/docs/guide/subagents-and-workflows.md +195 -0
  19. package/docs/guide/tasks-and-background-work.md +113 -0
  20. package/docs/guide/terminal-interface.md +157 -0
  21. package/docs/guide/tools.md +177 -0
  22. package/docs/guide/troubleshooting.md +177 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -28,17 +28,51 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
28
28
  import { Type } from "typebox";
29
29
  import {
30
30
  DEFER_CHANNEL,
31
- deferredRegistry,
31
+ deferredAddendumText,
32
+ deferredMissReminderText,
33
+ DeferredRegistry,
32
34
  deferredReminderText,
33
35
  type DeferRequest,
36
+ resultText,
34
37
  searchTools,
35
38
  selectedNames,
39
+ supportsToolReferences,
40
+ toolNotFoundName,
41
+ withDeferredToolDefinitions,
36
42
  } from "../lib/deferred.ts";
43
+ import { looksLikeAnthropicRequest } from "../lib/anthropic-payload.ts";
44
+ import { MCP_TOOLS_CHANNEL, type McpToolsPayload } from "../lib/mcp-share.ts";
37
45
  import { CONTEXT_ORDER, REMINDER_CHANNEL } from "../lib/reminders.ts";
46
+ import { sessionAlive } from "../lib/session-lifecycle.ts";
38
47
  import { ccToolRenderers } from "../lib/tui-render.ts";
48
+ import { normalizeToolName } from "../permissions/matcher.ts";
49
+ import { applyAnnouncement, planAnnouncement } from "./announce.ts";
50
+
51
+ /** Coalesces the burst of per-tool defers one server emits into one listing update. */
52
+ const ANNOUNCE_DEBOUNCE_MS = 100;
39
53
 
40
54
  export default function toolSearchExtension(pi: ExtensionAPI) {
55
+ // Owned here (see lib/deferred.ts): the only instance, fed over DEFER_CHANNEL.
56
+ const deferredRegistry = new DeferredRegistry();
41
57
  let sessionStarted = false;
58
+ // The deferred-tools listing is a first-prepend reminder on message 1, part of
59
+ // the cached prefix of every later request. Before the first request nothing
60
+ // is cached, so the listing is (re)written freely; after it the listing is
61
+ // FROZEN and later arrivals ride a one-shot addendum where the model reads
62
+ // next (./announce.ts — a rewrite re-cached 16.7k tokens of history on
63
+ // request 2 of every run whose MCP tools registered inside the debounce,
64
+ // measured 2026-09-04). While the initial MCP connect is still settling the
65
+ // addendum waits for settle, so one burst of per-tool defers is one notice.
66
+ let requestSent = false;
67
+ let mcpSettled = false;
68
+ let announcePending = false;
69
+ let announceTimer: NodeJS.Timeout | undefined;
70
+ // After session_shutdown every pi.* call throws (the runner is invalidated,
71
+ // findings §8); a debounced announce landing in a timer callback then would
72
+ // be an uncaught exception and take pi down (LIFECYCLE-REVIEW L1).
73
+ const alive = sessionAlive(pi);
74
+ /** Names the model has been told about: the frozen listing plus every addendum. */
75
+ const announced = new Set<string>();
42
76
 
43
77
  const searchableTools = () =>
44
78
  pi
@@ -50,18 +84,74 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
50
84
  keywords: deferredRegistry.keywordsFor(tool.name),
51
85
  }));
52
86
 
53
- const announceDeferred = () => {
87
+ const announce = () => {
88
+ if (!alive()) return;
54
89
  const available = searchableTools();
55
- if (available.length === 0) return;
56
- pi.events.emit(REMINDER_CHANNEL, {
57
- scope: "every-turn",
58
- key: "deferred-tools",
59
- text: deferredReminderText(available),
60
- placement: "first-prepend",
61
- order: CONTEXT_ORDER.deferredTools,
62
- });
90
+ const plan = planAnnouncement({ requestSent, announced, available: available.map((t) => t.name) });
91
+ if (plan.kind === "rewrite") {
92
+ pi.events.emit(REMINDER_CHANNEL, {
93
+ scope: "every-turn",
94
+ key: "deferred-tools",
95
+ text: deferredReminderText(available),
96
+ placement: "first-prepend",
97
+ order: CONTEXT_ORDER.deferredTools,
98
+ });
99
+ } else if (plan.kind === "addendum") {
100
+ // Unkeyed on purpose: a keyed next-turn reminder replaces its
101
+ // predecessor, and an addendum replaced before delivery would lose names.
102
+ pi.events.emit(REMINDER_CHANNEL, { text: deferredAddendumText(plan.added) });
103
+ }
104
+ applyAnnouncement(announced, plan);
105
+ };
106
+
107
+ const clearAnnounce = () => {
108
+ if (announceTimer) clearTimeout(announceTimer);
109
+ announceTimer = undefined;
110
+ };
111
+ const scheduleAnnounce = () => {
112
+ clearAnnounce();
113
+ if (!alive()) return;
114
+ announceTimer = setTimeout(() => {
115
+ announceTimer = undefined;
116
+ // Decided at fire time, not schedule time: a request may have gone out
117
+ // during the debounce (the -p race).
118
+ if (requestSent && !mcpSettled) {
119
+ announcePending = true;
120
+ return;
121
+ }
122
+ announce();
123
+ }, ANNOUNCE_DEBOUNCE_MS);
124
+ announceTimer.unref?.();
63
125
  };
64
126
 
127
+ pi.events.on(MCP_TOOLS_CHANNEL, (data) => {
128
+ if (!(data as McpToolsPayload | undefined)?.settled) return;
129
+ mcpSettled = true;
130
+ if (announcePending) {
131
+ announcePending = false;
132
+ announce();
133
+ }
134
+ });
135
+
136
+ pi.on("context", () => {
137
+ requestSent = true;
138
+ });
139
+
140
+ // On models that take client-side tool references (first-party Claude >= 4.5,
141
+ // not Haiku) every deferred tool rides every Anthropic request as a
142
+ // `defer_loading: true` definition from request 1, so `tools` never changes
143
+ // when tool_search loads one. Anthropic keeps deferred definitions out of the
144
+ // cached prefix, but adding one mid-session still re-cached the whole message
145
+ // history (lib/deferred.ts withDeferredToolDefinitions has the measurement);
146
+ // unloaded deferred definitions cost no input tokens, so this is free.
147
+ pi.on("before_provider_request", (event, ctx) => {
148
+ if (!supportsToolReferences(ctx.model as { provider?: string; id?: string } | undefined)) return undefined;
149
+ if (!looksLikeAnthropicRequest(event.payload)) return undefined;
150
+ return withDeferredToolDefinitions(event.payload as Record<string, unknown>, pi.getAllTools(), (name) =>
151
+ deferredRegistry.has(name),
152
+ );
153
+ });
154
+
65
155
  /** Deactivate every deferred-registry tool and announce the loadable set. */
66
156
  const deferAll = () => {
67
157
  const deferred = new Set(deferredRegistry.names);
@@ -69,7 +159,7 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
69
159
  const active = pi.getActiveTools();
70
160
  const next = active.filter((name) => !deferred.has(name));
71
161
  if (next.length !== active.length) pi.setActiveTools(next);
72
- announceDeferred();
162
+ announce();
73
163
  };
74
164
 
75
165
  pi.events.on(DEFER_CHANNEL, (data) => {
@@ -84,21 +174,54 @@ export default function toolSearchExtension(pi: ExtensionAPI) {
84
174
  if (active.includes(request.name)) {
85
175
  pi.setActiveTools(active.filter((name) => name !== request.name));
86
176
  }
87
- announceDeferred();
177
+ // The tool is searchable (keyword search reads the registry) from now;
178
+ // only the standing listing waits.
179
+ scheduleAnnounce();
88
180
  }
89
181
  });
90
182
 
91
183
  pi.on("session_start", () => {
92
184
  sessionStarted = true;
185
+ // A pending debounce belongs to the previous session's listing (RPC's
186
+ // new_session emits session_start twice, findings §3); deferAll below
187
+ // announces the fresh one.
188
+ clearAnnounce();
189
+ // A new session (/clear, or a resume in a new process) has no cached prefix
190
+ // yet: its first request gets a freshly written listing.
191
+ requestSent = false;
192
+ announced.clear();
93
193
  deferAll();
94
194
  });
95
195
 
196
+ pi.on("session_shutdown", clearAnnounce);
197
+
198
+ // The every-turn deferred-tools list tells the model to load via tool_search,
199
+ // but if it calls a deferred tool directly anyway, pi's core dispatcher fails
200
+ // the call with a bare "Tool <name> not found" — which fires here as an error
201
+ // tool_execution_end. (This path skips beforeToolCall/afterToolCall, so the
202
+ // tool_call/tool_result hooks never see it.) The call can't be salvaged
203
+ // mid-turn, but we steer the model straight back to tool_search on its next
204
+ // step with a one-shot reminder, far more pointed than the standing list.
205
+ pi.on("tool_execution_end", (event) => {
206
+ if (!event.isError) return;
207
+ const raw = toolNotFoundName(resultText(event.result));
208
+ // Map a Claude Code spelling (a direct `NotebookEdit` call) to our name so
209
+ // the miss is recognised and the steer names the loadable tool (M1).
210
+ const name = raw ? normalizeToolName(raw) : undefined;
211
+ if (!name || !deferredRegistry.has(name)) return;
212
+ pi.events.emit(REMINDER_CHANNEL, {
213
+ scope: "next-turn",
214
+ key: `deferred-miss-${name}`,
215
+ text: deferredMissReminderText(name),
216
+ });
217
+ });
218
+
96
219
  pi.registerTool({
97
220
  name: "tool_search",
98
221
  label: "Tool Search",
99
222
  ...ccToolRenderers("Tool Search"),
100
223
  description:
101
- "Load the schemas of tools that are available but not yet callable. Query forms: `select:<name>[,<name>]` to load exact tools by name, `+<term> <words>` to require a term in the tool name, or plain keywords to search. Returns the tools that are now callable.",
224
+ "Fetches full schema definitions for deferred tools so they can be called.\n\nDeferred tools appear by name in <system-reminder> messages. Until fetched, only the name is known — there is no parameter schema, so the tool cannot be invoked. This tool takes a query, matches it against the deferred tool list, and loads the matched tools' full schemas so they become callable exactly like any tool defined at the top of the prompt.\n\nQuery forms:\n- `select:web_fetch,notebook_edit` — fetch these exact tools by name\n- `notebook jupyter` — keyword search, up to max_results best matches\n- `+slack send` — require \"slack\" in the name, rank by remaining terms",
102
225
  promptSnippet: "Load additional tool schemas on demand",
103
226
  parameters: Type.Object({
104
227
  query: Type.String({ description: "Tool names (`select:a,b`) or keywords describing the capability needed" }),
@@ -0,0 +1,60 @@
1
+ /**
2
+ * turn-duration extension — Claude Code's "✻ Cooked for 5m 12s" line shown in
3
+ * the transcript after each response.
4
+ *
5
+ * The turn's wall-clock is measured across agent_start→agent_settled by span.ts
6
+ * (one turn can hold several runs — `lib/interrupt.ts`) and emitted as a
7
+ * display-only session entry (`appendEntry` — not part of the LLM context, so
8
+ * the model never sees its own timing line), rendered dim and led by the ✻ mark
9
+ * to match CC's TurnDurationMessage. CC shows it after every response with no
10
+ * threshold and defaults it on; CC_TURN_DURATION=0 opts out. The completion verb
11
+ * is sampled once per turn from CC's 8-verb list.
12
+ */
13
+
14
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
+ import { trackShellTasks } from "../lib/shell-tasks.ts";
16
+ import { dimMarkedLine } from "../lib/tui-render.ts";
17
+ import { TURN_MARK, turnDurationText } from "./line.ts";
18
+ import { TurnSpan } from "./span.ts";
19
+ import { pickCompletionVerb } from "./verbs.ts";
20
+
21
+ const ENTRY_TYPE = "one-code:turn-duration";
22
+
23
+ interface TurnDurationData {
24
+ verb: string;
25
+ durationMs: number;
26
+ /** Background shells still running when the turn ended (CC's `· 2 shells still running`). */
27
+ runningShells?: number;
28
+ }
29
+
30
+ export default function turnDurationExtension(pi: ExtensionAPI) {
31
+ const shellTasks = trackShellTasks(pi);
32
+
33
+ pi.registerEntryRenderer<TurnDurationData>(ENTRY_TYPE, (entry, _options, theme) => {
34
+ const data = entry.data;
35
+ if (!data) return undefined;
36
+ return dimMarkedLine(theme, TURN_MARK, turnDurationText(data.verb, data.durationMs, data.runningShells ?? 0));
37
+ });
38
+
39
+ const span = new TurnSpan();
40
+
41
+ pi.on("agent_start", () => {
42
+ span.runStarted(Date.now());
43
+ });
44
+
45
+ pi.on("agent_end", (event, ctx) => {
46
+ span.runEnded(event.messages, ctx.signal?.aborted);
47
+ });
48
+
49
+ pi.on("agent_settled", (_event, ctx) => {
50
+ const durationMs = span.settle(Date.now());
51
+ if (durationMs === undefined) return;
52
+ if (process.env.CC_TURN_DURATION === "0") return;
53
+ if (!ctx.hasUI) return;
54
+ pi.appendEntry<TurnDurationData>(ENTRY_TYPE, {
55
+ verb: pickCompletionVerb(),
56
+ durationMs,
57
+ runningShells: shellTasks.running().length,
58
+ });
59
+ });
60
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pure pieces of Claude Code's turn-duration line ("✻ Cooked for 5m 12s"),
3
+ * matched to CC's SystemTextMessage/TurnDurationMessage render
4
+ * (`${verb} for ${duration}`, dim, led by the ✻ teardrop asterisk). CC shows
5
+ * it after every response with no minimum-duration threshold; the optional
6
+ * token-budget suffix is CC's separate /budget feature and is not replicated.
7
+ */
8
+
9
+ import { countNoun, formatDuration } from "../lib/tui-render.ts";
10
+
11
+ /** CC's TEARDROP_ASTERISK (figures.ts), the mark leading the duration line. */
12
+ export const TURN_MARK = "✻";
13
+
14
+ /**
15
+ * `<Verb> for <duration>` — duration formatted like the spinner's elapsed
16
+ * clock — plus Claude Code's background-shell tail (`· 2 shells still
17
+ * running`) when shells outlive the turn.
18
+ */
19
+ export function turnDurationText(verb: string, durationMs: number, runningShells = 0): string {
20
+ const shells = runningShells > 0 ? ` · ${countNoun(runningShells, "shell")} still running` : "";
21
+ return `${verb} for ${formatDuration(0, durationMs)}${shells}`;
22
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The wall-clock span of one user-visible turn.
3
+ *
4
+ * A turn can hold several agent runs (see `lib/interrupt.ts` for why), so the
5
+ * span opens on the FIRST run and closes at settle: a retried run keeps the
6
+ * original start, which is what makes the rendered duration the time the user
7
+ * actually waited rather than the time the last attempt took.
8
+ *
9
+ * A turn that ends aborted or errored renders nothing: the interrupted
10
+ * extension prints its own note for an abort, and pi prints its own retry
11
+ * failure for an error. Claude Code gates its turn line the same way.
12
+ */
13
+
14
+ import { type RunMessage, RunOutcomeLatch } from "../lib/interrupt.ts";
15
+
16
+ export class TurnSpan {
17
+ /** Undefined rather than 0 for "no span open", so a start of 0 is still a start. */
18
+ private startedAt: number | undefined;
19
+ private readonly outcome = new RunOutcomeLatch();
20
+
21
+ /** `agent_start`: opens the span; a retry/continuation is a no-op, keeping the first start. */
22
+ runStarted(now: number): void {
23
+ this.startedAt ??= now;
24
+ }
25
+
26
+ /** `agent_end`: remembers how that run ended (with the run's `signal.aborted`), for settle to read. */
27
+ runEnded(messages: ReadonlyArray<RunMessage> | undefined, signalAborted = false): void {
28
+ this.outcome.record(messages, signalAborted);
29
+ }
30
+
31
+ /**
32
+ * `agent_settled`: closes the span and returns the duration to render, or
33
+ * undefined when this turn should show no line at all.
34
+ */
35
+ settle(now: number): number | undefined {
36
+ const startedAt = this.startedAt;
37
+ this.startedAt = undefined;
38
+ if (this.outcome.take() !== "ok" || startedAt === undefined) return undefined;
39
+ return Math.max(0, now - startedAt);
40
+ }
41
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Claude Code's turn-completion verbs, verbatim from the reconstructed source
3
+ * (src/constants/turnCompletionVerbs.ts) — the past-tense verb shown after a
4
+ * response finishes ("✻ Cooked for 5m 12s"). One is picked at random per turn.
5
+ * These read naturally with "for <duration>", so the gerund spinner list
6
+ * (spinner/verbs.ts) is deliberately not reused.
7
+ */
8
+
9
+ export const TURN_COMPLETION_VERBS: readonly string[] = [
10
+ "Baked",
11
+ "Brewed",
12
+ "Churned",
13
+ "Cogitated",
14
+ "Cooked",
15
+ "Crunched",
16
+ "Sautéed",
17
+ "Worked",
18
+ ];
19
+
20
+ /** Random completion verb for one turn; `random` is injectable for tests. */
21
+ export function pickCompletionVerb(random: () => number = Math.random): string {
22
+ const index = Math.min(TURN_COMPLETION_VERBS.length - 1, Math.floor(random() * TURN_COMPLETION_VERBS.length));
23
+ return TURN_COMPLETION_VERBS[index];
24
+ }