@oh-my-pi/pi-coding-agent 16.0.8 → 16.0.10

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 (47) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/cli.js +3004 -2976
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/collab/host.d.ts +2 -2
  5. package/dist/types/collab/protocol.d.ts +4 -5
  6. package/dist/types/commands/launch.d.ts +3 -0
  7. package/dist/types/config/model-resolver.d.ts +11 -2
  8. package/dist/types/config/settings-schema.d.ts +12 -2
  9. package/dist/types/goals/runtime.d.ts +4 -1
  10. package/dist/types/modes/print-mode.d.ts +2 -0
  11. package/dist/types/session/agent-session.d.ts +13 -0
  12. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  13. package/dist/types/slash-commands/helpers/collab-qrcode.d.ts +13 -0
  14. package/dist/types/tools/index.d.ts +9 -1
  15. package/dist/types/utils/image-loading.d.ts +12 -0
  16. package/dist/types/utils/qrcode.d.ts +48 -0
  17. package/package.json +12 -12
  18. package/src/cli/args.ts +10 -1
  19. package/src/cli/flag-tables.ts +1 -0
  20. package/src/collab/host.ts +4 -4
  21. package/src/collab/protocol.ts +48 -15
  22. package/src/commands/launch.ts +3 -0
  23. package/src/config/config-file.ts +1 -1
  24. package/src/config/keybindings.ts +2 -2
  25. package/src/config/model-registry.ts +16 -4
  26. package/src/config/model-resolver.ts +193 -35
  27. package/src/config/settings-schema.ts +14 -2
  28. package/src/config/settings.ts +3 -3
  29. package/src/goals/runtime.ts +19 -7
  30. package/src/internal-urls/docs-index.generated.txt +1 -1
  31. package/src/main.ts +10 -2
  32. package/src/modes/components/oauth-selector.ts +31 -2
  33. package/src/modes/interactive-mode.ts +7 -3
  34. package/src/modes/print-mode.ts +5 -1
  35. package/src/prompts/advisor/advise-tool.md +3 -1
  36. package/src/prompts/advisor/system.md +55 -12
  37. package/src/prompts/tools/inspect-image.md +1 -1
  38. package/src/sdk.ts +26 -7
  39. package/src/session/agent-session.ts +103 -16
  40. package/src/slash-commands/builtin-registry.ts +29 -11
  41. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  42. package/src/thinking.ts +25 -5
  43. package/src/tools/index.ts +10 -1
  44. package/src/tools/inspect-image.ts +72 -9
  45. package/src/utils/file-mentions.ts +5 -2
  46. package/src/utils/image-loading.ts +58 -0
  47. package/src/utils/qrcode.ts +535 -0
package/src/main.ts CHANGED
@@ -72,7 +72,7 @@ import { shouldShowStartupSplash } from "./startup-splash";
72
72
  import { discoverTitleSystemPromptFile, resolvePromptInput } from "./system-prompt";
73
73
  import { createPersistedSubagentReviverFactory } from "./task/persisted-revive";
74
74
  import { initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
75
- import { AUTO_THINKING } from "./thinking";
75
+ import { AUTO_THINKING, parseConfiguredThinkingLevel } from "./thinking";
76
76
  import type { LspStartupServerInfo } from "./tools";
77
77
  import {
78
78
  getChangelogPath,
@@ -857,7 +857,7 @@ async function buildSessionOptions(
857
857
  if (scopedModels.length > 0) {
858
858
  // `auto` is a session-level concept only; per-scoped-model (Ctrl+P) thinking
859
859
  // overrides stay concrete, so coerce the auto default to "unset" here.
860
- const defaultThinkingLevelSetting = activeSettings.get("defaultThinkingLevel");
860
+ const defaultThinkingLevelSetting = parseConfiguredThinkingLevel(activeSettings.get("defaultThinkingLevel"));
861
861
  const defaultThinkingLevel =
862
862
  defaultThinkingLevelSetting === AUTO_THINKING ? undefined : defaultThinkingLevelSetting;
863
863
  options.scopedModels = scopedModels.map(scopedModel => ({
@@ -1039,6 +1039,13 @@ export async function runRootCommand(
1039
1039
  });
1040
1040
  }
1041
1041
 
1042
+ // --print-thoughts (single-shot print mode) must surface reasoning, so un-hide
1043
+ // thinking before the session is built — otherwise a passive hideThinkingBlock
1044
+ // setting makes the provider omit summaries and the flag prints nothing. An
1045
+ // explicit --hide-thinking below still wins.
1046
+ if (parsedArgs.printThoughts && !isProtocolMode && !isInteractive) {
1047
+ settingsInstance.override("hideThinkingBlock", false);
1048
+ }
1042
1049
  // Apply --hide-thinking CLI flag (ephemeral, not persisted)
1043
1050
  if (parsedArgs.hideThinking) {
1044
1051
  settingsInstance.override("hideThinkingBlock", true);
@@ -1373,6 +1380,7 @@ export async function runRootCommand(
1373
1380
  messages: initialArgs.messages,
1374
1381
  initialMessage,
1375
1382
  initialImages,
1383
+ printThoughts: initialArgs.printThoughts,
1376
1384
  });
1377
1385
  if ($env.PI_TIMING) {
1378
1386
  logger.printTimings();
@@ -10,6 +10,7 @@ import {
10
10
  Spacer,
11
11
  TruncatedText,
12
12
  } from "@oh-my-pi/pi-tui";
13
+ import { settings } from "../../config/settings";
13
14
  import { theme } from "../../modes/theme/theme";
14
15
  import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
15
16
  import type { AuthStorage, CredentialOriginKind } from "../../session/auth-storage";
@@ -17,6 +18,20 @@ import { DynamicBorder } from "./dynamic-border";
17
18
 
18
19
  const OAUTH_SELECTOR_MAX_VISIBLE = 10;
19
20
 
21
+ /**
22
+ * Provider ids the user has disabled via settings. `/login` (login mode) hides
23
+ * these so a disabled provider's models stay out of reach end-to-end, mirroring
24
+ * the model picker's `disabledProviders` filtering. Reads the settings singleton
25
+ * defensively: it throws before `Settings.init()`, in which case nothing is disabled.
26
+ */
27
+ function getDisabledProviderIds(): ReadonlySet<string> {
28
+ try {
29
+ return new Set(settings.get("disabledProviders"));
30
+ } catch {
31
+ return new Set();
32
+ }
33
+ }
34
+
20
35
  /**
21
36
  * Rendered lines before the provider rows: top border, spacer, title, spacer
22
37
  * (must mirror the constructor's addChild order).
@@ -102,8 +117,22 @@ export class OAuthSelectorComponent extends Container {
102
117
 
103
118
  #loadProviders(): void {
104
119
  const providers = getOAuthProviders();
105
- this.#allProviders =
106
- this.#mode === "logout" ? providers.filter(provider => this.#hasSelectableAuth(provider.id)) : providers;
120
+ if (this.#mode === "logout") {
121
+ // Logout stays unfiltered by `disabledProviders`: a now-disabled
122
+ // provider may still hold stored credentials worth removing.
123
+ this.#allProviders = providers.filter(provider => this.#hasSelectableAuth(provider.id));
124
+ } else {
125
+ const disabled = getDisabledProviderIds();
126
+ // Hide a login entry when either its own id or the provider id it
127
+ // stores credentials under is disabled, so alias logins (e.g.
128
+ // `openai-codex-device` ⇒ `openai-codex`) disappear alongside the
129
+ // model provider they authenticate.
130
+ this.#allProviders = providers.filter(
131
+ provider =>
132
+ !disabled.has(provider.id) &&
133
+ !(provider.storeCredentialsAs && disabled.has(provider.storeCredentialsAs)),
134
+ );
135
+ }
107
136
  this.#filteredProviders = this.#allProviders;
108
137
  }
109
138
 
@@ -797,7 +797,7 @@ export class InteractiveMode implements InteractiveModeContext {
797
797
  await this.initHooksAndCustomTools();
798
798
 
799
799
  // Restore mode from session (e.g. plan mode on resume)
800
- this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession());
800
+ this.session.setSessionSwitchReconciler?.(() => this.#reconcileModeFromSession({ preserveActiveGoal: true }));
801
801
  await this.#reconcileModeFromSession();
802
802
 
803
803
  // Brand-new sessions optionally start in plan mode when the user has made it
@@ -1783,11 +1783,12 @@ export class InteractiveMode implements InteractiveModeContext {
1783
1783
  }
1784
1784
 
1785
1785
  /** Reconcile mode state from session entries on resume/switch. */
1786
- async #reconcileModeFromSession(): Promise<void> {
1786
+ async #reconcileModeFromSession(options?: { preserveActiveGoal?: boolean }): Promise<void> {
1787
1787
  await this.#clearTransientModeState();
1788
1788
  const sessionContext = this.sessionManager.buildSessionContext();
1789
1789
  const goalEnabled = this.session.settings.get("goal.enabled");
1790
1790
  if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
1791
+ this.session.goalRuntime.clearAccounting();
1791
1792
  this.sessionManager.appendModeChange("none");
1792
1793
  return;
1793
1794
  }
@@ -1802,7 +1803,9 @@ export class InteractiveMode implements InteractiveModeContext {
1802
1803
  mode: "active",
1803
1804
  goal,
1804
1805
  });
1805
- const restored = await this.session.goalRuntime.onThreadResumed();
1806
+ const restored = await this.session.goalRuntime.onThreadResumed({
1807
+ preserveActiveGoal: options?.preserveActiveGoal,
1808
+ });
1806
1809
  this.goalModeEnabled = restored?.enabled === true;
1807
1810
  this.goalModePaused = restored?.enabled !== true && restored?.goal.status === "paused";
1808
1811
  // sdk.ts excludes "goal" from the initial active tool set unconditionally.
@@ -1815,6 +1818,7 @@ export class InteractiveMode implements InteractiveModeContext {
1815
1818
  this.#updateGoalModeStatus();
1816
1819
  return;
1817
1820
  }
1821
+ this.session.goalRuntime.clearAccounting();
1818
1822
  if (!this.session.settings.get("plan.enabled")) {
1819
1823
  // Clear stale plan/plan_paused mode so re-enabling the setting
1820
1824
  // later doesn't unexpectedly restore an old plan session.
@@ -24,6 +24,8 @@ export interface PrintModeOptions {
24
24
  initialMessage?: string;
25
25
  /** Images to attach to the initial message */
26
26
  initialImages?: ImageContent[];
27
+ /** If true, include thinking blocks in text output */
28
+ printThoughts?: boolean;
27
29
  }
28
30
 
29
31
  /**
@@ -31,7 +33,7 @@ export interface PrintModeOptions {
31
33
  * Sends prompts to the agent and outputs the result.
32
34
  */
33
35
  export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {
34
- const { mode, messages = [], initialMessage, initialImages } = options;
36
+ const { mode, messages = [], initialMessage, initialImages, printThoughts } = options;
35
37
 
36
38
  // Emit session header for JSON mode
37
39
  if (mode === "json") {
@@ -108,6 +110,8 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
108
110
  for (const content of assistantMsg.content) {
109
111
  if (content.type === "text") {
110
112
  process.stdout.write(`${sanitizeText(content.text)}\n`);
113
+ } else if (printThoughts && content.type === "thinking" && content.thinking.trim().length > 0) {
114
+ process.stdout.write(`${sanitizeText(content.thinking)}\n`);
111
115
  }
112
116
  }
113
117
  }
@@ -1 +1,3 @@
1
- Send one concrete, terse piece of advice to the agent you are watching. Use sparingly; stay silent when nothing matters.
1
+ Send one concrete, terse piece of advice to the agent you are watching.
2
+ - Use sparingly; stay silent when nothing matters.
3
+ - Call it to head off likely-wrong or materially wasteful work.
@@ -1,32 +1,75 @@
1
1
  <system-conventions>
2
2
  RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` are aliases for `MUST NOT` and `SHOULD NOT`.
3
- You can explore the workspace; budget is 2–3 tool calls per advise (exception: critical bugs warrant deeper verification before raising a blocker).
4
3
  </system-conventions>
5
4
 
6
- You bring a different angle.
7
- The agent might not have thought about an edge case, spotted a hallucinated API, or realized a simpler approach exists.
5
+ You bring a different angle, and advocate for the user and the code-quality & robustness.
6
+ You're watching over the main agent as a peer-programmer:
7
+ - They might not have thought about an edge case, or realized a more elegant approach exists.
8
+ - They might be sinking deeper into a hole that will not get the user's request accomplished.
9
+
8
10
  Your job is to offer that view before they sink work into the wrong direction.
9
11
 
10
12
  <workflow>
11
- You receive the agent's transcript incrementally, including private thinking.
13
+ You receive the agent's transcript incrementally, including their thoughts.
12
14
  You have read-only access through `read`, `search`, `find` to verify your suspicions.
13
- Keep exploration lean — 2–3 calls per advise unless you've spotted a critical bug and need to be absolutely certain before raising a blocker.
15
+ Keep exploration lean:
16
+ - 2–3 tool calls per advise.
17
+ - Exception: critical bugs may need deeper verification before raising a blocker.
14
18
  </workflow>
15
19
 
16
20
  <communication>
17
- At most one `advise` per update. Prefer silence when the agent is on track. Address the agent directly. Offer alternatives, not lectures. Never restate what they know; never explain how to use the advisor.
18
- Do not comment merely to add insight, context, or a second opinion. NEVER restate information the agent already has, including tool or CLI errors returned directly to it. NEVER flag a problem that will surface on its own — type errors, LSP diagnostics, failed builds, failing tests, lint — the agent's own tooling catches those. NEVER repeat advice you already gave.
21
+ - You call `advise` to surface your commentary to the driving agent; at most one `advise` per update.
22
+ - Prefer silence when the agent is on track.
23
+ - Address the agent directly.
24
+ - Offer alternatives, not lectures.
25
+ - NEVER restate information the agent already has, including errors they have seen.
26
+ - Examples: type errors, LSP diagnostics, failed builds, failing tests, lint.
27
+ - NEVER repeat advice you already gave.
28
+ - NEVER nitpick about things user stated they are okay with. You are the advocate for the user.
19
29
  </communication>
20
30
 
21
31
  <critical>
22
- You SHOULD call `advise` when: agent might be heading the wrong way, missed an edge case, about to call a hallucinated API, going in circles, picking brittle approach over better one. Low confidence bar "this might be wrong" is worth noting if they didn't think about it.
32
+ A low-confidence bar applies ONLY to concrete technical risk:
33
+ - Generic uncertainty, vague unease, or user-intent ambiguity → stay SILENT.
34
+
23
35
  NEVER advise just to second-guess decisions the agent understands and is committed to, if you are not certain.
36
+
37
+ NEVER advise on intent or process:
38
+ - Do not push the agent to ask for clarification, confirm scope, or summarize input before acting.
39
+ - Do not question whether the user's ask is clear enough.
40
+ - Intent is the agent's domain; it defaults to informed action.
41
+ - Your lane: correctness, edge cases, design, process.
42
+
43
+ Cite the exact instruction or risk.
24
44
  </critical>
25
45
 
26
46
  <completeness>
27
- **`nit`** — Non-urgent cleanup, refactor, style, missed opportunity. Folded at next step boundary; agent keeps working. Examples: edge cases that don't break correctness, simplifications, better approach the agent can consider.
28
- **`concern`** Agent might be heading wrong or missed something material. Offers your view; agent decides. Use when: exploring wrong code path, picking fragile approach when better exists, missing constraint, hallucinated API, going in circles, edge case about to be baked in.
29
- **`blocker`** Stop and reconsider. Use ONLY when: continuing will clearly waste the turn, produce broken output, or the path is fundamentally unsound. Verify thoroughly before raising.
47
+ **`nit`**
48
+ - Non-urgent cleanup, refactor, style, missed opportunity.
49
+ - Folded at next step boundary; agent keeps working.
50
+ - Examples:
51
+ - Edge cases that don't break correctness.
52
+ - Simplifications.
53
+ - Better approach the agent can consider.
54
+
55
+ **`concern`**
56
+ - Agent might be heading wrong or missed something material.
57
+ - Offers your view; agent decides.
58
+ - Use when:
59
+ - Exploring wrong code path.
60
+ - Picking fragile approach when better exists.
61
+ - Not parallelizing when user request is obviously parallelizable.
62
+ - Missing constraint.
63
+ - Edge case about to be baked in.
64
+
65
+ **`blocker`**
66
+ - Stop and reconsider.
67
+ - Use ONLY when the agent making progress will clearly:
68
+ - Waste the users time with a larger refactor.
69
+ - Will require the user to interrupt the agent later on, due to them going in circles without a solution.
70
+ - Be fundamentally unsound.
71
+ - Verify thoroughly before raising.
30
72
  </completeness>
31
73
 
32
- You MAY suggest an approach or fix if you've explored enough to be confident. Your job is pair programming, not just bugs — offer the better designs, not just the warning.
74
+ You MAY suggest an approach or fix if you've explored enough to be confident.
75
+ Offer the better designs, not just the warning.
@@ -2,7 +2,7 @@ Inspects an image file with a vision-capable model and returns compact text anal
2
2
 
3
3
  <instruction>
4
4
  - Use this for image understanding tasks (OCR, UI/screenshot debugging, scene/object questions)
5
- - Provide `path` to the local image file
5
+ - Provide `path` as a local image file path, `Image #N` attachment label, or `attachment://N` URI
6
6
  - Write a specific `question`:
7
7
  - what to inspect
8
8
  - constraints (for example: "quote visible text verbatim", "only report confirmed findings")
package/src/sdk.ts CHANGED
@@ -144,6 +144,7 @@ import { AgentOutputManager } from "./task/output-manager";
144
144
  import {
145
145
  AUTO_THINKING,
146
146
  type ConfiguredThinkingLevel,
147
+ parseConfiguredThinkingLevel,
147
148
  parseThinkingLevel,
148
149
  resolveProvisionalAutoLevel,
149
150
  resolveThinkingLevelForModel,
@@ -1266,12 +1267,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1266
1267
  ? getRestorableSessionModels(existingSession.models, sessionManager.getLastModelChangeRole())
1267
1268
  : [];
1268
1269
  let restoredSessionModelIndex = -1;
1270
+ let restoredSessionThinkingLevel: ThinkingLevel | undefined;
1269
1271
  if (!hasExplicitModel && !model && sessionModelStrings.length > 0) {
1270
1272
  logger.time("restoreSessionModel", () => {
1271
1273
  let failedSessionModel: string | undefined;
1272
1274
  for (let i = 0; i < sessionModelStrings.length; i++) {
1273
1275
  const sessionModelStr = sessionModelStrings[i];
1274
- const parsedModel = parseModelString(sessionModelStr);
1276
+ const parsedModel = parseModelString(sessionModelStr, {
1277
+ allowMaxAlias: true,
1278
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
1279
+ });
1275
1280
  if (!parsedModel) {
1276
1281
  failedSessionModel ??= sessionModelStr;
1277
1282
  continue;
@@ -1281,6 +1286,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1281
1286
  if (restoredModel && hasModelAuth(restoredModel)) {
1282
1287
  model = restoredModel;
1283
1288
  restoredSessionModelIndex = i;
1289
+ restoredSessionThinkingLevel = parsedModel.thinkingLevel;
1284
1290
  break;
1285
1291
  }
1286
1292
  failedSessionModel ??= sessionModelStr;
@@ -1305,15 +1311,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1305
1311
  const taskDepth = options.taskDepth ?? 0;
1306
1312
 
1307
1313
  // Resolves the session/agent thinking level using the same precedence we
1308
- // apply at startup: explicit option → persisted session entry → default
1309
- // role's explicit selector → selected model's defaultLevelglobal
1310
- // settings default. Run again after extension role reclaim so the final
1311
- // model's own defaults aren't masked by an earlier fallback model's.
1314
+ // apply at startup: explicit option → persisted session entry → restored
1315
+ // model selector suffix default role's explicit selector selected
1316
+ // model's defaultLevel → global settings default. Run again after extension
1317
+ // role reclaim so the final model's own defaults aren't masked by an earlier
1318
+ // fallback model's.
1312
1319
  const pickInitialThinkingLevel = (selectedModel: Model | undefined): ConfiguredThinkingLevel | undefined => {
1313
1320
  let level = options.thinkingLevel;
1314
1321
  if (level === undefined && hasExistingSession && hasThinkingEntry) {
1315
1322
  level = parseThinkingLevel(existingSession.thinkingLevel);
1316
1323
  }
1324
+ if (level === undefined && !hasThinkingEntry && restoredSessionThinkingLevel !== undefined) {
1325
+ level = restoredSessionThinkingLevel;
1326
+ }
1317
1327
  if (level === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
1318
1328
  level = defaultRoleSpec.thinkingLevel;
1319
1329
  }
@@ -1321,7 +1331,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1321
1331
  level = selectedModel.thinking.defaultLevel;
1322
1332
  }
1323
1333
  if (level === undefined) {
1324
- level = settings.get("defaultThinkingLevel");
1334
+ level = parseConfiguredThinkingLevel(settings.get("defaultThinkingLevel"));
1325
1335
  }
1326
1336
  return level;
1327
1337
  };
@@ -1533,6 +1543,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1533
1543
  getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
1534
1544
  getActiveModelString,
1535
1545
  getActiveModel: () => agent?.state.model ?? model,
1546
+ getImageAttachments: () => session?.getImageAttachments() ?? [],
1536
1547
  getPlanModeState: () => session?.getPlanModeState(),
1537
1548
  getPlanReferencePath: () => session?.getPlanReferencePath() ?? "local://PLAN.md",
1538
1549
  getGoalModeState: () => session?.getGoalModeState(),
@@ -1905,13 +1916,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1905
1916
  if (!hasExplicitModel && sessionRetryLimit > 0) {
1906
1917
  for (let i = 0; i < sessionRetryLimit; i++) {
1907
1918
  const sessionModelStr = sessionModelStrings[i];
1908
- const parsedModel = parseModelString(sessionModelStr);
1919
+ const parsedModel = parseModelString(sessionModelStr, {
1920
+ allowMaxAlias: true,
1921
+ isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
1922
+ });
1909
1923
  if (!parsedModel) continue;
1910
1924
  const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
1911
1925
  if (restoredModel && hasModelAuth(restoredModel)) {
1912
1926
  model = restoredModel;
1913
1927
  modelFallbackMessage = undefined;
1914
1928
  restoredSessionModelIndex = i;
1929
+ restoredSessionThinkingLevel = parsedModel.thinkingLevel;
1915
1930
  // Recompute thinking-level from scratch against the reclaimed
1916
1931
  // model: any value derived from the earlier fallback model's
1917
1932
  // `thinking.defaultLevel` must not become sticky.
@@ -2585,6 +2600,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2585
2600
  if (watchdogFiles && watchdogFiles.length > 0) {
2586
2601
  advisorWatchdogPrompt = watchdogFiles.join("\n\n");
2587
2602
  }
2603
+ // Owned only when this session created the manager; subagents receive a
2604
+ // parent's manager via `options.mcpManager` and MUST NOT disconnect it.
2605
+ const ownedMcpManager = options.mcpManager ? undefined : mcpManager;
2588
2606
  session = new AgentSession({
2589
2607
  advisorWatchdogPrompt,
2590
2608
  agent,
@@ -2630,6 +2648,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2630
2648
  return out;
2631
2649
  }
2632
2650
  : undefined,
2651
+ disconnectOwnedMcpManager: ownedMcpManager ? () => ownedMcpManager.disconnectAll() : undefined,
2633
2652
  mcpDiscoveryEnabled,
2634
2653
  initialSelectedMCPToolNames,
2635
2654
  defaultSelectedMCPToolNames,