@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.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 (266) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/types/cli/args.d.ts +1 -1
  3. package/dist/types/cli/dry-balance-cli.d.ts +15 -1
  4. package/dist/types/cli/gallery-cli.d.ts +43 -0
  5. package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
  6. package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
  7. package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
  8. package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
  9. package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
  10. package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
  11. package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
  12. package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
  13. package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
  14. package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
  15. package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
  16. package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
  17. package/dist/types/cli/gallery-screenshot.d.ts +35 -0
  18. package/dist/types/commands/gallery.d.ts +47 -0
  19. package/dist/types/commit/analysis/conventional.d.ts +2 -2
  20. package/dist/types/commit/analysis/summary.d.ts +2 -2
  21. package/dist/types/commit/changelog/generate.d.ts +2 -2
  22. package/dist/types/commit/changelog/index.d.ts +2 -2
  23. package/dist/types/commit/map-reduce/index.d.ts +3 -3
  24. package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
  25. package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
  26. package/dist/types/commit/model-selection.d.ts +10 -4
  27. package/dist/types/config/api-key-resolver.d.ts +34 -0
  28. package/dist/types/config/keybindings.d.ts +6 -1
  29. package/dist/types/config/model-id-affixes.d.ts +2 -0
  30. package/dist/types/config/model-registry.d.ts +25 -2
  31. package/dist/types/config/settings-schema.d.ts +41 -6
  32. package/dist/types/dap/config.d.ts +14 -1
  33. package/dist/types/dap/types.d.ts +10 -0
  34. package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
  35. package/dist/types/lsp/types.d.ts +10 -0
  36. package/dist/types/lsp/utils.d.ts +3 -2
  37. package/dist/types/main.d.ts +3 -2
  38. package/dist/types/memory-backend/index.d.ts +2 -1
  39. package/dist/types/memory-backend/resolve.d.ts +1 -1
  40. package/dist/types/memory-backend/types.d.ts +1 -1
  41. package/dist/types/modes/components/chat-block.d.ts +64 -0
  42. package/dist/types/modes/components/custom-editor.d.ts +5 -1
  43. package/dist/types/modes/components/overlay-box.d.ts +17 -0
  44. package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
  45. package/dist/types/modes/components/plan-toc.d.ts +41 -0
  46. package/dist/types/modes/components/read-tool-group.d.ts +2 -0
  47. package/dist/types/modes/components/tool-execution.d.ts +18 -0
  48. package/dist/types/modes/components/transcript-container.d.ts +11 -0
  49. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  50. package/dist/types/modes/controllers/event-controller.d.ts +0 -1
  51. package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
  52. package/dist/types/modes/controllers/input-controller.d.ts +1 -1
  53. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  54. package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
  55. package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
  56. package/dist/types/modes/index.d.ts +5 -4
  57. package/dist/types/modes/interactive-mode.d.ts +16 -6
  58. package/dist/types/modes/setup-version.d.ts +11 -0
  59. package/dist/types/modes/setup-wizard/index.d.ts +2 -1
  60. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
  61. package/dist/types/modes/theme/theme.d.ts +1 -1
  62. package/dist/types/modes/types.d.ts +19 -6
  63. package/dist/types/modes/utils/copy-targets.d.ts +21 -1
  64. package/dist/types/plan-mode/approved-plan.d.ts +27 -8
  65. package/dist/types/plan-mode/plan-protection.d.ts +4 -4
  66. package/dist/types/sdk.d.ts +3 -1
  67. package/dist/types/session/agent-session.d.ts +21 -0
  68. package/dist/types/session/messages.d.ts +12 -0
  69. package/dist/types/session/session-manager.d.ts +3 -1
  70. package/dist/types/slash-commands/types.d.ts +4 -6
  71. package/dist/types/task/executor.d.ts +14 -0
  72. package/dist/types/task/index.d.ts +1 -0
  73. package/dist/types/task/render.d.ts +3 -2
  74. package/dist/types/telemetry-export.d.ts +1 -1
  75. package/dist/types/tools/archive-reader.d.ts +5 -0
  76. package/dist/types/tools/ast-edit.d.ts +3 -0
  77. package/dist/types/tools/ast-grep.d.ts +3 -0
  78. package/dist/types/tools/bash.d.ts +1 -0
  79. package/dist/types/tools/eval-render.d.ts +1 -8
  80. package/dist/types/tools/fetch.d.ts +15 -7
  81. package/dist/types/tools/find.d.ts +8 -4
  82. package/dist/types/tools/grouped-file-output.d.ts +95 -12
  83. package/dist/types/tools/memory-render.d.ts +4 -1
  84. package/dist/types/tools/plan-mode-guard.d.ts +8 -9
  85. package/dist/types/tools/render-utils.d.ts +13 -9
  86. package/dist/types/tools/renderers.d.ts +16 -2
  87. package/dist/types/tools/search.d.ts +5 -1
  88. package/dist/types/tools/sqlite-reader.d.ts +1 -0
  89. package/dist/types/tools/todo.d.ts +3 -2
  90. package/dist/types/tools/write.d.ts +5 -0
  91. package/dist/types/tui/output-block.d.ts +16 -4
  92. package/dist/types/tui/status-line.d.ts +3 -0
  93. package/dist/types/utils/enhanced-paste.d.ts +20 -0
  94. package/dist/types/web/scrapers/github.d.ts +22 -0
  95. package/dist/types/web/search/providers/kimi.d.ts +1 -1
  96. package/dist/types/web/search/providers/perplexity.d.ts +8 -1
  97. package/dist/types/web/search/types.d.ts +1 -1
  98. package/package.json +9 -9
  99. package/scripts/dev-launch +42 -0
  100. package/scripts/dev-launch-preload.ts +19 -0
  101. package/src/auto-thinking/classifier.ts +5 -1
  102. package/src/cli/args.ts +2 -2
  103. package/src/cli/dry-balance-cli.ts +52 -17
  104. package/src/cli/gallery-cli.ts +226 -0
  105. package/src/cli/gallery-fixtures/agentic.ts +292 -0
  106. package/src/cli/gallery-fixtures/codeintel.ts +188 -0
  107. package/src/cli/gallery-fixtures/edit.ts +194 -0
  108. package/src/cli/gallery-fixtures/fs.ts +153 -0
  109. package/src/cli/gallery-fixtures/index.ts +40 -0
  110. package/src/cli/gallery-fixtures/interaction.ts +49 -0
  111. package/src/cli/gallery-fixtures/memory.ts +81 -0
  112. package/src/cli/gallery-fixtures/misc.ts +250 -0
  113. package/src/cli/gallery-fixtures/search.ts +213 -0
  114. package/src/cli/gallery-fixtures/shell.ts +167 -0
  115. package/src/cli/gallery-fixtures/types.ts +41 -0
  116. package/src/cli/gallery-fixtures/web.ts +158 -0
  117. package/src/cli/gallery-screenshot.ts +279 -0
  118. package/src/cli-commands.ts +1 -0
  119. package/src/commands/gallery.ts +52 -0
  120. package/src/commands/launch.ts +1 -1
  121. package/src/commit/analysis/conventional.ts +2 -2
  122. package/src/commit/analysis/summary.ts +2 -2
  123. package/src/commit/changelog/generate.ts +2 -2
  124. package/src/commit/changelog/index.ts +2 -2
  125. package/src/commit/map-reduce/index.ts +3 -3
  126. package/src/commit/map-reduce/map-phase.ts +2 -2
  127. package/src/commit/map-reduce/reduce-phase.ts +2 -2
  128. package/src/commit/model-selection.ts +33 -9
  129. package/src/commit/pipeline.ts +4 -4
  130. package/src/config/api-key-resolver.ts +58 -0
  131. package/src/config/keybindings.ts +15 -6
  132. package/src/config/model-equivalence.ts +35 -12
  133. package/src/config/model-id-affixes.ts +39 -22
  134. package/src/config/model-registry.ts +41 -18
  135. package/src/config/settings-schema.ts +28 -5
  136. package/src/config/settings.ts +31 -2
  137. package/src/dap/client.ts +14 -16
  138. package/src/dap/config.ts +41 -2
  139. package/src/dap/defaults.json +1 -0
  140. package/src/dap/session.ts +1 -0
  141. package/src/dap/types.ts +10 -0
  142. package/src/debug/index.ts +40 -54
  143. package/src/edit/renderer.ts +111 -119
  144. package/src/eval/__tests__/agent-bridge.test.ts +75 -32
  145. package/src/eval/__tests__/llm-bridge.test.ts +90 -31
  146. package/src/eval/agent-bridge.ts +34 -7
  147. package/src/eval/llm-bridge.ts +8 -3
  148. package/src/extensibility/extensions/runner.ts +1 -0
  149. package/src/extensibility/plugins/doctor.ts +0 -1
  150. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  151. package/src/goals/tools/goal-tool.ts +37 -27
  152. package/src/internal-urls/docs-index.generated.ts +10 -10
  153. package/src/lsp/client.ts +104 -55
  154. package/src/lsp/types.ts +10 -0
  155. package/src/lsp/utils.ts +3 -2
  156. package/src/main.ts +53 -56
  157. package/src/memories/index.ts +12 -5
  158. package/src/memory-backend/index.ts +13 -1
  159. package/src/memory-backend/resolve.ts +3 -5
  160. package/src/memory-backend/types.ts +1 -1
  161. package/src/mnemopi/backend.ts +5 -1
  162. package/src/modes/acp/acp-agent.ts +33 -26
  163. package/src/modes/components/assistant-message.ts +2 -9
  164. package/src/modes/components/chat-block.ts +111 -0
  165. package/src/modes/components/copy-selector.ts +1 -44
  166. package/src/modes/components/custom-editor.ts +33 -1
  167. package/src/modes/components/custom-message.ts +1 -3
  168. package/src/modes/components/execution-shared.ts +1 -2
  169. package/src/modes/components/hook-message.ts +1 -3
  170. package/src/modes/components/overlay-box.ts +108 -0
  171. package/src/modes/components/plan-review-overlay.ts +799 -0
  172. package/src/modes/components/plan-toc.ts +138 -0
  173. package/src/modes/components/read-tool-group.ts +20 -4
  174. package/src/modes/components/skill-message.ts +0 -1
  175. package/src/modes/components/status-line.ts +3 -5
  176. package/src/modes/components/tips.txt +1 -0
  177. package/src/modes/components/todo-reminder.ts +0 -2
  178. package/src/modes/components/tool-execution.ts +115 -90
  179. package/src/modes/components/transcript-container.ts +84 -24
  180. package/src/modes/components/user-message.ts +1 -2
  181. package/src/modes/controllers/command-controller-shared.ts +7 -6
  182. package/src/modes/controllers/command-controller.ts +70 -57
  183. package/src/modes/controllers/event-controller.ts +41 -40
  184. package/src/modes/controllers/extension-ui-controller.ts +10 -73
  185. package/src/modes/controllers/input-controller.ts +135 -122
  186. package/src/modes/controllers/mcp-command-controller.ts +69 -60
  187. package/src/modes/controllers/selector-controller.ts +25 -27
  188. package/src/modes/controllers/streaming-reveal.ts +212 -0
  189. package/src/modes/controllers/tan-command-controller.ts +173 -0
  190. package/src/modes/index.ts +5 -4
  191. package/src/modes/interactive-mode.ts +171 -82
  192. package/src/modes/setup-version.ts +11 -0
  193. package/src/modes/setup-wizard/index.ts +3 -2
  194. package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
  195. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  196. package/src/modes/theme/theme-schema.json +1 -1
  197. package/src/modes/theme/theme.ts +8 -4
  198. package/src/modes/types.ts +19 -8
  199. package/src/modes/utils/context-usage.ts +10 -6
  200. package/src/modes/utils/copy-targets.ts +133 -27
  201. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  202. package/src/modes/utils/ui-helpers.ts +44 -46
  203. package/src/plan-mode/approved-plan.ts +66 -43
  204. package/src/plan-mode/plan-protection.ts +4 -4
  205. package/src/prompts/system/background-tan-dispatch.md +8 -0
  206. package/src/prompts/system/plan-mode-active.md +67 -58
  207. package/src/prompts/system/plan-mode-approved.md +1 -1
  208. package/src/sdk.ts +32 -60
  209. package/src/session/agent-session.ts +89 -13
  210. package/src/session/messages.ts +26 -0
  211. package/src/session/session-manager.ts +13 -5
  212. package/src/slash-commands/builtin-registry.ts +37 -10
  213. package/src/slash-commands/helpers/usage-report.ts +2 -0
  214. package/src/slash-commands/types.ts +4 -6
  215. package/src/task/executor.ts +25 -4
  216. package/src/task/index.ts +4 -0
  217. package/src/task/render.ts +212 -148
  218. package/src/telemetry-export.ts +25 -7
  219. package/src/tools/archive-reader.ts +64 -0
  220. package/src/tools/ask.ts +119 -164
  221. package/src/tools/ast-edit.ts +98 -71
  222. package/src/tools/ast-grep.ts +37 -43
  223. package/src/tools/bash.ts +50 -6
  224. package/src/tools/debug.ts +20 -8
  225. package/src/tools/eval-backends.ts +6 -17
  226. package/src/tools/eval-render.ts +21 -18
  227. package/src/tools/eval.ts +5 -4
  228. package/src/tools/fetch.ts +391 -91
  229. package/src/tools/find.ts +44 -30
  230. package/src/tools/gh-renderer.ts +81 -42
  231. package/src/tools/grouped-file-output.ts +272 -48
  232. package/src/tools/image-gen.ts +150 -103
  233. package/src/tools/inspect-image-renderer.ts +63 -41
  234. package/src/tools/inspect-image.ts +8 -1
  235. package/src/tools/job.ts +3 -4
  236. package/src/tools/memory-render.ts +4 -1
  237. package/src/tools/plan-mode-guard.ts +21 -39
  238. package/src/tools/read.ts +23 -16
  239. package/src/tools/render-utils.ts +38 -40
  240. package/src/tools/renderers.ts +16 -1
  241. package/src/tools/report-tool-issue.ts +1 -1
  242. package/src/tools/resolve.ts +14 -0
  243. package/src/tools/search-tool-bm25.ts +36 -23
  244. package/src/tools/search.ts +189 -95
  245. package/src/tools/sqlite-reader.ts +9 -12
  246. package/src/tools/todo.ts +138 -59
  247. package/src/tools/write.ts +100 -60
  248. package/src/tui/output-block.ts +60 -13
  249. package/src/tui/status-line.ts +5 -1
  250. package/src/utils/commit-message-generator.ts +9 -1
  251. package/src/utils/enhanced-paste.ts +202 -0
  252. package/src/utils/title-generator.ts +2 -1
  253. package/src/web/scrapers/github.ts +255 -3
  254. package/src/web/scrapers/youtube.ts +3 -2
  255. package/src/web/search/providers/anthropic.ts +25 -19
  256. package/src/web/search/providers/exa.ts +11 -3
  257. package/src/web/search/providers/kimi.ts +28 -17
  258. package/src/web/search/providers/parallel.ts +35 -24
  259. package/src/web/search/providers/perplexity.ts +199 -51
  260. package/src/web/search/providers/synthetic.ts +8 -6
  261. package/src/web/search/providers/tavily.ts +9 -8
  262. package/src/web/search/providers/zai.ts +8 -6
  263. package/src/web/search/render.ts +39 -54
  264. package/src/web/search/types.ts +5 -1
  265. package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
  266. package/src/eval/__tests__/shared-executors.test.ts +0 -609
@@ -1,15 +1,12 @@
1
- import * as fs from "node:fs/promises";
2
- import { isEnoent } from "@oh-my-pi/pi-utils";
3
- import { resolveLocalUrlToPath } from "../internal-urls";
4
- import { normalizeLocalScheme } from "../tools/path-utils";
5
1
  import { ToolError } from "../tools/tool-errors";
6
2
 
7
3
  /** Shape forwarded from the plan-mode resolve handler to InteractiveMode's
8
4
  * approval popup. Populated by the standing handler that the resolve tool
9
- * dispatches to when the agent submits `resolve { action: "apply" }`. */
5
+ * dispatches to when the agent submits `resolve { action: "apply" }`.
6
+ * `planFilePath` is the agent-chosen `local://<slug>-plan.md` artifact — it is
7
+ * never renamed on approval, so links to it stay valid for the session. */
10
8
  export interface PlanApprovalDetails {
11
9
  planFilePath: string;
12
- finalPlanFilePath: string;
13
10
  title: string;
14
11
  planExists: boolean;
15
12
  }
@@ -110,54 +107,80 @@ export function humanizePlanTitle(title: string): string {
110
107
  return spaced.charAt(0).toUpperCase() + spaced.slice(1);
111
108
  }
112
109
 
113
- interface RenameApprovedPlanFileOptions {
114
- planFilePath: string;
115
- finalPlanFilePath: string;
116
- getArtifactsDir: () => string | null;
117
- getSessionId: () => string | null;
110
+ /** The `local://` URL a plan slug maps to. The agent writes the plan here and
111
+ * passes the slug to `resolve`; the file is never renamed, so this URL — and
112
+ * any hyperlink to it — stays valid for the life of the session. */
113
+ export function planFileUrlForSlug(slug: string): string {
114
+ return `local://${slug}-plan.md`;
118
115
  }
119
116
 
120
- function assertLocalUrl(path: string, label: "source" | "destination"): void {
121
- if (!path.startsWith("local:/") && !path.startsWith("local://")) {
122
- throw new Error(`Approved plan ${label} path must use local: scheme with / or // (received ${path}).`);
117
+ /** Derive a `<slug>` from an agent-supplied `extra.title`, or `undefined` when
118
+ * the title is missing/non-string/unsanitizable. A trailing `-plan` is stripped
119
+ * so a supplied "auth-plan" maps to `auth-plan.md`, not `auth-plan-plan.md`. */
120
+ function planSlugFromSupplied(suppliedTitle: unknown): string | undefined {
121
+ if (typeof suppliedTitle !== "string" || !suppliedTitle.trim()) return undefined;
122
+ try {
123
+ const { title } = normalizePlanTitle(suppliedTitle);
124
+ const slug = title.replace(/-plan$/i, "");
125
+ return slug || title;
126
+ } catch {
127
+ return undefined;
123
128
  }
124
129
  }
125
130
 
126
- export async function renameApprovedPlanFile(options: RenameApprovedPlanFileOptions): Promise<void> {
127
- const { planFilePath, finalPlanFilePath, getArtifactsDir, getSessionId } = options;
128
- assertLocalUrl(planFilePath, "source");
129
- assertLocalUrl(finalPlanFilePath, "destination");
131
+ export interface ResolveApprovedPlanInput {
132
+ /** The agent's `extra.title` from the `resolve` call, if any. */
133
+ suppliedTitle?: unknown;
134
+ /** The plan path recorded in plan-mode state (the entry default or a prior plan). */
135
+ statePlanFilePath: string;
136
+ /** Read a plan `local://` URL, returning null when the file does not exist. */
137
+ readPlan: (planUrl: string) => Promise<string | null>;
138
+ /** Optional fallback: list candidate plan `local://` URLs (newest first) so a
139
+ * plan whose name can't be reconstructed (e.g. a dropped `extra.title`) is
140
+ * still found. */
141
+ listPlanFiles?: () => Promise<string[]>;
142
+ }
143
+
144
+ export interface ResolvedApprovedPlan {
145
+ planFilePath: string;
146
+ planContent: string;
147
+ title: string;
148
+ }
130
149
 
131
- const resolveOptions = {
132
- getArtifactsDir: () => getArtifactsDir(),
133
- getSessionId: () => getSessionId(),
150
+ /** Locate the plan file the agent wrote and finalize its title — without
151
+ * renaming anything. Tries, in order: the slug derived from `extra.title`
152
+ * (`local://<slug>-plan.md`), the plan path from plan-mode state, then a scan
153
+ * of recent plan files. Throws a `ToolError` guiding the agent when none exist. */
154
+ export async function resolveApprovedPlan(input: ResolveApprovedPlanInput): Promise<ResolvedApprovedPlan> {
155
+ const ordered: string[] = [];
156
+ const consider = (url: string | undefined): void => {
157
+ if (url && !ordered.includes(url)) ordered.push(url);
134
158
  };
135
- const resolvedSource = resolveLocalUrlToPath(normalizeLocalScheme(planFilePath), resolveOptions);
136
- const resolvedDestination = resolveLocalUrlToPath(normalizeLocalScheme(finalPlanFilePath), resolveOptions);
137
159
 
138
- if (resolvedSource === resolvedDestination) {
139
- return;
160
+ const slug = planSlugFromSupplied(input.suppliedTitle);
161
+ consider(slug ? planFileUrlForSlug(slug) : undefined);
162
+ consider(input.statePlanFilePath);
163
+
164
+ for (const url of ordered) {
165
+ const content = await input.readPlan(url);
166
+ if (content !== null) return finalizeApprovedPlan(url, content, input.suppliedTitle);
140
167
  }
141
168
 
142
- try {
143
- const destinationStat = await fs.stat(resolvedDestination);
144
- if (destinationStat.isFile()) {
145
- throw new Error(
146
- `Plan destination already exists at ${finalPlanFilePath}. Choose a different title and submit the plan for approval again.`,
147
- );
148
- }
149
- throw new Error(`Plan destination exists but is not a file: ${finalPlanFilePath}`);
150
- } catch (error) {
151
- if (!isEnoent(error)) {
152
- throw error;
169
+ if (input.listPlanFiles) {
170
+ for (const url of await input.listPlanFiles()) {
171
+ if (ordered.includes(url)) continue;
172
+ const content = await input.readPlan(url);
173
+ if (content !== null) return finalizeApprovedPlan(url, content, input.suppliedTitle);
153
174
  }
154
175
  }
155
176
 
156
- try {
157
- await fs.rename(resolvedSource, resolvedDestination);
158
- } catch (error) {
159
- throw new Error(
160
- `Failed to rename approved plan from ${planFilePath} to ${finalPlanFilePath}: ${error instanceof Error ? error.message : String(error)}`,
161
- );
162
- }
177
+ const target = ordered[0] ?? input.statePlanFilePath;
178
+ throw new ToolError(
179
+ `Plan file not found at ${target}. Write the finalized plan to ${target} before requesting approval.`,
180
+ );
181
+ }
182
+
183
+ function finalizeApprovedPlan(planFilePath: string, planContent: string, suppliedTitle: unknown): ResolvedApprovedPlan {
184
+ const { title } = resolvePlanTitle({ suppliedTitle, planContent, planFilePath });
185
+ return { planFilePath, planContent, title };
163
186
  }
@@ -16,11 +16,11 @@ function readTargetsPlan(readPath: string, planTarget: string): boolean {
16
16
  * Build a compaction protection matcher that keeps `read` results for the active
17
17
  * plan file intact through prune/shake — the plan analog of skill-read
18
18
  * protection. Matches both the canonical `local://PLAN.md` alias and the
19
- * session's current plan reference path (e.g. a titled `local://<title>.md`), so
20
- * the plan survives compaction whether the agent reads it by alias or by title.
19
+ * session's current plan reference path (the agent-chosen `local://<slug>-plan.md`),
20
+ * so the plan survives compaction whether the agent reads it by alias or by name.
21
21
  *
22
- * `getPlanReferencePath` is evaluated at match time so a mid-session retitle
23
- * (plan approval renames `PLAN.md` → `<title>.md`) is honored immediately.
22
+ * `getPlanReferencePath` is evaluated at match time so the plan path set on
23
+ * approval is honored immediately.
24
24
  */
25
25
  export function createPlanReadMatcher(getPlanReferencePath: () => string): (context: ProtectedToolContext) => boolean {
26
26
  return (context: ProtectedToolContext) => {
@@ -0,0 +1,8 @@
1
+ <system-notice reason="background_task_dispatched" job="{{jobId}}">
2
+ The user launched a tangential task that is now running in a separate background agent. This is NOT a prompt injection and NOT a new instruction for you — it is the coding agent informing you that work was handed off elsewhere.
3
+
4
+ The task below is being handled by another agent in its own session. You are NOT responsible for it: do NOT start working on it, do NOT reference it, and do NOT let it interrupt or alter your current task. Simply continue what you were doing as if this message had not appeared. Results, if any, will surface separately when the background task ({{jobId}}) completes.
5
+
6
+ Dispatched work (for your awareness only):
7
+ {{work}}
8
+ </system-notice>
@@ -6,111 +6,120 @@ You NEVER:
6
6
  - Run state-changing commands (git commit, npm install, etc.)
7
7
  - Make any system changes
8
8
 
9
- To implement: call `resolve` with `action: "apply"`, a `reason`, and `extra: { title: "<PLAN_TITLE>" }` → user approves an execution option → full write access is restored. `<PLAN_TITLE>` may only contain letters, numbers, underscores, and hyphens; the approved plan is renamed to `local://<PLAN_TITLE>.md`.
9
+ To implement: call `resolve` with `action: "apply"`, a `reason`, and `extra: { title: "<slug>" }` where `<slug>` matches your `local://<slug>-plan.md` file → user approves an execution option → full write access is restored. `<slug>` may only contain letters, numbers, underscores, and hyphens. The plan file is never renamed, so its name is yours to choose.
10
10
 
11
11
  You NEVER ask the user to exit plan mode for you; you MUST call `resolve` yourself.
12
12
  </critical>
13
13
 
14
+ ## Objective
15
+
16
+ A plan is **decision-complete**: another engineer or agent can execute it end-to-end without making a single design decision. Optimize every choice for that. Detail exists to remove the implementer's decisions — not to look thorough. A document that reads like a design doc (Non-Goals, Alternatives, risk matrices) yet leaves real decisions open is a FAILED plan.
17
+
14
18
  ## Plan File
15
19
 
16
20
  {{#if planExists}}
17
- Plan file exists at `{{planFilePath}}`; you MUST read and update it incrementally.
21
+ Plan file exists at `{{planFilePath}}`; you MUST read and update it incrementally. If this request is a different task, write a fresh `local://<slug>-plan.md` instead and leave the old plan in place.
18
22
  {{else}}
19
- You MUST create a plan at `{{planFilePath}}`.
23
+ Choose a short kebab-case `<slug>` that names this task (letters, numbers, hyphens) and write the plan to `local://<slug>-plan.md` — e.g. `local://auth-token-refresh-plan.md`. You MUST pass that same `<slug>` as `title` when you call `resolve`.
20
24
  {{/if}}
21
25
 
22
- You MUST use `{{editToolName}}` for incremental updates; use `{{writeToolName}}` only for create/full replace.
26
+ You MUST use `{{editToolName}}` for incremental updates; use `{{writeToolName}}` only for create/full replace. You MUST update the plan as you learn — you NEVER batch all writing to the end.
23
27
 
24
- <caution>
25
- The approval selector includes:
26
- - **Approve and execute**: starts execution in fresh context (session cleared).
27
- - **Approve and compact context**: distills the plan-mode discussion into a summary, then starts execution in this session.
28
- - **Approve and keep context**: starts execution in this session, preserving exploration history.
28
+ ## Resolving Unknowns
29
29
 
30
- You MUST still make the plan file self-contained: include requirements, decisions, key findings, and remaining todos.
31
- </caution>
30
+ You MUST eliminate unknowns by discovering facts, not by asking. Before asking the user anything, perform at least one targeted exploration pass.
31
+
32
+ Two kinds of unknowns, treated differently:
33
+ - **Discoverable facts** — repo/system truth: file locations, current behavior, existing patterns, types, configs. You MUST explore first (`find`, `search`, `read`, parallel explore subagents). You NEVER ask what the codebase can answer (e.g. "where is this defined?"). Ask only when several plausible candidates remain or a required identifier is genuinely absent — and then present the candidates with a recommendation.
34
+ - **Preferences and tradeoffs** — intent, UX, scope boundaries, performance-vs-simplicity: not derivable from code. You MUST surface these early via `{{askToolName}}` with 2–4 mutually exclusive options and a recommended default. If left unanswered, proceed with the default and record it under Assumptions.
35
+
36
+ Every question MUST materially change the plan, confirm a load-bearing assumption, or choose between real tradeoffs. You MUST batch questions. You NEVER ask filler questions or offer obviously-wrong options.
32
37
 
33
38
  {{#if reentry}}
34
39
  ## Re-entry
35
40
 
36
41
  <procedure>
37
- 1. Read existing plan
38
- 2. Evaluate request against it
42
+ 1. Read the existing plan.
43
+ 2. Evaluate the new request against it.
39
44
  3. Decide:
40
- - **Different task** → Overwrite plan
41
- - **Same task, continuing** → Update and clean outdated sections
42
- 4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete
45
+ - **Different task** → overwrite the plan.
46
+ - **Same task, continuing** → update and delete outdated sections.
47
+ 4. Call `resolve` with `action: "apply"` and `extra: { title }` when complete.
43
48
  </procedure>
44
49
  {{/if}}
45
50
 
46
51
  {{#if iterative}}
47
- ## Iterative Planning
52
+ ## Workflow — Iterative
48
53
 
49
54
  <procedure>
50
55
  ### 1. Explore
51
- You MUST use `find`, `search`, `read` to understand the codebase.
56
+ You MUST use `find`, `search`, `read` to ground yourself in the actual code. Hunt for existing functions, utilities, and conventions to reuse before proposing anything new.
52
57
 
53
58
  ### 2. Interview
54
- You MUST use `{{askToolName}}` to clarify:
55
- - Ambiguous requirements
56
- - Technical decisions and tradeoffs
57
- - Preferences: UI/UX, performance, edge cases
59
+ You MUST use `{{askToolName}}` to resolve preferences and tradeoffs (see Resolving Unknowns). Batch questions; never ask what exploration answers.
58
60
 
59
- You MUST batch questions. You NEVER ask what you can answer by exploring.
60
-
61
- ### 3. Update Incrementally
62
- You MUST use `{{editToolName}}` to update plan file as you learn; NEVER wait until end.
61
+ ### 3. Update incrementally
62
+ You MUST use `{{editToolName}}` to revise the plan file as you learn.
63
63
 
64
64
  ### 4. Calibrate
65
- - Large unspecified task → multiple interview rounds
66
- - Smaller task → fewer or no questions
65
+ - Large, unspecified task → multiple interview rounds.
66
+ - Small, well-specified task → few or no questions.
67
67
  </procedure>
68
-
69
- <caution>
70
- ### Plan Structure
71
-
72
- You MUST use clear markdown headers; include:
73
- - Recommended approach (not alternatives)
74
- - Paths of critical files to modify
75
- - Verification: how to test end-to-end
76
-
77
- The plan MUST be scannable yet detailed enough to execute.
78
- </caution>
79
-
80
68
  {{else}}
81
- ## Planning Workflow
69
+ ## Workflow — Parallel
82
70
 
83
71
  <procedure>
84
- ### Phase 1: Understand
85
- You MUST focus on the request and associated code. You SHOULD launch parallel explore agents when scope spans multiple areas.
72
+ ### Phase 1 Understand
73
+ You MUST focus on the request and the code behind it. You SHOULD launch parallel `explore` subagents (via `task`) when scope spans multiple areas — give each a distinct focus (existing implementations, related components, test patterns). Actively hunt for reusable functions, utilities, and conventions; avoid proposing new code when a suitable implementation already exists.
86
74
 
87
- ### Phase 2: Design
88
- You MUST draft an approach based on exploration. You MUST consider trade-offs briefly, then choose.
75
+ ### Phase 2 Design
76
+ You MUST draft an approach from your exploration, weigh trade-offs briefly, then commit to one. For large or cross-cutting changes you MAY spawn a planning/critique subagent to pressure-test the approach before you commit.
89
77
 
90
- ### Phase 3: Review
91
- You MUST read critical files. You MUST verify plan matches original request. You SHOULD use `{{askToolName}}` to clarify remaining questions.
78
+ ### Phase 3 Review
79
+ You MUST read the critical files you intend to touch to confirm the approach holds against the real code. You MUST verify the plan still matches the original request. You SHOULD use `{{askToolName}}` to close remaining preference questions.
92
80
 
93
- ### Phase 4: Update Plan
94
- You MUST update `{{planFilePath}}` (`{{editToolName}}` for changes, `{{writeToolName}}` only if creating from scratch):
95
- - Recommended approach only
96
- - Paths of critical files to modify
97
- - Verification section
81
+ ### Phase 4 Write the plan
82
+ You MUST write the plan file (see **Plan File** above) per **The Plan** below.
98
83
  </procedure>
84
+ {{/if}}
85
+
86
+ ## The Plan
87
+
88
+ The plan MUST be self-contained: approval may clear or compact this conversation, so the file alone must carry everything needed to execute.
99
89
 
100
90
  <caution>
101
- You MUST ask questions throughout. You NEVER make large assumptions about user intent.
91
+ Write 3–5 short, scannable markdown sections. The usual shape:
92
+ - **Context** — why this change: the problem or need, what prompted it, the intended outcome. 2–4 sentences.
93
+ - **Approach** — the recommended approach only. Group bullets by subsystem or behavior, NOT file-by-file. Name existing functions/utilities to reuse, with their paths. Describe a repeated pattern once with a few representative paths — you NEVER enumerate every file or line.
94
+ - **Critical files** — the ≤5 files that disambiguate non-obvious changes, each with a one-line reason. Skip files whose change is already obvious from the Approach.
95
+ - **Verification** — how to test end-to-end: exact commands, tests to run or add, manual steps.
96
+ - **Assumptions** — only the decisions you made that the user might want to override.
97
+
98
+ Prefer the minimum detail needed for safe implementation, not exhaustive coverage. Compress related changes into high-signal bullets; omit branch-by-branch logic, restated invariants, and lists of unaffected behavior. Behavior-level descriptions beat symbol-by-symbol removal lists.
102
99
  </caution>
103
- {{/if}}
104
100
 
105
101
  <directives>
106
- - You MUST use `{{askToolName}}` only for clarifying requirements or choosing approaches
102
+ - You NEVER include sections that decide nothing: Non-Goals, Out of Scope, Alternatives Considered, Risks/Mitigations boilerplate, Future Work. Omit them entirely.
103
+ - You NEVER invent schema, validation, precedence, or fallback policy the request did not establish, unless it is required to prevent a concrete implementation mistake.
104
+ - You NEVER present alternatives in the final plan — choose. Record a discarded option only when it is a live tradeoff the user should confirm, and put it under Assumptions.
107
105
  </directives>
108
106
 
107
+ <caution>
108
+ The approval selector offers:
109
+ - **Approve and execute** — execution starts in fresh context (session cleared).
110
+ - **Approve and compact context** — distills this discussion into a summary, then executes in this session.
111
+ - **Approve and keep context** — executes in this session, preserving exploration history.
112
+
113
+ All three rely on the plan file being self-contained.
114
+ </caution>
115
+
109
116
  <critical>
117
+ You MUST use `{{askToolName}}` only to clarify requirements or choose between approaches.
118
+
110
119
  Your turn ends ONLY by:
111
120
  1. Using `{{askToolName}}` to gather information, OR
112
- 2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<PLAN_TITLE>" }` when ready — this triggers user approval, then implementation with full tool access
121
+ 2. Calling `resolve` with `action: "apply"`, `reason`, and `extra: { title: "<slug>" }` (the slug of your `local://<slug>-plan.md`) when ready — this triggers user approval, then implementation with full tool access.
113
122
 
114
- You NEVER ask plan approval via text or `{{askToolName}}`; you MUST use `resolve`.
115
- You MUST keep going until complete.
123
+ You NEVER ask for plan approval via text or `{{askToolName}}`; you MUST use `resolve`.
124
+ You MUST keep going until the plan is decision-complete.
116
125
  </critical>
@@ -16,7 +16,7 @@ The plan path is for subagent handoff only. You already have the plan; NEVER rea
16
16
 
17
17
  The full plan is injected below. You MUST execute it now:
18
18
 
19
- <plan path="{{finalPlanFilePath}}">
19
+ <plan path="{{planFilePath}}">
20
20
  {{planContent}}
21
21
  </plan>
22
22
 
package/src/sdk.ts CHANGED
@@ -10,7 +10,6 @@ import {
10
10
  } from "@oh-my-pi/pi-agent-core";
11
11
  import {
12
12
  type CredentialDisabledEvent,
13
- isUsageLimitError,
14
13
  type Message,
15
14
  type Model,
16
15
  type SimpleStreamOptions,
@@ -24,7 +23,6 @@ import type { Component } from "@oh-my-pi/pi-tui";
24
23
  import {
25
24
  $env,
26
25
  $flag,
27
- extractRetryHint,
28
26
  getAgentDbPath,
29
27
  getAgentDir,
30
28
  getAuthBrokerSnapshotCachePath,
@@ -36,10 +34,10 @@ import {
36
34
  } from "@oh-my-pi/pi-utils";
37
35
  import chalk from "chalk";
38
36
  import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled } from "./async";
39
- import { createAutoresearchExtension } from "./autoresearch";
40
37
  import { loadCapability } from "./capability";
41
38
  import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
42
39
  import { bucketRules } from "./capability/rule-buckets";
40
+ import { createApiKeyResolver } from "./config/api-key-resolver";
43
41
  import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
44
42
  import { ModelRegistry } from "./config/model-registry";
45
43
  import {
@@ -57,7 +55,6 @@ import { resolveConfigValue } from "./config/resolve-config-value";
57
55
  import { initializeWithSettings } from "./discovery";
58
56
  import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
59
57
  import { defaultEvalSessionId } from "./eval/session-id";
60
- import { TtsrManager } from "./export/ttsr";
61
58
  import {
62
59
  type CustomCommandsLoadResult,
63
60
  type LoadedCustomCommand,
@@ -90,7 +87,7 @@ import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls
90
87
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
91
88
  import { discoverAndLoadMCPTools, MCPManager, type MCPToolsLoadResult } from "./mcp";
92
89
  import { resolveMemoryBackend } from "./memory-backend";
93
- import { getMnemopiSessionState, type MnemopiSessionState } from "./mnemopi/state";
90
+ import type { MnemopiSessionState } from "./mnemopi/state";
94
91
  import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
95
92
  import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
96
93
  import {
@@ -282,6 +279,8 @@ export interface CreateAgentSessionOptions {
282
279
  /** Optional provider-facing session identifier for prompt caches and sticky auth selection.
283
280
  * Keeps persisted session files isolated while reusing provider-side caches. */
284
281
  providerSessionId?: string;
282
+ /** Optional provider-facing prompt cache key, distinct from request lineage. */
283
+ providerPromptCacheKey?: string;
285
284
 
286
285
  /** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
287
286
  customTools?: (CustomTool | ToolDefinition)[];
@@ -1150,6 +1149,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1150
1149
 
1151
1150
  // Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
1152
1151
  const { ttsrManager, rulebookRules, alwaysApplyRules } = await logger.time("discoverTtsrRules", async () => {
1152
+ const { TtsrManager } = await import("./export/ttsr");
1153
1153
  const ttsrSettings = settings.getGroup("ttsr");
1154
1154
  const ttsrManager = new TtsrManager(ttsrSettings);
1155
1155
  const rulesResult =
@@ -1295,7 +1295,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1295
1295
  session ? session.trackEvalExecution(execution, abortController) : execution,
1296
1296
  getSessionId: () => sessionManager.getSessionId?.() ?? null,
1297
1297
  getHindsightSessionState: () => session?.getHindsightSessionState(),
1298
- getMnemopiSessionState: () => getMnemopiSessionState(session),
1298
+ getMnemopiSessionState: () => session?.getMnemopiSessionState(),
1299
1299
  getAgentId: () => resolvedAgentId,
1300
1300
  getToolByName: name => session?.getToolByName(name),
1301
1301
  agentRegistry,
@@ -1472,7 +1472,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1472
1472
  }
1473
1473
 
1474
1474
  const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
1475
- inlineExtensions.push(createAutoresearchExtension);
1475
+ inlineExtensions.push((await import("./autoresearch")).createAutoresearchExtension);
1476
1476
  if (customTools.length > 0) {
1477
1477
  inlineExtensions.push(createCustomToolsExtension(customTools));
1478
1478
  }
@@ -1607,9 +1607,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1607
1607
  // `ExtensionToolWrapper` installed below is the only place the per-tool approval gate runs.
1608
1608
  // A conditional runner means the approval system silently disappears for users with no
1609
1609
  // extensions, contradicting non-yolo `tools.approvalMode` settings without feedback.
1610
- // (Today `createAutoresearchExtension` is unconditionally pushed below, so this scenario
1611
- // is unreachable; the unconditional construction makes that invariant explicit instead of
1612
- // implicit, so a future change to make autoresearch optional cannot silently re-open the hole.)
1610
+ // (The builtin autoresearch extension is unconditionally loaded above, so this scenario
1611
+ // is unreachable; unconditional runner construction keeps that invariant explicit and
1612
+ // prevents future optional extensions from silently re-opening the hole.)
1613
1613
  const extensionRunner: ExtensionRunner = new ExtensionRunner(
1614
1614
  extensionsResult.extensions,
1615
1615
  extensionsResult.runtime,
@@ -1723,7 +1723,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1723
1723
 
1724
1724
  const repeatToolDescriptions = settings.get("repeatToolDescriptions");
1725
1725
  const eagerTasks = settings.get("task.eager");
1726
- const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
1726
+ const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
1727
1727
  const rebuildSystemPrompt = async (
1728
1728
  toolNames: string[],
1729
1729
  tools: Map<string, AgentTool>,
@@ -1749,7 +1749,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1749
1749
  const promptTools = buildSystemPromptToolMetadata(tools, {
1750
1750
  search_tool_bm25: { description: renderSearchToolBm25Description(discoverableToolsForDesc) },
1751
1751
  });
1752
- const memoryBackend = resolveMemoryBackend(settings);
1752
+ const memoryBackend = await resolveMemoryBackend(settings);
1753
1753
  const memoryInstructions = await memoryBackend.buildDeveloperInstructions(agentDir, settings, session);
1754
1754
 
1755
1755
  // Build combined append prompt: memory instructions + MCP server instructions
@@ -2002,6 +2002,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2002
2002
  onPayload,
2003
2003
  onResponse,
2004
2004
  sessionId: providerSessionId,
2005
+ promptCacheKey: options.providerPromptCacheKey,
2005
2006
  transformContext,
2006
2007
  steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
2007
2008
  followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
@@ -2018,9 +2019,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2018
2019
  kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
2019
2020
  preferWebsockets: preferOpenAICodexWebsockets,
2020
2021
  getToolContext: tc => toolContextStore.getContext(tc),
2021
- getApiKey: async provider => {
2022
+ getApiKey: async (provider, ctx) => {
2022
2023
  // Read agent.sessionId at call time so credential selection stays aligned
2023
2024
  // with metadataResolver after /new, fork, resume, or branch switches.
2025
+ // Retry steps (ctx carries an auth error) drive the central a/b/c
2026
+ // policy — force-refresh the same account, then rotate to a sibling —
2027
+ // and may legitimately yield no key when every account is exhausted.
2028
+ if (ctx?.error !== undefined) {
2029
+ return createApiKeyResolver(modelRegistry, provider, { sessionId: agent.sessionId })(ctx);
2030
+ }
2024
2031
  const key = await modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
2025
2032
  if (!key) {
2026
2033
  throw new Error(`No API key found for provider "${provider}"`);
@@ -2034,40 +2041,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2034
2041
  return streamSimple(streamModel, context, {
2035
2042
  ...streamOptions,
2036
2043
  openrouterVariant: streamOptions?.openrouterVariant ?? openrouterVariant,
2037
- onAuthError: async (provider, oldKey, error) => {
2038
- const message = error instanceof Error ? error.message : String(error);
2039
- // streamSimple invokes this for both 401 auth failures AND
2040
- // rotatable usage-limit errors (Codex usage_limit_reached,
2041
- // Anthropic usage_limit_reached, etc.). The two need
2042
- // different storage actions: a real 401 means the credential
2043
- // is bad and should be marked suspect; a usage limit just
2044
- // means this account is parked until reset and should be
2045
- // temporarily blocked so a sibling can pick the request up.
2046
- if (isUsageLimitError(message)) {
2047
- const retryAfterMs = extractRetryHint(undefined, message);
2048
- const switched = await modelRegistry.authStorage.markUsageLimitReached(provider, agent.sessionId, {
2049
- retryAfterMs,
2050
- signal: streamOptions?.signal,
2051
- });
2052
- logger.debug("Retrying provider request after usage-limit block", {
2053
- provider,
2054
- switched,
2055
- retryAfterMs,
2056
- error: message,
2057
- });
2058
- if (!switched) return undefined;
2059
- return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
2060
- }
2061
- await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
2062
- signal: streamOptions?.signal,
2063
- sessionId: agent.sessionId,
2064
- });
2065
- logger.debug("Retrying provider request after credential invalidation", {
2066
- provider,
2067
- error: message,
2068
- });
2069
- return modelRegistry.getApiKeyForProvider(provider, agent.sessionId);
2070
- },
2071
2044
  });
2072
2045
  },
2073
2046
  cursorExecHandlers,
@@ -2267,19 +2240,18 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2267
2240
  }
2268
2241
  }
2269
2242
 
2270
- logger.time("startMemoryStartupTask", () =>
2271
- Promise.resolve(
2272
- resolveMemoryBackend(settings).start({
2273
- session,
2274
- settings,
2275
- modelRegistry,
2276
- agentDir,
2277
- taskDepth,
2278
- parentHindsightSessionState: options.parentHindsightSessionState,
2279
- parentMnemopiSessionState: options.parentMnemopiSessionState,
2280
- }),
2281
- ),
2282
- );
2243
+ logger.time("startMemoryStartupTask", async () => {
2244
+ const memoryBackend = await resolveMemoryBackend(settings);
2245
+ await memoryBackend.start({
2246
+ session,
2247
+ settings,
2248
+ modelRegistry,
2249
+ agentDir,
2250
+ taskDepth,
2251
+ parentHindsightSessionState: options.parentHindsightSessionState,
2252
+ parentMnemopiSessionState: options.parentMnemopiSessionState,
2253
+ });
2254
+ });
2283
2255
 
2284
2256
  // Wire MCP manager callbacks to session for reactive tool updates.
2285
2257
  // Skip when reusing a parent's manager — the parent owns the callbacks.