@oh-my-pi/pi-coding-agent 13.11.0 → 13.12.0

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 (74) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/package.json +7 -7
  3. package/src/capability/rule.ts +4 -0
  4. package/src/cli/commands/init-xdg.ts +27 -0
  5. package/src/cli/config-cli.ts +8 -3
  6. package/src/cli/shell-cli.ts +1 -1
  7. package/src/commands/config.ts +1 -1
  8. package/src/config/model-registry.ts +160 -26
  9. package/src/config/model-resolver.ts +84 -21
  10. package/src/config/settings-schema.ts +812 -647
  11. package/src/discovery/helpers.ts +11 -2
  12. package/src/exa/index.ts +1 -11
  13. package/src/exa/search.ts +1 -122
  14. package/src/exec/bash-executor.ts +62 -25
  15. package/src/extensibility/custom-tools/types.ts +2 -3
  16. package/src/extensibility/extensions/types.ts +2 -0
  17. package/src/extensibility/hooks/types.ts +2 -0
  18. package/src/index.ts +6 -6
  19. package/src/internal-urls/docs-index.generated.ts +3 -3
  20. package/src/lsp/config.ts +1 -0
  21. package/src/lsp/defaults.json +3 -3
  22. package/src/memories/index.ts +20 -7
  23. package/src/memories/storage.ts +46 -32
  24. package/src/modes/components/agent-dashboard.ts +23 -35
  25. package/src/modes/components/assistant-message.ts +25 -2
  26. package/src/modes/components/btw-panel.ts +104 -0
  27. package/src/modes/components/settings-defs.ts +5 -1
  28. package/src/modes/components/settings-selector.ts +6 -6
  29. package/src/modes/controllers/btw-controller.ts +193 -0
  30. package/src/modes/controllers/command-controller.ts +3 -1
  31. package/src/modes/controllers/event-controller.ts +4 -0
  32. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  33. package/src/modes/controllers/input-controller.ts +10 -1
  34. package/src/modes/controllers/selector-controller.ts +18 -17
  35. package/src/modes/interactive-mode.ts +22 -0
  36. package/src/modes/prompt-action-autocomplete.ts +17 -3
  37. package/src/modes/rpc/rpc-client.ts +30 -19
  38. package/src/modes/theme/theme.ts +28 -36
  39. package/src/modes/types.ts +4 -0
  40. package/src/modes/utils/ui-helpers.ts +3 -0
  41. package/src/patch/hashline.ts +120 -16
  42. package/src/prompts/system/btw-user.md +8 -0
  43. package/src/prompts/system/custom-system-prompt.md +1 -1
  44. package/src/prompts/system/system-prompt.md +1 -0
  45. package/src/prompts/tools/code-search.md +45 -0
  46. package/src/prompts/tools/hashline.md +3 -0
  47. package/src/prompts/tools/read.md +2 -2
  48. package/src/sdk.ts +36 -40
  49. package/src/session/agent-session.ts +65 -37
  50. package/src/session/blob-store.ts +32 -0
  51. package/src/session/compaction/compaction.ts +27 -6
  52. package/src/session/history-storage.ts +2 -2
  53. package/src/session/session-manager.ts +116 -44
  54. package/src/session/streaming-output.ts +17 -54
  55. package/src/slash-commands/builtin-registry.ts +11 -0
  56. package/src/system-prompt.ts +4 -17
  57. package/src/task/agents.ts +1 -1
  58. package/src/task/executor.ts +1 -1
  59. package/src/task/index.ts +9 -8
  60. package/src/tools/browser.ts +11 -0
  61. package/src/tools/exit-plan-mode.ts +6 -0
  62. package/src/tools/fetch.ts +1 -1
  63. package/src/tools/output-meta.ts +104 -9
  64. package/src/tools/read.ts +13 -26
  65. package/src/utils/title-generator.ts +70 -92
  66. package/src/utils/tools-manager.ts +1 -1
  67. package/src/web/scrapers/index.ts +7 -7
  68. package/src/web/scrapers/utils.ts +1 -0
  69. package/src/web/search/code-search.ts +385 -0
  70. package/src/web/search/index.ts +25 -280
  71. package/src/web/search/provider.ts +1 -1
  72. package/src/web/search/types.ts +28 -0
  73. package/src/exa/company.ts +0 -26
  74. package/src/exa/linkedin.ts +0 -26
@@ -177,16 +177,14 @@ export type SymbolKey =
177
177
  | "lang.archive"
178
178
  | "lang.binary"
179
179
  // Settings tab icons
180
- | "tab.display"
181
- | "tab.agent"
182
- | "tab.input"
180
+ | "tab.appearance"
181
+ | "tab.model"
182
+ | "tab.interaction"
183
+ | "tab.context"
184
+ | "tab.editing"
183
185
  | "tab.tools"
184
- | "tab.config"
185
- | "tab.services"
186
- | "tab.bash"
187
- | "tab.lsp"
188
- | "tab.ttsr"
189
- | "tab.status";
186
+ | "tab.tasks"
187
+ | "tab.providers";
190
188
 
191
189
  type SymbolMap = Record<SymbolKey, string>;
192
190
 
@@ -338,16 +336,14 @@ const UNICODE_SYMBOLS: SymbolMap = {
338
336
  "lang.archive": "🗜",
339
337
  "lang.binary": "⚙",
340
338
  // Settings tabs
341
- "tab.display": "🎨",
342
- "tab.agent": "🤖",
343
- "tab.input": "⌨",
339
+ "tab.appearance": "🎨",
340
+ "tab.model": "🤖",
341
+ "tab.interaction": "⌨",
342
+ "tab.context": "📋",
343
+ "tab.editing": "💻",
344
344
  "tab.tools": "🔧",
345
- "tab.config": "",
346
- "tab.services": "🌐",
347
- "tab.bash": "💻",
348
- "tab.lsp": "📝",
349
- "tab.ttsr": "⏱",
350
- "tab.status": "📊",
345
+ "tab.tasks": "📦",
346
+ "tab.providers": "🌐",
351
347
  };
352
348
 
353
349
  const NERD_SYMBOLS: SymbolMap = {
@@ -591,16 +587,14 @@ const NERD_SYMBOLS: SymbolMap = {
591
587
  "lang.archive": "\u{F187}",
592
588
  "lang.binary": "\u{F019A}",
593
589
  // Settings tab icons
594
- "tab.display": "󰃣",
595
- "tab.agent": "󰚩",
596
- "tab.input": "󰌌",
590
+ "tab.appearance": "󰃣",
591
+ "tab.model": "󰚩",
592
+ "tab.interaction": "󰌌",
593
+ "tab.context": "󰘸",
594
+ "tab.editing": "",
597
595
  "tab.tools": "󰠭",
598
- "tab.config": "󰒓",
599
- "tab.services": "󰖟",
600
- "tab.bash": "",
601
- "tab.lsp": "󰌌",
602
- "tab.ttsr": "󱎫",
603
- "tab.status": "󰄪",
596
+ "tab.tasks": "󰐱",
597
+ "tab.providers": "󰖟",
604
598
  };
605
599
 
606
600
  const ASCII_SYMBOLS: SymbolMap = {
@@ -750,16 +744,14 @@ const ASCII_SYMBOLS: SymbolMap = {
750
744
  "lang.archive": "zip",
751
745
  "lang.binary": "bin",
752
746
  // Settings tab icons
753
- "tab.display": "[D]",
754
- "tab.agent": "[A]",
755
- "tab.input": "[I]",
747
+ "tab.appearance": "[A]",
748
+ "tab.model": "[M]",
749
+ "tab.interaction": "[I]",
750
+ "tab.context": "[X]",
751
+ "tab.editing": "[E]",
756
752
  "tab.tools": "[T]",
757
- "tab.config": "[C]",
758
- "tab.services": "[S]",
759
- "tab.bash": "[B]",
760
- "tab.lsp": "[L]",
761
- "tab.ttsr": "[R]",
762
- "tab.status": "[=]",
753
+ "tab.tasks": "[K]",
754
+ "tab.providers": "[P]",
763
755
  };
764
756
 
765
757
  const SYMBOL_PRESETS: Record<SymbolPreset, SymbolMap> = {
@@ -56,6 +56,7 @@ export interface InteractiveModeContext {
56
56
  pendingMessagesContainer: Container;
57
57
  statusContainer: Container;
58
58
  todoContainer: Container;
59
+ btwContainer: Container;
59
60
  editor: CustomEditor;
60
61
  editorContainer: Container;
61
62
  statusLine: StatusLineComponent;
@@ -205,6 +206,9 @@ export interface InteractiveModeContext {
205
206
  handleDequeue(): void;
206
207
  handleBackgroundCommand(): void;
207
208
  handleImagePaste(): Promise<boolean>;
209
+ handleBtwCommand(question: string): Promise<void>;
210
+ hasActiveBtw(): boolean;
211
+ handleBtwEscape(): boolean;
208
212
  cycleThinkingLevel(): void;
209
213
  cycleRoleModel(options?: { temporary?: boolean }): Promise<void>;
210
214
  toggleToolOutputExpansion(): void;
@@ -232,6 +232,9 @@ export class UiHelpers {
232
232
  this.ctx.addMessageToChat(message);
233
233
  const lastChild = this.ctx.chatContainer.children[this.ctx.chatContainer.children.length - 1];
234
234
  const assistantComponent = lastChild instanceof AssistantMessageComponent ? lastChild : undefined;
235
+ if (assistantComponent) {
236
+ assistantComponent.setUsageInfo(message.usage);
237
+ }
235
238
  readGroup = null;
236
239
  const hasErrorStop = message.stopReason === "aborted" || message.stopReason === "error";
237
240
  const errorMessage = hasErrorStop
@@ -744,11 +744,130 @@ export interface CompactHashlineDiffOptions {
744
744
  maxOutputLines?: number;
745
745
  }
746
746
 
747
- const NUMBERED_DIFF_LINE_RE = /^([ +-])(\d+)\|(.*)$/;
747
+ const NUMBERED_DIFF_LINE_RE = /^([ +-])(\s*\d+)\|(.*)$/;
748
+ const HASHLINE_PREVIEW_PLACEHOLDER = " ";
748
749
 
749
750
  type DiffRunKind = " " | "+" | "-" | "meta";
750
751
  type DiffRun = { kind: DiffRunKind; lines: string[] };
751
752
 
753
+ interface ParsedNumberedDiffLine {
754
+ kind: " " | "+" | "-";
755
+ lineNumber: number;
756
+ lineWidth: number;
757
+ content: string;
758
+ raw: string;
759
+ }
760
+
761
+ interface CompactPreviewCounters {
762
+ oldLine?: number;
763
+ newLine?: number;
764
+ }
765
+
766
+ function parseNumberedDiffLine(line: string): ParsedNumberedDiffLine | undefined {
767
+ const match = NUMBERED_DIFF_LINE_RE.exec(line);
768
+ if (!match) return undefined;
769
+
770
+ const kind = match[1];
771
+ if (kind !== " " && kind !== "+" && kind !== "-") return undefined;
772
+
773
+ const lineField = match[2];
774
+ const lineNumber = Number(lineField.trim());
775
+ if (!Number.isInteger(lineNumber)) return undefined;
776
+
777
+ return { kind, lineNumber, lineWidth: lineField.length, content: match[3], raw: line };
778
+ }
779
+
780
+ function syncOldLineCounters(counters: CompactPreviewCounters, lineNumber: number): void {
781
+ if (counters.oldLine === undefined || counters.newLine === undefined) {
782
+ counters.oldLine = lineNumber;
783
+ counters.newLine = lineNumber;
784
+ return;
785
+ }
786
+
787
+ const delta = lineNumber - counters.oldLine;
788
+ counters.oldLine = lineNumber;
789
+ counters.newLine += delta;
790
+ }
791
+
792
+ function syncNewLineCounters(counters: CompactPreviewCounters, lineNumber: number): void {
793
+ if (counters.oldLine === undefined || counters.newLine === undefined) {
794
+ counters.oldLine = lineNumber;
795
+ counters.newLine = lineNumber;
796
+ return;
797
+ }
798
+
799
+ const delta = lineNumber - counters.newLine;
800
+ counters.oldLine += delta;
801
+ counters.newLine = lineNumber;
802
+ }
803
+
804
+ function formatCompactHashlineLine(kind: " " | "+", lineNumber: number, width: number, content: string): string {
805
+ const padded = String(lineNumber).padStart(width, " ");
806
+ return `${kind}${padded}#${computeLineHash(lineNumber, content)}|${content}`;
807
+ }
808
+
809
+ function formatCompactRemovedLine(lineNumber: number, width: number, content: string): string {
810
+ const padded = String(lineNumber).padStart(width, " ");
811
+ return `-${padded}${HASHLINE_PREVIEW_PLACEHOLDER}|${content}`;
812
+ }
813
+
814
+ function formatCompactPreviewLine(line: string, counters: CompactPreviewCounters): { kind: DiffRunKind; text: string } {
815
+ const parsed = parseNumberedDiffLine(line);
816
+ if (!parsed) return { kind: "meta", text: line };
817
+
818
+ if (parsed.content === "...") {
819
+ if (parsed.kind === "+") {
820
+ syncNewLineCounters(counters, parsed.lineNumber);
821
+ } else {
822
+ syncOldLineCounters(counters, parsed.lineNumber);
823
+ }
824
+ return { kind: parsed.kind, text: parsed.raw };
825
+ }
826
+
827
+ switch (parsed.kind) {
828
+ case "+": {
829
+ syncNewLineCounters(counters, parsed.lineNumber);
830
+ const newLine = counters.newLine;
831
+ if (newLine === undefined) return { kind: "+", text: parsed.raw };
832
+ const text = formatCompactHashlineLine("+", newLine, parsed.lineWidth, parsed.content);
833
+ counters.newLine = newLine + 1;
834
+ return { kind: "+", text };
835
+ }
836
+ case "-": {
837
+ syncOldLineCounters(counters, parsed.lineNumber);
838
+ const text = formatCompactRemovedLine(parsed.lineNumber, parsed.lineWidth, parsed.content);
839
+ counters.oldLine = parsed.lineNumber + 1;
840
+ return { kind: "-", text };
841
+ }
842
+ case " ": {
843
+ syncOldLineCounters(counters, parsed.lineNumber);
844
+ const newLine = counters.newLine;
845
+ if (newLine === undefined) return { kind: " ", text: parsed.raw };
846
+ const text = formatCompactHashlineLine(" ", newLine, parsed.lineWidth, parsed.content);
847
+ counters.oldLine = parsed.lineNumber + 1;
848
+ counters.newLine = newLine + 1;
849
+ return { kind: " ", text };
850
+ }
851
+ }
852
+ }
853
+
854
+ function splitDiffRuns(lines: string[]): DiffRun[] {
855
+ const runs: DiffRun[] = [];
856
+ const counters: CompactPreviewCounters = {};
857
+
858
+ for (const line of lines) {
859
+ const formatted = formatCompactPreviewLine(line, counters);
860
+ const prev = runs[runs.length - 1];
861
+ if (prev && prev.kind === formatted.kind) {
862
+ prev.lines.push(formatted.text);
863
+ continue;
864
+ }
865
+ runs.push({ kind: formatted.kind, lines: [formatted.text] });
866
+ }
867
+
868
+ return runs;
869
+ }
870
+
752
871
  function collapseFromStart(lines: string[], maxLines: number, label: string): string[] {
753
872
  if (lines.length <= maxLines) return lines;
754
873
  const hidden = lines.length - maxLines;
@@ -767,21 +886,6 @@ function collapseFromMiddle(lines: string[], maxLines: number, label: string): s
767
886
  return [...lines.slice(0, maxLines), ` ... ${hidden} more ${label} lines`, ...lines.slice(-maxLines)];
768
887
  }
769
888
 
770
- function splitDiffRuns(lines: string[]): DiffRun[] {
771
- const runs: DiffRun[] = [];
772
- for (const line of lines) {
773
- const match = NUMBERED_DIFF_LINE_RE.exec(line);
774
- const kind = (match?.[1] as " " | "+" | "-" | undefined) ?? "meta";
775
- const prev = runs[runs.length - 1];
776
- if (prev && prev.kind === kind) {
777
- prev.lines.push(line);
778
- continue;
779
- }
780
- runs.push({ kind, lines: [line] });
781
- }
782
- return runs;
783
- }
784
-
785
889
  /**
786
890
  * Build a compact diff preview suitable for model-visible tool responses.
787
891
  *
@@ -0,0 +1,8 @@
1
+ <btw>
2
+ This is an ephemeral side question for the current interactive session.
3
+ Answer briefly and directly using the conversation context already provided.
4
+ Do not use tools.
5
+ Do not ask follow-up questions.
6
+ Question:
7
+ {{question}}
8
+ </btw>
@@ -54,5 +54,5 @@ You **MUST** read `rule://<name>` when working in that domain.
54
54
  {{/list}}
55
55
  </rules>
56
56
  {{/if}}
57
- Current date and time: {{dateTime}}
57
+ Current date: {{date}}
58
58
  Current working directory: {{cwd}}
@@ -297,6 +297,7 @@ When a tool call fails, read the full error before doing anything else. When a f
297
297
  - You **MUST** exhaust tools/context/files first — explore.
298
298
  ## 7. Verification
299
299
  - Test everything rigorously → Future contributor cannot break behavior without failure. Prefer unit/e2e.
300
+ - You **MUST NOT** rely on mocks — they invent behaviors that never happen in production and hide real bugs.
300
301
  - You **SHOULD** run only tests you added/modified unless asked otherwise.
301
302
  - You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
302
303
 
@@ -0,0 +1,45 @@
1
+ Search code snippets, and technical content.
2
+ This tool behaves more like grep than natural-language web search.
3
+
4
+ <instruction>
5
+ - Query with exact symbols, identifiers, error strings, CLI flags, filenames, import paths, and short code fragments
6
+ - Start with the smallest distinctive token; widen or add one nearby token only if the first query is too broad
7
+ - Prefer exact syntax when punctuation carries meaning, such as `Promise.withResolvers`, `useEffect(`, `--watch`, or `"direnv loading"`
8
+ - Keep `query` terse; remove filler words, prose, and request framing
9
+ - Use `code_context` only for a few disambiguating tokens such as language, library, framework, repo, runtime, or API name
10
+ - If a multi-word literal matters exactly, quote the shortest stable phrase first, then refine
11
+ - When looking for usage examples of a specific API, search the symbol first; add surrounding call syntax only when needed
12
+ </instruction>
13
+
14
+ <parameters>
15
+ - query: Grep-style code search query; use exact tokens, short fragments, or short quoted phrases
16
+ - code_context: Optional disambiguation tokens only, not a sentence
17
+ </parameters>
18
+
19
+ <examples>
20
+ Good queries:
21
+ - `Promise.withResolvers`
22
+ - `DIRENV_LOG_FORMAT`
23
+ - `"direnv loading"`
24
+ - `useState` with `code_context: react hooks`
25
+ - `app.get(` with `code_context: express`
26
+ - `ERR_REQUIRE_ESM` with `code_context: node`
27
+
28
+ Bad queries:
29
+ - `Need the official or source-backed way to silence direnv loading output`
30
+ - `How do I use Promise.withResolvers in Bun?`
31
+ - `find examples of React state hooks in TypeScript projects`
32
+ - `search GitHub for express routing docs`
33
+ </examples>
34
+
35
+ <avoid>
36
+ - Do not use this tool for broad conceptual research, comparisons, or authoritative sourcing; use `web_search`, `web_search_deep`, or `fetch` instead
37
+ - Do not put full-sentence instructions into `query` or `code_context`
38
+ - Do not pack many weak terms into one query; one strong token plus minimal context usually works better
39
+ </avoid>
40
+
41
+ <critical>
42
+ - `query` should be grep-style code search, not a natural-language request
43
+ - `code_context` is optional and should stay short
44
+ - If you need explanations, best practices, or comprehensive answers, use broader web search tools instead of this one
45
+ </critical>
@@ -184,4 +184,7 @@ Use a trailing `""` to preserve the blank line between sibling declarations.
184
184
  - **Never target shared boundary lines.** Do not use `replace` spans that start, end, or pivot on a line that closes one construct and opens/separates another, such as `},{`, `}),`, `} else {`, or `} catch (err) {`. Those lines are not owned by a single block. Move the range inward to body-only lines, or widen it to consume one whole owned construct including its true trailing delimiter.
185
185
  - **`lines` must not extend past `end`.** `lines` replaces exactly `pos..end`. Content after `end` survives. If you include lines in `lines` that exist after `end`, they will appear twice. Either extend `end` to cover all lines you are re-emitting, or remove the extra lines from `lines`.
186
186
  - `lines` entries **MUST** be literal file content with indentation copied exactly from the `read` output. If the file uses tabs, use a real tab character.
187
+ - After any successful `edit` call on a file, the next change to that same file **MUST** start with a fresh `read`. Do not chain a second `edit` call off stale mental state, even if the intended range is nearby.
188
+ - If you need a second change in the same local region, default to one wider `replace` over the whole owned block instead of a sequence of micro-edits on adjacent lines. Repeated small patches in a moving region are unstable.
189
+ - If a local region is already malformed or a prior patch partially landed, stop nibbling at it. Re-read the file and replace the full owned block from a stable boundary; for a small file, prefer rewriting the file over stacking more tiny repairs.
187
190
  </critical>
@@ -4,10 +4,10 @@ Reads files from local filesystem or internal URLs.
4
4
  - Reads up to {{DEFAULT_LIMIT}} lines default
5
5
  - Use `offset` and `limit` for large files; max {{DEFAULT_MAX_LINES}} lines per call
6
6
  {{#if IS_HASHLINE_MODE}}
7
- - Text output is CID prefixed: `LINE#ID:content`
7
+ - Filesystem output is CID prefixed: `LINE#ID:content`
8
8
  {{else}}
9
9
  {{#if IS_LINE_NUMBER_MODE}}
10
- - Text output is line-number-prefixed
10
+ - Filesystem output is line-number-prefixed
11
11
  {{/if}}
12
12
  {{/if}}
13
13
  - Supports images (PNG, JPG) and PDFs
package/src/sdk.ts CHANGED
@@ -89,10 +89,13 @@ import {
89
89
  GrepTool,
90
90
  getSearchTools,
91
91
  HIDDEN_TOOLS,
92
+ isCodeSearchProviderId,
93
+ isSearchProviderPreference,
92
94
  loadSshTool,
93
95
  PythonTool,
94
96
  ReadTool,
95
97
  ResolveTool,
98
+ setPreferredCodeSearchProvider,
96
99
  setPreferredImageProvider,
97
100
  setPreferredSearchProvider,
98
101
  type Tool,
@@ -224,17 +227,17 @@ export {
224
227
  BashTool,
225
228
  // Tool classes and factories
226
229
  BUILTIN_TOOLS,
227
- HIDDEN_TOOLS,
228
230
  createTools,
229
231
  EditTool,
230
232
  FindTool,
231
233
  GrepTool,
234
+ HIDDEN_TOOLS,
232
235
  loadSshTool,
233
236
  PythonTool,
234
237
  ReadTool,
235
238
  ResolveTool,
236
- WriteTool,
237
239
  type ToolSession,
240
+ WriteTool,
238
241
  };
239
242
 
240
243
  // Helper Functions
@@ -628,8 +631,20 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
628
631
  options.skills === undefined ? discoverSkills(cwd, agentDir, skillsSettings) : undefined;
629
632
 
630
633
  // Initialize provider preferences from settings
631
- setPreferredSearchProvider(settings.get("providers.webSearch") ?? "auto");
632
- setPreferredImageProvider(settings.get("providers.image") ?? "auto");
634
+ const webSearchProvider = settings.get("providers.webSearch");
635
+ if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
636
+ setPreferredSearchProvider(webSearchProvider);
637
+ }
638
+
639
+ const codeSearchProvider = settings.get("providers.codeSearch");
640
+ if (typeof codeSearchProvider === "string" && isCodeSearchProviderId(codeSearchProvider)) {
641
+ setPreferredCodeSearchProvider(codeSearchProvider);
642
+ }
643
+
644
+ const imageProvider = settings.get("providers.image");
645
+ if (imageProvider === "auto" || imageProvider === "gemini" || imageProvider === "openrouter") {
646
+ setPreferredImageProvider(imageProvider);
647
+ }
633
648
 
634
649
  const sessionManager = options.sessionManager ?? logger.time("sessionManager", SessionManager.create, cwd);
635
650
  const sessionId = sessionManager.getSessionId();
@@ -687,19 +702,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
687
702
  }
688
703
  }
689
704
 
690
- // For subagent sessions using GitHub Copilot, add X-Initiator header
691
- // to ensure proper billing (agent-initiated vs user-initiated)
692
705
  const taskDepth = options.taskDepth ?? 0;
693
- const forceCopilotAgentInitiator = taskDepth > 0;
694
- if (forceCopilotAgentInitiator && model?.provider === "github-copilot") {
695
- model = {
696
- ...model,
697
- headers: {
698
- ...model.headers,
699
- "X-Initiator": "agent",
700
- },
701
- };
702
- }
703
706
 
704
707
  let thinkingLevel = options.thinkingLevel;
705
708
 
@@ -962,19 +965,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
962
965
  customTools.push(...(geminiImageTools as unknown as CustomTool[]));
963
966
  }
964
967
 
965
- // Add specialized Exa web search tools if EXA_API_KEY is available
966
- const exaSettings = settings.getGroup("exa");
967
- if (exaSettings.enabled && exaSettings.enableSearch) {
968
- const exaSearchTools = await logger.timeAsync("getSearchTools", getSearchTools, {
969
- enableLinkedin: exaSettings.enableLinkedin as boolean,
970
- enableCompany: exaSettings.enableCompany as boolean,
971
- });
972
- // Filter out the base web_search (already in built-in tools), add specialized Exa tools
973
- const specializedTools = exaSearchTools.filter(t => t.name !== "web_search");
974
- if (specializedTools.length > 0) {
975
- customTools.push(...specializedTools);
976
- }
977
- }
968
+ // Add web search tools
969
+ customTools.push(...getSearchTools());
978
970
 
979
971
  // Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
980
972
  const builtInToolNames = builtinTools.map(t => t.name);
@@ -1336,6 +1328,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1336
1328
  if (!obfuscator?.hasSecrets()) return converted;
1337
1329
  return obfuscateMessages(obfuscator, converted);
1338
1330
  };
1331
+ const transformContext = extensionRunner
1332
+ ? async (messages: AgentMessage[], _signal?: AbortSignal) => {
1333
+ return await extensionRunner.emitContext(messages);
1334
+ }
1335
+ : undefined;
1336
+ const onPayload = extensionRunner
1337
+ ? async (payload: unknown, _model?: Model) => {
1338
+ return await extensionRunner.emitBeforeProviderRequest(payload);
1339
+ }
1340
+ : undefined;
1339
1341
 
1340
1342
  const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
1341
1343
  toolContextStore.setUIContext(uiContext, hasUI);
@@ -1358,17 +1360,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1358
1360
  tools: initialTools,
1359
1361
  },
1360
1362
  convertToLlm: convertToLlmFinal,
1361
- onPayload: extensionRunner
1362
- ? async (payload, _model) => {
1363
- return extensionRunner.emitBeforeProviderRequest(payload);
1364
- }
1365
- : undefined,
1363
+ onPayload,
1366
1364
  sessionId: sessionManager.getSessionId(),
1367
- transformContext: extensionRunner
1368
- ? async messages => {
1369
- return extensionRunner.emitContext(messages);
1370
- }
1371
- : undefined,
1365
+ transformContext,
1372
1366
  steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
1373
1367
  followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
1374
1368
  interruptMode: settings.get("interruptMode") ?? "immediate",
@@ -1443,9 +1437,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1443
1437
  skillsSettings: settings.getGroup("skills") as Required<SkillsSettings>,
1444
1438
  modelRegistry,
1445
1439
  toolRegistry,
1440
+ transformContext,
1441
+ onPayload,
1442
+ convertToLlm: convertToLlmFinal,
1446
1443
  rebuildSystemPrompt,
1447
1444
  ttsrManager,
1448
- forceCopilotAgentInitiator,
1449
1445
  obfuscator,
1450
1446
  asyncJobManager,
1451
1447
  pendingActionStore,