@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.6

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 (165) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/types/capability/rule-buckets.d.ts +1 -1
  3. package/dist/types/capability/rule.d.ts +6 -1
  4. package/dist/types/cli/update-cli.d.ts +11 -1
  5. package/dist/types/config/model-registry.d.ts +18 -1
  6. package/dist/types/discovery/at-imports.d.ts +15 -0
  7. package/dist/types/edit/diff.d.ts +3 -2
  8. package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
  9. package/dist/types/eval/backend.d.ts +7 -0
  10. package/dist/types/eval/js/context-manager.d.ts +1 -0
  11. package/dist/types/eval/js/executor.d.ts +2 -0
  12. package/dist/types/eval/js/index.d.ts +1 -1
  13. package/dist/types/eval/js/shared/helpers.d.ts +6 -0
  14. package/dist/types/eval/js/shared/runtime.d.ts +5 -0
  15. package/dist/types/eval/js/worker-protocol.d.ts +6 -0
  16. package/dist/types/eval/py/executor.d.ts +7 -0
  17. package/dist/types/eval/py/index.d.ts +1 -1
  18. package/dist/types/exa/index.d.ts +1 -19
  19. package/dist/types/exa/mcp-client.d.ts +10 -3
  20. package/dist/types/exa/types.d.ts +0 -83
  21. package/dist/types/export/ttsr.d.ts +14 -0
  22. package/dist/types/extensibility/extensions/types.d.ts +8 -1
  23. package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
  24. package/dist/types/internal-urls/local-protocol.d.ts +10 -0
  25. package/dist/types/mcp/oauth-flow.d.ts +2 -2
  26. package/dist/types/modes/components/custom-editor.d.ts +3 -0
  27. package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
  28. package/dist/types/modes/components/status-line/index.d.ts +1 -0
  29. package/dist/types/modes/components/status-line/types.d.ts +31 -2
  30. package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
  31. package/dist/types/modes/image-references.d.ts +8 -3
  32. package/dist/types/modes/interactive-mode.d.ts +9 -1
  33. package/dist/types/modes/theme/theme.d.ts +2 -1
  34. package/dist/types/modes/types.d.ts +3 -1
  35. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  36. package/dist/types/session/agent-session.d.ts +0 -2
  37. package/dist/types/task/render.d.ts +1 -0
  38. package/dist/types/tools/ask.d.ts +1 -0
  39. package/dist/types/tools/browser/tab-worker.d.ts +15 -0
  40. package/dist/types/tools/index.d.ts +17 -2
  41. package/dist/types/tools/render-utils.d.ts +1 -1
  42. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  43. package/dist/types/utils/block-context.d.ts +35 -0
  44. package/dist/types/utils/git.d.ts +6 -0
  45. package/dist/types/utils/image-loading.d.ts +12 -0
  46. package/package.json +29 -9
  47. package/src/capability/rule-buckets.ts +4 -2
  48. package/src/capability/rule.ts +10 -1
  49. package/src/cli/auth-broker-cli.ts +6 -7
  50. package/src/cli/auth-gateway-cli.ts +4 -3
  51. package/src/cli/list-models.ts +5 -0
  52. package/src/cli/update-cli.ts +138 -16
  53. package/src/commit/agentic/tools/split-commit.ts +8 -1
  54. package/src/config/model-provider-priority.ts +1 -0
  55. package/src/config/model-registry.ts +81 -2
  56. package/src/debug/index.ts +4 -8
  57. package/src/discovery/at-imports.ts +273 -0
  58. package/src/discovery/builtin-rules/index.ts +4 -0
  59. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  60. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  61. package/src/discovery/helpers.ts +2 -1
  62. package/src/edit/diff.ts +114 -4
  63. package/src/edit/hashline/diff.ts +1 -1
  64. package/src/edit/hashline/execute.ts +1 -1
  65. package/src/edit/modes/patch.ts +6 -2
  66. package/src/edit/modes/replace.ts +1 -1
  67. package/src/edit/renderer.ts +12 -2
  68. package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
  69. package/src/eval/backend.ts +15 -0
  70. package/src/eval/js/context-manager.ts +4 -2
  71. package/src/eval/js/executor.ts +3 -0
  72. package/src/eval/js/index.ts +7 -1
  73. package/src/eval/js/shared/helpers.ts +53 -6
  74. package/src/eval/js/shared/runtime.ts +8 -0
  75. package/src/eval/js/worker-core.ts +1 -0
  76. package/src/eval/js/worker-protocol.ts +6 -0
  77. package/src/eval/py/executor.ts +12 -0
  78. package/src/eval/py/index.ts +7 -1
  79. package/src/eval/py/prelude.py +43 -4
  80. package/src/eval/py/runner.py +1 -0
  81. package/src/exa/index.ts +1 -26
  82. package/src/exa/mcp-client.ts +10 -10
  83. package/src/exa/types.ts +0 -97
  84. package/src/export/ttsr.ts +122 -1
  85. package/src/extensibility/extensions/types.ts +8 -1
  86. package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
  87. package/src/extensibility/plugins/doctor.ts +1 -1
  88. package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
  89. package/src/goals/tools/goal-tool.ts +1 -1
  90. package/src/internal-urls/docs-index.generated.ts +7 -6
  91. package/src/internal-urls/local-protocol.ts +13 -0
  92. package/src/lsp/render.ts +8 -6
  93. package/src/mcp/oauth-flow.ts +3 -3
  94. package/src/mcp/render.ts +7 -1
  95. package/src/modes/components/agent-dashboard.ts +6 -4
  96. package/src/modes/components/custom-editor.ts +12 -6
  97. package/src/modes/components/login-dialog.ts +1 -1
  98. package/src/modes/components/oauth-selector.ts +4 -4
  99. package/src/modes/components/read-tool-group.ts +10 -3
  100. package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
  101. package/src/modes/components/status-line/index.ts +1 -0
  102. package/src/modes/components/status-line/types.ts +23 -8
  103. package/src/modes/components/tool-execution.ts +1 -1
  104. package/src/modes/components/transcript-container.ts +17 -10
  105. package/src/modes/components/user-message.ts +6 -3
  106. package/src/modes/components/welcome.ts +1 -1
  107. package/src/modes/controllers/event-controller.ts +8 -0
  108. package/src/modes/controllers/extension-ui-controller.ts +143 -127
  109. package/src/modes/controllers/input-controller.ts +60 -11
  110. package/src/modes/controllers/mcp-command-controller.ts +52 -17
  111. package/src/modes/controllers/selector-controller.ts +4 -11
  112. package/src/modes/controllers/ssh-command-controller.ts +2 -2
  113. package/src/modes/image-references.ts +13 -7
  114. package/src/modes/interactive-mode.ts +35 -3
  115. package/src/modes/rpc/rpc-mode.ts +1 -1
  116. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
  117. package/src/modes/theme/theme.ts +95 -1
  118. package/src/modes/types.ts +3 -1
  119. package/src/modes/utils/ui-helpers.ts +14 -5
  120. package/src/prompts/tools/bash.md +1 -1
  121. package/src/prompts/tools/eval.md +4 -4
  122. package/src/sdk.ts +31 -14
  123. package/src/session/agent-session.ts +290 -196
  124. package/src/session/session-manager.ts +1 -1
  125. package/src/slash-commands/builtin-registry.ts +9 -1
  126. package/src/system-prompt.ts +15 -9
  127. package/src/task/index.ts +9 -1
  128. package/src/task/render.ts +36 -14
  129. package/src/tools/ask.ts +14 -5
  130. package/src/tools/bash-interactive.ts +1 -1
  131. package/src/tools/bash.ts +14 -2
  132. package/src/tools/browser/render.ts +5 -2
  133. package/src/tools/browser/tab-worker.ts +211 -91
  134. package/src/tools/debug.ts +5 -2
  135. package/src/tools/eval-render.ts +6 -3
  136. package/src/tools/eval.ts +1 -1
  137. package/src/tools/gh-renderer.ts +29 -15
  138. package/src/tools/index.ts +32 -4
  139. package/src/tools/inspect-image-renderer.ts +12 -5
  140. package/src/tools/job.ts +9 -6
  141. package/src/tools/memory-render.ts +19 -5
  142. package/src/tools/read.ts +165 -18
  143. package/src/tools/render-utils.ts +3 -1
  144. package/src/tools/resolve.ts +1 -1
  145. package/src/tools/review.ts +1 -1
  146. package/src/tools/ssh.ts +4 -1
  147. package/src/tools/todo.ts +8 -1
  148. package/src/tools/tool-timeouts.ts +1 -1
  149. package/src/tools/write.ts +1 -1
  150. package/src/tui/code-cell.ts +1 -1
  151. package/src/utils/block-context.ts +312 -0
  152. package/src/utils/git.ts +41 -0
  153. package/src/utils/image-loading.ts +31 -1
  154. package/src/web/search/providers/codex.ts +1 -1
  155. package/src/web/search/render.ts +14 -6
  156. package/dist/types/exa/factory.d.ts +0 -13
  157. package/dist/types/exa/render.d.ts +0 -19
  158. package/dist/types/exa/researcher.d.ts +0 -9
  159. package/dist/types/exa/search.d.ts +0 -9
  160. package/dist/types/exa/websets.d.ts +0 -9
  161. package/src/exa/factory.ts +0 -60
  162. package/src/exa/render.ts +0 -244
  163. package/src/exa/researcher.ts +0 -36
  164. package/src/exa/search.ts +0 -47
  165. package/src/exa/websets.ts +0 -248
@@ -3,30 +3,36 @@ import { logger } from "@oh-my-pi/pi-utils";
3
3
  import { type BlobPutResult, blobExtensionForImageMimeType } from "../session/blob-store";
4
4
  import { fileHyperlink } from "../tui/hyperlink";
5
5
 
6
- const IMAGE_REFERENCE_REGEX = /\[Image #([1-9]\d*)\]/g;
6
+ /** Matches `[Image #N]`/`[Image #N, WxH]` and `[Paste #N, +X lines]`/`[Paste #N, Y chars]` tokens.
7
+ * Group 1 is the kind (`Image`/`Paste`), group 2 the 1-based index. The optional metadata
8
+ * tail (`, …`) is captured loosely (no `]`/newline) so future label tweaks keep matching. */
9
+ export const PLACEHOLDER_REGEX = /\[(Image|Paste) #([1-9]\d*)(?:,[^\]\n]*)?\]/g;
7
10
 
8
11
  type ImageBlobWriter = (data: Buffer, options?: { extension?: string }) => Promise<BlobPutResult>;
9
12
  type ImageBlobWriterSync = (data: Buffer, options?: { extension?: string }) => BlobPutResult;
10
13
 
11
- export interface ImageReferenceRenderers {
14
+ export type PlaceholderKind = "image" | "paste";
15
+
16
+ export interface PlaceholderRenderers {
12
17
  renderText: (text: string) => string;
13
- renderReference: (label: string, index: number) => string;
18
+ renderReference: (label: string, kind: PlaceholderKind, index: number) => string;
14
19
  }
15
20
 
16
- export function renderImageReferences(text: string, renderers: ImageReferenceRenderers): string {
17
- IMAGE_REFERENCE_REGEX.lastIndex = 0;
21
+ export function renderPlaceholders(text: string, renderers: PlaceholderRenderers): string {
22
+ PLACEHOLDER_REGEX.lastIndex = 0;
18
23
  let result = "";
19
24
  let last = 0;
20
25
  let matched = false;
21
26
 
22
27
  for (;;) {
23
- const match = IMAGE_REFERENCE_REGEX.exec(text);
28
+ const match = PLACEHOLDER_REGEX.exec(text);
24
29
  if (match === null) break;
25
30
  matched = true;
26
31
  if (match.index > last) {
27
32
  result += renderers.renderText(text.slice(last, match.index));
28
33
  }
29
- result += renderers.renderReference(match[0], Number(match[1]));
34
+ const kind: PlaceholderKind = match[1] === "Paste" ? "paste" : "image";
35
+ result += renderers.renderReference(match[0], kind, Number(match[2]));
30
36
  last = match.index + match[0].length;
31
37
  }
32
38
 
@@ -65,7 +65,12 @@ import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slas
65
65
  import type { Goal, GoalModeState } from "../goals/state";
66
66
  import { resolveLocalUrlToPath } from "../internal-urls";
67
67
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
68
- import { humanizePlanTitle, type PlanApprovalDetails, resolveApprovedPlan } from "../plan-mode/approved-plan";
68
+ import {
69
+ humanizePlanTitle,
70
+ type PlanApprovalDetails,
71
+ resolveApprovedPlan,
72
+ resolvePlanTitle,
73
+ } from "../plan-mode/approved-plan";
69
74
  import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
70
75
  import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
71
76
  type: "text",
@@ -2265,6 +2270,33 @@ export class InteractiveMode implements InteractiveModeContext {
2265
2270
  await this.#startGoalFromObjective(objective);
2266
2271
  }
2267
2272
 
2273
+ /** Manually (re-)open the plan-review overlay — bound to `/plan-review`. Lets
2274
+ * the operator pull the review back up after dismissing it, or review a plan
2275
+ * the agent wrote without calling `resolve`. There is no fixed plan filename:
2276
+ * `getPlanReferencePath()` is empty until a plan is actually approved (and does
2277
+ * not survive a restart), so this drives off the newest `local://<slug>-plan.md`
2278
+ * the agent wrote — the files persist in the session artifacts dir, so the scan
2279
+ * works before any review and across restarts. */
2280
+ async openPlanReview(): Promise<void> {
2281
+ if (!this.planModeEnabled) {
2282
+ this.showWarning("Plan mode is not active.");
2283
+ return;
2284
+ }
2285
+ const noPlan = "No plan to review yet — write one to a local://<slug>-plan.md file first.";
2286
+ const [planFilePath] = await this.#listLocalPlanFiles();
2287
+ if (!planFilePath) {
2288
+ this.showWarning(noPlan);
2289
+ return;
2290
+ }
2291
+ const planContent = await this.#readPlanFile(planFilePath);
2292
+ if (planContent === null) {
2293
+ this.showWarning(noPlan);
2294
+ return;
2295
+ }
2296
+ const { title } = resolvePlanTitle({ planContent, planFilePath });
2297
+ await this.handlePlanApproval({ planFilePath, title, planExists: true });
2298
+ }
2299
+
2268
2300
  async handlePlanApproval(details: PlanApprovalDetails): Promise<void> {
2269
2301
  if (!this.planModeEnabled) {
2270
2302
  this.showWarning("Plan mode is not active.");
@@ -2806,8 +2838,8 @@ export class InteractiveMode implements InteractiveModeContext {
2806
2838
  this.#uiHelpers.updatePendingMessagesDisplay();
2807
2839
  }
2808
2840
 
2809
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void {
2810
- this.#uiHelpers.queueCompactionMessage(text, mode);
2841
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void {
2842
+ this.#uiHelpers.queueCompactionMessage(text, mode, images);
2811
2843
  }
2812
2844
 
2813
2845
  flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {
@@ -10,7 +10,7 @@
10
10
  * - Events: AgentSessionEvent objects streamed as they occur
11
11
  * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
12
12
  */
13
- import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
13
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
14
14
  import { $env, readJsonl, Snowflake } from "@oh-my-pi/pi-utils";
15
15
  import {
16
16
  type ExtensionUIContext,
@@ -1,20 +1,12 @@
1
1
  import type { AuthStorage } from "@oh-my-pi/pi-ai";
2
- import type { OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types";
2
+ import { PASTE_CODE_LOGIN_PROVIDERS } from "@oh-my-pi/pi-ai";
3
+ import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
3
4
  import { Input, matchesKey, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
4
5
  import { getAgentDbPath } from "@oh-my-pi/pi-utils";
5
6
  import { OAuthSelectorComponent } from "../../components/oauth-selector";
6
7
  import { theme } from "../../theme/theme";
7
8
  import type { SetupSceneHost, SetupTab } from "./types";
8
9
 
9
- /** Providers whose OAuth flow needs a pasted code/redirect URL rather than a callback server. */
10
- const CALLBACK_SERVER_PROVIDERS: Partial<Record<OAuthProvider, true>> = {
11
- anthropic: true,
12
- "openai-codex": true,
13
- "gitlab-duo": true,
14
- "google-gemini-cli": true,
15
- "google-antigravity": true,
16
- };
17
-
18
10
  function loginUrlLink(url: string): string {
19
11
  return `\x1b]8;;${url}\x07Open login URL\x1b]8;;\x07`;
20
12
  }
@@ -119,7 +111,7 @@ export class SignInTab implements SetupTab {
119
111
 
120
112
  async #login(providerId: string): Promise<void> {
121
113
  if (this.#loggingInProvider || this.#disposed) return;
122
- const useManualInput = CALLBACK_SERVER_PROVIDERS[providerId as OAuthProvider] === true;
114
+ const useManualInput = PASTE_CODE_LOGIN_PROVIDERS.has(providerId);
123
115
  this.#selector.stopValidation();
124
116
  this.#loggingInProvider = providerId;
125
117
  this.#statusLines = [theme.fg("dim", "Starting OAuth flow…")];
@@ -43,6 +43,7 @@ export type SymbolKey =
43
43
  | "status.running"
44
44
  | "status.shadowed"
45
45
  | "status.aborted"
46
+ | "status.done"
46
47
  // Navigation
47
48
  | "nav.cursor"
48
49
  | "nav.selected"
@@ -197,7 +198,29 @@ export type SymbolKey =
197
198
  | "tab.tools"
198
199
  | "tab.memory"
199
200
  | "tab.tasks"
200
- | "tab.providers";
201
+ | "tab.providers"
202
+ // Tool identity icons
203
+ | "tool.write"
204
+ | "tool.edit"
205
+ | "tool.bash"
206
+ | "tool.ssh"
207
+ | "tool.lsp"
208
+ | "tool.gh"
209
+ | "tool.webSearch"
210
+ | "tool.exa"
211
+ | "tool.browser"
212
+ | "tool.eval"
213
+ | "tool.debug"
214
+ | "tool.mcp"
215
+ | "tool.job"
216
+ | "tool.task"
217
+ | "tool.todo"
218
+ | "tool.memory"
219
+ | "tool.ask"
220
+ | "tool.resolve"
221
+ | "tool.review"
222
+ | "tool.inspectImage"
223
+ | "tool.goal";
201
224
 
202
225
  type SymbolMap = Record<SymbolKey, string>;
203
226
 
@@ -213,6 +236,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
213
236
  "status.running": "⟳",
214
237
  "status.shadowed": "◌",
215
238
  "status.aborted": "⏹",
239
+ "status.done": "•",
216
240
  // Navigation
217
241
  "nav.cursor": "❯",
218
242
  "nav.selected": "➤",
@@ -368,6 +392,28 @@ const UNICODE_SYMBOLS: SymbolMap = {
368
392
  "tab.memory": "🧠",
369
393
  "tab.tasks": "📦",
370
394
  "tab.providers": "🌐",
395
+ // Tool identity icons (per-tool signature glyph on the success header)
396
+ "tool.write": "✎",
397
+ "tool.edit": "✎",
398
+ "tool.bash": "❯",
399
+ "tool.ssh": "⇄",
400
+ "tool.lsp": "💡",
401
+ "tool.gh": "⎇",
402
+ "tool.webSearch": "⌕",
403
+ "tool.exa": "🔭",
404
+ "tool.browser": "🌐",
405
+ "tool.eval": "▶",
406
+ "tool.debug": "🐞",
407
+ "tool.mcp": "🔌",
408
+ "tool.job": "⚙",
409
+ "tool.task": "⇶",
410
+ "tool.todo": "☑",
411
+ "tool.memory": "🧠",
412
+ "tool.ask": "?",
413
+ "tool.resolve": "✓",
414
+ "tool.review": "◉",
415
+ "tool.inspectImage": "🖼",
416
+ "tool.goal": "◎",
371
417
  };
372
418
 
373
419
  const NERD_SYMBOLS: SymbolMap = {
@@ -392,6 +438,8 @@ const NERD_SYMBOLS: SymbolMap = {
392
438
  "status.shadowed": "◐",
393
439
  // pick:  | alt:  
394
440
  "status.aborted": "\uf04d",
441
+ // pick: • | alt: ● ·
442
+ "status.done": "•",
395
443
  // Navigation
396
444
  // pick:  | alt:  
397
445
  "nav.cursor": "\uf054",
@@ -638,6 +686,28 @@ const NERD_SYMBOLS: SymbolMap = {
638
686
  "tab.memory": "󰧑",
639
687
  "tab.tasks": "󰐱",
640
688
  "tab.providers": "󰖟",
689
+ // Tool identity icons (per-tool signature glyph on the success header)
690
+ "tool.write": "\uEA7F",
691
+ "tool.edit": "\uEA73",
692
+ "tool.bash": "\uEBCA",
693
+ "tool.ssh": "\uEB3A",
694
+ "tool.lsp": "\uEA61",
695
+ "tool.gh": "\uEA84",
696
+ "tool.webSearch": "\uEB01",
697
+ "tool.exa": "\uEB68",
698
+ "tool.browser": "\uEAAE",
699
+ "tool.eval": "\uEBAF",
700
+ "tool.debug": "\uEAD8",
701
+ "tool.mcp": "\uEB2D",
702
+ "tool.job": "\uEBA2",
703
+ "tool.task": "\uEA7E",
704
+ "tool.todo": "\uEAB3",
705
+ "tool.memory": "\uEACE",
706
+ "tool.ask": "\uEAC7",
707
+ "tool.resolve": "\uEBB1",
708
+ "tool.review": "\uEA70",
709
+ "tool.inspectImage": "\uEAEA",
710
+ "tool.goal": "\uEBF8",
641
711
  };
642
712
 
643
713
  const ASCII_SYMBOLS: SymbolMap = {
@@ -652,6 +722,7 @@ const ASCII_SYMBOLS: SymbolMap = {
652
722
  "status.running": "[~]",
653
723
  "status.shadowed": "[/]",
654
724
  "status.aborted": "[-]",
725
+ "status.done": "*",
655
726
  // Navigation
656
727
  "nav.cursor": ">",
657
728
  "nav.selected": "->",
@@ -805,6 +876,28 @@ const ASCII_SYMBOLS: SymbolMap = {
805
876
  "tab.memory": "[Y]",
806
877
  "tab.tasks": "[K]",
807
878
  "tab.providers": "[P]",
879
+ // Tool identity icons (per-tool signature glyph on the success header)
880
+ "tool.write": "+f",
881
+ "tool.edit": "~",
882
+ "tool.bash": "$",
883
+ "tool.ssh": "ssh",
884
+ "tool.lsp": "lsp",
885
+ "tool.gh": "gh",
886
+ "tool.webSearch": "web",
887
+ "tool.exa": "exa",
888
+ "tool.browser": "[w]",
889
+ "tool.eval": ">_",
890
+ "tool.debug": "dbg",
891
+ "tool.mcp": "<>",
892
+ "tool.job": "job",
893
+ "tool.task": ">>>",
894
+ "tool.todo": "[x]",
895
+ "tool.memory": "mem",
896
+ "tool.ask": "[?]",
897
+ "tool.resolve": "[v]",
898
+ "tool.review": "rev",
899
+ "tool.inspectImage": "[i]",
900
+ "tool.goal": "(o)",
808
901
  };
809
902
 
810
903
  const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
@@ -1485,6 +1578,7 @@ export class Theme {
1485
1578
  running: this.#symbols["status.running"],
1486
1579
  shadowed: this.#symbols["status.shadowed"],
1487
1580
  aborted: this.#symbols["status.aborted"],
1581
+ done: this.#symbols["status.done"],
1488
1582
  };
1489
1583
  }
1490
1584
 
@@ -35,6 +35,7 @@ import type { Theme } from "./theme/theme";
35
35
  export type CompactionQueuedMessage = {
36
36
  text: string;
37
37
  mode: "steer" | "followUp";
38
+ images?: ImageContent[];
38
39
  };
39
40
 
40
41
  export type SubmittedUserInput = {
@@ -180,7 +181,7 @@ export interface InteractiveModeContext {
180
181
  showNewVersionNotification(newVersion: string): void;
181
182
  clearEditor(): void;
182
183
  updatePendingMessagesDisplay(): void;
183
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void;
184
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void;
184
185
  flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void>;
185
186
  flushPendingBashComponents(): void;
186
187
  flushPendingModelSwitch(): Promise<void>;
@@ -317,6 +318,7 @@ export interface InteractiveModeContext {
317
318
  disableLoopMode(): void;
318
319
  pauseLoop(): void;
319
320
  handlePlanApproval(details: PlanApprovalDetails): Promise<void>;
321
+ openPlanReview(): Promise<void>;
320
322
 
321
323
  // Hook UI methods
322
324
  initHooksAndCustomTools(): Promise<void>;
@@ -161,7 +161,7 @@ export class UiHelpers {
161
161
  const typeLabel = job.type ? `[${job.type}]` : "[job]";
162
162
  const duration = typeof job.durationMs === "number" ? formatDuration(job.durationMs) : undefined;
163
163
  const line = [
164
- theme.fg("success", `${theme.status.success} Background job completed`),
164
+ theme.fg("success", `${theme.status.done} Background job completed`),
165
165
  theme.fg("dim", typeLabel),
166
166
  theme.fg("accent", jobId),
167
167
  duration ? theme.fg("dim", `(${duration})`) : undefined,
@@ -630,12 +630,18 @@ export class UiHelpers {
630
630
  }
631
631
  }
632
632
 
633
- queueCompactionMessage(text: string, mode: "steer" | "followUp"): void {
634
- this.ctx.compactionQueuedMessages.push({ text, mode } as CompactionQueuedMessage);
633
+ queueCompactionMessage(text: string, mode: "steer" | "followUp", images?: ImageContent[]): void {
634
+ const queuedImages = images && images.length > 0 ? images : undefined;
635
+ this.ctx.compactionQueuedMessages.push({ text, mode, images: queuedImages } as CompactionQueuedMessage);
635
636
  this.ctx.editor.addToHistory(text);
636
637
  this.ctx.editor.setText("");
638
+ this.ctx.editor.imageLinks = undefined;
639
+ this.ctx.pendingImages = [];
640
+ this.ctx.pendingImageLinks = [];
637
641
  this.ctx.updatePendingMessagesDisplay();
638
- this.ctx.showStatus("Queued message for after compaction");
642
+ this.ctx.showStatus(
643
+ queuedImages ? "Queued message with image for after compaction" : "Queued message for after compaction",
644
+ );
639
645
  }
640
646
 
641
647
  async #deliverQueuedMessage(message: CompactionQueuedMessage): Promise<void> {
@@ -644,7 +650,9 @@ export class UiHelpers {
644
650
  return;
645
651
  }
646
652
  await this.ctx.withLocalSubmission(message.text, () =>
647
- message.mode === "followUp" ? this.ctx.session.followUp(message.text) : this.ctx.session.steer(message.text),
653
+ message.mode === "followUp"
654
+ ? this.ctx.session.followUp(message.text, message.images)
655
+ : this.ctx.session.steer(message.text, message.images),
648
656
  );
649
657
  }
650
658
 
@@ -738,6 +746,7 @@ export class UiHelpers {
738
746
  const promptPromise = this.ctx.session
739
747
  .prompt(firstPrompt.text, {
740
748
  streamingBehavior: firstPrompt.mode === "followUp" ? "followUp" : "steer",
749
+ images: firstPrompt.images,
741
750
  })
742
751
  .catch((error: unknown) => {
743
752
  disposeFirstPrompt();
@@ -29,7 +29,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
29
29
 
30
30
  - `timeout` (seconds) caps the **wall-clock duration** of the command. When it elapses the process is killed and the call returns with a timeout annotation. Range: `1`–`3600`s; default `300`s (see `clampTimeout("bash", …)` in `tool-timeouts.ts`).
31
31
  - `async: true` only defers **reporting** of the result — it does NOT disable, extend, or detach the timeout. A daemon started with `async: true` is still killed when `timeout` elapses, regardless of how long the agent waits before reading the result.
32
- - For long-running daemons (dev servers, watchers): either pass an explicit large `timeout` (up to `3600`), or fully detach the process from this shell using `nohup … &` / `setsid &` / `disown` so it survives independent of the bash call's lifecycle.
32
+ - For long-running daemons (dev servers, watchers): pass an explicit large `timeout` (up to `3600`). The shell session persists across calls, so a backgrounded job (`cmd &`) keeps running between bash calls on its own.
33
33
  {{/if}}
34
34
  {{#if autoBackgroundEnabled}}
35
35
 
@@ -8,7 +8,7 @@ Cell fields:
8
8
  - `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}.
9
9
  - `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers.
10
10
  - `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`).
11
- - `timeout` (optional) — per-cell wall-clock budget in seconds (1-600). Default 30. It bounds the cell's **own** work, but is paused while an `agent()`/`parallel()`/`completion()` call is in flight — so a long fanout or a slow completion runs to completion, while the cell itself is still bounded. Compute, `print`/stdout, `log()`/`phase()`, and ordinary tool calls all count against the budget; raise `timeout` for a cell that does heavy local work or long non-agent tool calls.
11
+ - `timeout` (optional) — per-cell wall-clock budget in seconds (1-3600). Default 30. It bounds the cell's **own** work, but is paused while an `agent()`/`parallel()`/`completion()` call is in flight — so a long fanout or a slow completion runs to completion, while the cell itself is still bounded. Compute, `print`/stdout, `log()`/`phase()`, and ordinary tool calls all count against the budget; raise `timeout` for a cell that does heavy local work or long non-agent tool calls.
12
12
  - `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}}
13
13
 
14
14
  **Work incrementally:**
@@ -29,11 +29,11 @@ display(value) → None
29
29
  print(value, ...) → None
30
30
  Print to the cell's text output.
31
31
  read(path, offset?=1, limit?=None) → str
32
- Read file contents as text. offset/limit are 1-indexed line bounds.
32
+ Read file contents as text. offset/limit are 1-indexed line bounds. Accepts `local://…` (resolved to the session-local root, same place `read local://…` reads).
33
33
  write(path, content) → str
34
- Write content to a file (creates parent directories). Returns the resolved path.
34
+ Write content to a file (creates parent directories). Returns the resolved path. Accepts `local://…` to persist artifacts across turns / share with subagents.
35
35
  append(path, content) → str
36
- Append content to a file. Returns the resolved path.
36
+ Append content to a file. Returns the resolved path. Accepts `local://…`.
37
37
  tree(path?=".", max_depth?=3, show_hidden?=False) → str
38
38
  Render a directory tree.
39
39
  diff(a, b) → str
package/src/sdk.ts CHANGED
@@ -154,6 +154,7 @@ import {
154
154
  EditTool,
155
155
  EvalTool,
156
156
  FindTool,
157
+ filterInitialToolsForDiscoveryAll,
157
158
  getSearchTools,
158
159
  HIDDEN_TOOLS,
159
160
  isImageProviderPreference,
@@ -1427,7 +1428,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1427
1428
  const getArtifactsDir = () => sessionManager.getArtifactsDir();
1428
1429
  if (!options.parentTaskPrefix) {
1429
1430
  setActiveSkills(skills);
1430
- setActiveRules([...rulebookRules, ...alwaysApplyRules]);
1431
+ // Include TTSR rules so `rule://<name>` can resolve them too. They are
1432
+ // registered with the manager and bucketed out before rulebook/always,
1433
+ // so without this a TTSR-only rule (e.g. a triggered builtin) is not
1434
+ // addressable and `rule://` reports "Available: none".
1435
+ setActiveRules([...rulebookRules, ...alwaysApplyRules, ...ttsrManager.getRules()]);
1431
1436
  if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
1432
1437
  }
1433
1438
  const localProtocolOptions = options.localProtocolOptions ?? {
@@ -1570,6 +1575,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1570
1575
  }
1571
1576
  extensionsResult.runtime.pendingProviderRegistrations = [];
1572
1577
  }
1578
+ // Discover runtime (extension) provider catalogs now that they are
1579
+ // registered. The startup refreshInBackground() ran before extensions
1580
+ // loaded, so dynamic extension providers are only discovered here. Runs in
1581
+ // the background (cache-aware) so startup is never blocked on the fetch; the
1582
+ // model list re-renders when the catalog arrives, like other dynamic providers.
1583
+ void modelRegistry.refreshRuntimeProviders().catch(error => {
1584
+ logger.warn("runtime provider discovery failed", {
1585
+ error: error instanceof Error ? error.message : String(error),
1586
+ });
1587
+ });
1573
1588
 
1574
1589
  // Retry session-model candidates now that extension providers are
1575
1590
  // registered. The initial restore runs before extensions load, so a role
@@ -1951,19 +1966,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1951
1966
  // from the initial set unless they were explicitly requested or restored from persistence.
1952
1967
  // The model finds them via search_tool_bm25 and activates them on demand.
1953
1968
  if (effectiveDiscoveryMode === "all") {
1954
- const essentialBuiltinNames = new Set(computeEssentialBuiltinNames(settings));
1955
- const explicitlyRequestedToolNames = new Set(options.toolNames?.map(name => name.toLowerCase()) ?? []);
1956
- // Back-compat: persisted activations live under selectedMCPToolNames today (built-in
1957
- // activation persistence is a follow-up). MCP names won't collide with built-in names.
1958
- const restoredDiscoveredNames = new Set(existingSession.selectedMCPToolNames);
1959
- initialToolNames = initialToolNames.filter(name => {
1960
- const tool = toolRegistry.get(name);
1961
- if (!tool?.loadMode) return true; // not a built-in — leave MCP/custom/extension to existing logic
1962
- if (tool.loadMode === "essential") return true;
1963
- if (essentialBuiltinNames.has(name)) return true;
1964
- if (explicitlyRequestedToolNames.has(name)) return true;
1965
- if (restoredDiscoveredNames.has(name)) return true;
1966
- return false;
1969
+ // Tools a forced tool_choice will target must stay active, or the named
1970
+ // choice references a tool absent from the request (provider 400). Eager
1971
+ // todos force a named `todo` choice on the first turn.
1972
+ const forceActive = new Set<string>();
1973
+ if (settings.get("todo.eager") && settings.get("todo.enabled") && toolRegistry.has("todo")) {
1974
+ forceActive.add("todo");
1975
+ }
1976
+ initialToolNames = filterInitialToolsForDiscoveryAll(initialToolNames, {
1977
+ loadModeOf: name => toolRegistry.get(name)?.loadMode,
1978
+ essentialNames: new Set(computeEssentialBuiltinNames(settings)),
1979
+ explicitlyRequested: new Set(options.toolNames?.map(name => name.toLowerCase()) ?? []),
1980
+ // Back-compat: persisted activations live under selectedMCPToolNames today (built-in
1981
+ // activation persistence is a follow-up). MCP names won't collide with built-in names.
1982
+ restored: new Set(existingSession.selectedMCPToolNames),
1983
+ forceActive,
1967
1984
  });
1968
1985
  }
1969
1986