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
@@ -0,0 +1,215 @@
1
+ /**
2
+ * recap extension — Claude Code's "while you were away" summary (the ※ line).
3
+ *
4
+ * CC fires it when the terminal has been blurred for 5 minutes; pi exposes no
5
+ * terminal focus/blur event, so the closest faithful trigger is an idle timer:
6
+ * armed when a turn ends, reset on any keystroke (onTerminalInput) and cleared
7
+ * while a turn runs, it fires after CC's 5-minute lull with no interaction. At
8
+ * most one recap per user turn (firedSinceTurn), matching CC's
9
+ * hasSummarySinceLastUserTurn guard.
10
+ *
11
+ * Generation mirrors CC's awaySummary.ts: a cheap same-containment model
12
+ * (pickEconomicalContainedModel — the getSmallFastModel analog), CC's verbatim
13
+ * prompt, and only the last 30 messages. It deliberately does NOT reuse the
14
+ * session prompt cache the way compaction does — CC makes a small standalone
15
+ * call here (skipCacheWrite). The result is a display-only entry (appendEntry,
16
+ * not in LLM context). Best-effort throughout: any failure just shows nothing.
17
+ *
18
+ * Deviations from CC, logged in docs/decisions: the session-memory block is
19
+ * omitted (decoupling), and name-only stubs of the active tools are sent
20
+ * rather than an empty tool list — some providers reject a history carrying
21
+ * tool_use blocks with no tools declared, and the small-token instruction keeps
22
+ * the model answering in text rather than calling one. A failed recap does not
23
+ * retry until the next turn (one attempt per turn, success or not). CC_RECAP=0
24
+ * opts out; CC_RECAP_IDLE_MS overrides the 5-minute delay.
25
+ */
26
+
27
+ import type { AgentMessage } from "@earendil-works/pi-agent-core";
28
+ import type { Api, Model, Tool } from "@earendil-works/pi-ai";
29
+ import { completeSimple } from "@earendil-works/pi-ai/compat";
30
+ import { convertToLlm, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
31
+ import { withReasoningFallback } from "../lib/model-policy.ts";
32
+ import { recordUsage } from "../lib/usage-bus.ts";
33
+ import { pickEconomicalContainedModel } from "../lib/model-tier.ts";
34
+ import { dimMarkedLine } from "../lib/tui-render.ts";
35
+ import { RECAP_PROMPT, recapLine, recentForRecap, REFERENCE_MARK } from "./prompt.ts";
36
+ import { RecapScheduler } from "./scheduler.ts";
37
+
38
+ const ENTRY_TYPE = "one-code:recap";
39
+ const DEFAULT_IDLE_MS = 5 * 60_000; // CC's BLUR_DELAY_MS
40
+ const RECAP_MAX_TOKENS = 256; // 1-3 short sentences
41
+ const RECAP_TIMEOUT_MS = 30_000;
42
+
43
+ /** The idle delay before a recap, overridable via CC_RECAP_IDLE_MS (floor 1s). */
44
+ function idleMs(): number {
45
+ const raw = Number(process.env.CC_RECAP_IDLE_MS);
46
+ return Number.isFinite(raw) && raw >= 1000 ? raw : DEFAULT_IDLE_MS;
47
+ }
48
+
49
+ interface RecapData {
50
+ content: string;
51
+ }
52
+
53
+ /**
54
+ * Name-only tool declarations: enough for a provider to accept the tool_use
55
+ * blocks already in the history, without the descriptions and schemas (the
56
+ * recap never calls a tool, and its prompt asks for text).
57
+ */
58
+ export function toolStubs(names: readonly string[]): Tool[] {
59
+ return names.map(
60
+ (name) =>
61
+ ({
62
+ name,
63
+ description: "Unavailable during this summary; answer in text.",
64
+ parameters: { type: "object", properties: {} },
65
+ }) as unknown as Tool,
66
+ );
67
+ }
68
+
69
+ export default function recapExtension(pi: ExtensionAPI) {
70
+ pi.registerEntryRenderer<RecapData>(ENTRY_TYPE, (entry, _options, theme) => {
71
+ const content = entry.data?.content;
72
+ if (!content) return undefined;
73
+ return dimMarkedLine(theme, REFERENCE_MARK, recapLine(content));
74
+ });
75
+
76
+ // The exact request messages the session last sent (after every extension's
77
+ // mutations), captured like the compaction extension does.
78
+ let capturedMessages: AgentMessage[] | undefined;
79
+ pi.on("context", (event) => {
80
+ capturedMessages = event.messages;
81
+ });
82
+
83
+ let lastCtx: ExtensionContext | undefined;
84
+ let inFlight: AbortController | undefined;
85
+ let inputHookRegistered = false;
86
+
87
+ const abortInFlight = () => {
88
+ inFlight?.abort();
89
+ inFlight = undefined;
90
+ };
91
+
92
+ const scheduler = new RecapScheduler(
93
+ {
94
+ set: (cb, ms) => {
95
+ const handle = setTimeout(cb, ms);
96
+ handle.unref?.();
97
+ return handle;
98
+ },
99
+ clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
100
+ },
101
+ idleMs,
102
+ () => process.env.CC_RECAP !== "0",
103
+ () => void generate(),
104
+ );
105
+
106
+ async function generate() {
107
+ const ctx = lastCtx;
108
+ const messages = capturedMessages;
109
+ if (!ctx?.hasUI || !messages?.length || scheduler.hasFiredSinceTurn) return;
110
+ abortInFlight();
111
+ const controller = new AbortController();
112
+ inFlight = controller;
113
+ try {
114
+ const model = ctx.model;
115
+ if (!model) return;
116
+ const choice = pickEconomicalContainedModel(ctx.modelRegistry.getAvailable(), model);
117
+ if (!choice) return;
118
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(choice.model);
119
+ if (!auth.ok) return;
120
+ const baseUrl = (auth as { baseUrl?: string }).baseUrl;
121
+
122
+ // Name-only stubs of the active tools, so a history carrying tool_use
123
+ // blocks stays valid on strict providers (see the header note) without
124
+ // shipping every full schema (~6k tokens) to a call that must answer
125
+ // in text.
126
+ const tools = toolStubs(pi.getActiveTools());
127
+
128
+ const recent = recentForRecap(messages);
129
+ const recapMessages = [...convertToLlm(recent), { role: "user" as const, content: RECAP_PROMPT, timestamp: Date.now() }];
130
+ // Thinking off unless the model cannot disable it; withReasoningFallback
131
+ // sends a level up front for catalog-marked models and retries on the 400
132
+ // for the rest. Fires on a 5-min idle timer, so no cross-call memo.
133
+ const result = await withReasoningFallback(choice.model, (reasoning) => {
134
+ const timeout = AbortSignal.timeout(RECAP_TIMEOUT_MS);
135
+ return completeSimple(
136
+ baseUrl ? ({ ...choice.model, baseUrl } as Model<Api>) : choice.model,
137
+ { systemPrompt: "", messages: recapMessages, tools },
138
+ {
139
+ apiKey: auth.apiKey,
140
+ headers: auth.headers,
141
+ env: auth.env,
142
+ signal: AbortSignal.any([controller.signal, timeout]),
143
+ maxTokens: RECAP_MAX_TOKENS,
144
+ ...(reasoning ? { reasoning } : {}),
145
+ },
146
+ );
147
+ }, undefined, (usage) => recordUsage(pi, "recap", usage));
148
+ if (controller.signal.aborted) return;
149
+ const content = result.content
150
+ .filter((block): block is { type: "text"; text: string } => block.type === "text")
151
+ .map((block) => block.text)
152
+ .join("\n")
153
+ .trim();
154
+ if (!content) return;
155
+ scheduler.markFired();
156
+ pi.appendEntry<RecapData>(ENTRY_TYPE, { content });
157
+ } catch {
158
+ // Best-effort: a failed recap shows nothing — and does not retry. Without
159
+ // this the failure re-arms on every keystroke (interacted() → arm()),
160
+ // hammering the provider once per idle period until a turn resets it.
161
+ // An abort means a new turn or session already reset the scheduler; a
162
+ // late markFired there would suppress that turn's own recap.
163
+ if (!controller.signal.aborted) scheduler.markFired();
164
+ } finally {
165
+ if (inFlight === controller) inFlight = undefined;
166
+ }
167
+ }
168
+
169
+ pi.on("session_start", (_event, ctx) => {
170
+ lastCtx = ctx;
171
+ // A new session (including after /clear): drop any timer armed by the
172
+ // previous session and its captured messages, so a stale recap can never
173
+ // surface against the fresh conversation.
174
+ scheduler.reset();
175
+ abortInFlight();
176
+ capturedMessages = undefined;
177
+ if (!ctx.hasUI) return;
178
+ // Reset the idle clock on any keystroke — the closest signal to CC's
179
+ // terminal-blur trigger pi exposes. Registered ONCE (session_start fires
180
+ // again on every /clear and session switch, so an unguarded registration
181
+ // would leak a growing stack of listeners); the handler drives the
182
+ // scheduler, which reads only its own state.
183
+ if (inputHookRegistered) return;
184
+ inputHookRegistered = true;
185
+ ctx.ui.onTerminalInput(() => {
186
+ scheduler.interacted();
187
+ return undefined; // observe only; never consume the keystroke
188
+ });
189
+ });
190
+
191
+ pi.on("agent_start", (_event, ctx) => {
192
+ lastCtx = ctx;
193
+ scheduler.turnStarted();
194
+ abortInFlight();
195
+ });
196
+
197
+ // Settle, not agent_end: `agent_end` fires per run, and arming the idle
198
+ // timer there could fire a recap during a retry backoff longer than the
199
+ // configured idle (CC_RECAP_IDLE_MS floors at 1s), burning the
200
+ // one-per-turn budget mid-turn.
201
+ pi.on("agent_settled", (_event, ctx) => {
202
+ lastCtx = ctx;
203
+ // Unconditional: turnEnded() both clears turn state and arms the idle
204
+ // timer, so gating it on hasUI could leave turnRunning stuck true (and
205
+ // block all future re-arming) if a turn ever ends without UI. Arming
206
+ // without UI is harmless — generate() bails on !hasUI and the timer is
207
+ // unref'd, so a one-shot -p process exits before it can fire.
208
+ scheduler.turnEnded();
209
+ });
210
+
211
+ pi.on("session_shutdown", () => {
212
+ scheduler.reset();
213
+ abortInFlight();
214
+ });
215
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure pieces of Claude Code's "while you were away" recap (the ※ line CC shows
3
+ * when you come back after stepping away). Prompt text is verbatim from CC's
4
+ * services/awaySummary.ts; the display format matches CC's away_summary render
5
+ * (dim, led by the ※ reference mark). The session-memory block CC optionally
6
+ * prepends is omitted here (kept decoupled from One Code's memory extension).
7
+ */
8
+
9
+ /** CC's REFERENCE_MARK (figures.ts) — the away-summary/recap marker. */
10
+ export const REFERENCE_MARK = "※";
11
+
12
+ /**
13
+ * Recap only needs recent context; CC truncates to the last 30 messages to
14
+ * avoid "prompt too long" on large sessions (≈15 exchanges — plenty for
15
+ * "where we left off").
16
+ */
17
+ export const RECENT_MESSAGE_WINDOW = 30;
18
+
19
+ /** The away-summary instruction, verbatim from CC's buildAwaySummaryPrompt (no memory block). */
20
+ export const RECAP_PROMPT =
21
+ "The user stepped away and is coming back. Write exactly 1-3 short sentences. Start by stating the high-level task — what they are building or debugging, not implementation details. Next: the concrete next step. Skip status reports and commit recaps.";
22
+
23
+ /** The transcript text after the ※ mark, matching CC's shipped "recap: …" line. */
24
+ export function recapLine(content: string): string {
25
+ return `recap: ${content.trim()}`;
26
+ }
27
+
28
+ /**
29
+ * The recent-message window for the recap: the last `window` messages, trimmed
30
+ * forward to the first clean turn boundary — a `user` prompt or an `assistant`
31
+ * reply. A raw slice can begin mid-exchange with a leading tool-result message
32
+ * whose matching assistant tool_use fell outside the window; convertToLlm would
33
+ * then emit an orphan tool_result block that strict providers (e.g. Anthropic)
34
+ * reject, silently failing the recap. An assistant start is safe — its own tool
35
+ * results follow within the window. When the window holds no user/assistant
36
+ * message at all (e.g. one long tool-only stretch), nothing is safe to send, so
37
+ * it returns empty and the recap runs on the instruction alone. Generic over
38
+ * `{ role }` so it is testable with plain objects.
39
+ */
40
+ export function recentForRecap<T extends { role: string }>(messages: readonly T[], window = RECENT_MESSAGE_WINDOW): T[] {
41
+ const tail = messages.slice(-window);
42
+ const start = tail.findIndex((message) => message.role === "user" || message.role === "assistant");
43
+ return start === -1 ? [] : tail.slice(start);
44
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Pure idle-timer state machine for the recap (no pi imports, so it is
3
+ * unit-tested directly; index.ts is thin wiring).
4
+ *
5
+ * The recap fires after a lull with no interaction following a turn — the
6
+ * closest faithful substitute for Claude Code's 5-minute terminal-blur trigger,
7
+ * which pi exposes no event for. The rules:
8
+ * - armed when a turn ends, re-armed on any keystroke, cleared while a turn runs;
9
+ * - at most one recap per user turn (CC's hasSummarySinceLastUserTurn guard);
10
+ * - `reset()` drops a pending fire and all turn state, so a timer armed by a
11
+ * previous session can never fire against a new one (after /clear).
12
+ *
13
+ * The clock is injected (`TimerOps`) so tests drive it deterministically; the
14
+ * async model call it triggers stays in the extension.
15
+ */
16
+
17
+ export interface TimerOps {
18
+ /** Schedule `cb` after `ms`; return a handle for `clear`. */
19
+ set(cb: () => void, ms: number): unknown;
20
+ clear(handle: unknown): void;
21
+ }
22
+
23
+ export class RecapScheduler {
24
+ private turnRunning = false;
25
+ private firedSinceTurn = false;
26
+ private handle: unknown;
27
+
28
+ constructor(
29
+ private readonly timer: TimerOps,
30
+ /** The idle delay in ms, read at arm time. */
31
+ private readonly idleMs: () => number,
32
+ /** Whether the recap is enabled (e.g. CC_RECAP !== "0"). */
33
+ private readonly enabled: () => boolean,
34
+ /** Called when the idle timer elapses. */
35
+ private readonly onFire: () => void,
36
+ ) {}
37
+
38
+ private clear(): void {
39
+ if (this.handle !== undefined) {
40
+ this.timer.clear(this.handle);
41
+ this.handle = undefined;
42
+ }
43
+ }
44
+
45
+ private arm(): void {
46
+ this.clear();
47
+ if (this.turnRunning || this.firedSinceTurn || !this.enabled()) return;
48
+ this.handle = this.timer.set(() => {
49
+ this.handle = undefined;
50
+ this.onFire();
51
+ }, this.idleMs());
52
+ }
53
+
54
+ /** A turn began: cancel any pending recap and allow a fresh one after it. */
55
+ turnStarted(): void {
56
+ this.turnRunning = true;
57
+ this.firedSinceTurn = false;
58
+ this.clear();
59
+ }
60
+
61
+ /** A turn ended: start the idle countdown. */
62
+ turnEnded(): void {
63
+ this.turnRunning = false;
64
+ this.arm();
65
+ }
66
+
67
+ /** Any interaction between turns resets the idle clock. */
68
+ interacted(): void {
69
+ if (!this.turnRunning) this.arm();
70
+ }
71
+
72
+ /** New session or shutdown: drop the pending fire and all turn state. */
73
+ reset(): void {
74
+ this.turnRunning = false;
75
+ this.firedSinceTurn = false;
76
+ this.clear();
77
+ }
78
+
79
+ /** A recap was emitted; suppress another until the next turn. */
80
+ markFired(): void {
81
+ this.firedSinceTurn = true;
82
+ }
83
+
84
+ get hasFiredSinceTurn(): boolean {
85
+ return this.firedSinceTurn;
86
+ }
87
+ }
Binary file
@@ -9,13 +9,15 @@ import type { PromptTier } from "../lib/model-tier.ts";
9
9
  export const SEARCH_TOOLS = ["grep", "find", "ls"] as const;
10
10
 
11
11
  /**
12
- * Returns the active-tool list adjusted for the tier: mid/low gain the search
13
- * tools (deduped, appended in a stable order), frontier loses them (Claude
14
- * Code's frontier surface has no dedicated search tools). Order of the
15
- * existing names is preserved so the rebuilt system prompt stays byte-stable.
12
+ * Returns the active-tool list adjusted for the tier: only `tiny` gains the
13
+ * search tools (deduped, appended in a stable order); frontier/workhorse/cheap
14
+ * lose them. Claude Code ships no dedicated search tools on any tier (Haiku
15
+ * included — bash covers search), so `cheap` matches CC exactly; the grep/find/ls
16
+ * crutch is reserved for the sub-Haiku `tiny` models CC never serves. Order of
17
+ * the existing names is preserved so the rebuilt system prompt stays byte-stable.
16
18
  */
17
19
  export function withSearchTools(active: string[], tier: PromptTier): string[] {
18
- if (tier === "frontier") {
20
+ if (tier !== "tiny") {
19
21
  const drop = new Set<string>(SEARCH_TOOLS);
20
22
  return active.filter((name) => !drop.has(name));
21
23
  }