@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.2

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 (126) hide show
  1. package/CHANGELOG.md +92 -20
  2. package/dist/cli.js +3485 -3448
  3. package/dist/types/advisor/config.d.ts +14 -6
  4. package/dist/types/advisor/runtime.d.ts +20 -11
  5. package/dist/types/autolearn/controller.d.ts +1 -0
  6. package/dist/types/config/model-discovery.d.ts +17 -0
  7. package/dist/types/config/model-resolver.d.ts +6 -2
  8. package/dist/types/config/settings-schema.d.ts +32 -7
  9. package/dist/types/config/settings.d.ts +50 -0
  10. package/dist/types/cursor.d.ts +11 -0
  11. package/dist/types/discovery/helpers.d.ts +5 -2
  12. package/dist/types/exec/bash-executor.d.ts +2 -0
  13. package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  15. package/dist/types/extensibility/extensions/types.d.ts +18 -0
  16. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
  17. package/dist/types/extensibility/shared-events.d.ts +6 -0
  18. package/dist/types/extensibility/utils.d.ts +2 -2
  19. package/dist/types/mcp/transports/stdio.d.ts +13 -1
  20. package/dist/types/modes/components/advisor-config.d.ts +8 -1
  21. package/dist/types/modes/components/hook-editor.d.ts +7 -0
  22. package/dist/types/modes/components/model-hub.d.ts +5 -2
  23. package/dist/types/modes/components/session-selector.d.ts +2 -0
  24. package/dist/types/modes/controllers/command-controller.d.ts +8 -0
  25. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  26. package/dist/types/modes/print-mode.d.ts +1 -1
  27. package/dist/types/modes/warp-events.d.ts +24 -0
  28. package/dist/types/modes/warp-events.test.d.ts +1 -0
  29. package/dist/types/plan-mode/model-transition.d.ts +47 -0
  30. package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
  31. package/dist/types/registry/agent-lifecycle.d.ts +26 -1
  32. package/dist/types/sdk.d.ts +13 -2
  33. package/dist/types/session/agent-session.d.ts +34 -10
  34. package/dist/types/session/session-history-format.d.ts +10 -0
  35. package/dist/types/session/session-manager.d.ts +14 -0
  36. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
  37. package/dist/types/task/label.d.ts +1 -1
  38. package/dist/types/telemetry-export.d.ts +34 -9
  39. package/dist/types/tools/approval.d.ts +8 -0
  40. package/dist/types/tools/bash.d.ts +2 -0
  41. package/dist/types/tools/essential-tools.d.ts +29 -0
  42. package/dist/types/tools/image-gen.d.ts +2 -1
  43. package/dist/types/tools/index.d.ts +1 -0
  44. package/dist/types/tools/xdev.d.ts +11 -2
  45. package/dist/types/utils/title-generator.d.ts +2 -1
  46. package/dist/types/web/search/providers/kimi.d.ts +4 -1
  47. package/dist/types/web/search/types.d.ts +1 -1
  48. package/package.json +21 -16
  49. package/src/advisor/__tests__/advisor.test.ts +1304 -42
  50. package/src/advisor/__tests__/config.test.ts +58 -2
  51. package/src/advisor/config.ts +76 -24
  52. package/src/advisor/runtime.ts +445 -92
  53. package/src/autolearn/controller.ts +23 -28
  54. package/src/cli.ts +5 -1
  55. package/src/config/model-discovery.ts +81 -21
  56. package/src/config/model-registry.ts +25 -6
  57. package/src/config/model-resolver.ts +14 -7
  58. package/src/config/settings-schema.ts +42 -6
  59. package/src/config/settings.ts +405 -25
  60. package/src/cursor.ts +20 -3
  61. package/src/debug/report-bundle.ts +40 -4
  62. package/src/discovery/helpers.ts +28 -5
  63. package/src/exec/bash-executor.ts +14 -5
  64. package/src/extensibility/custom-tools/loader.ts +3 -3
  65. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  66. package/src/extensibility/extensions/loader.ts +3 -3
  67. package/src/extensibility/extensions/managed-timers.ts +83 -0
  68. package/src/extensibility/extensions/runner.ts +26 -0
  69. package/src/extensibility/extensions/types.ts +18 -0
  70. package/src/extensibility/extensions/wrapper.ts +2 -1
  71. package/src/extensibility/hooks/loader.ts +3 -3
  72. package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
  73. package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
  74. package/src/extensibility/plugins/manager.ts +2 -2
  75. package/src/extensibility/shared-events.ts +6 -0
  76. package/src/extensibility/utils.ts +91 -25
  77. package/src/irc/bus.ts +22 -3
  78. package/src/launch/broker.ts +3 -2
  79. package/src/launch/client.ts +2 -2
  80. package/src/launch/presence.ts +2 -2
  81. package/src/lsp/client.ts +1 -1
  82. package/src/main.ts +11 -8
  83. package/src/mcp/manager.ts +9 -3
  84. package/src/mcp/transports/stdio.ts +103 -23
  85. package/src/modes/components/advisor-config.ts +65 -3
  86. package/src/modes/components/ask-dialog.ts +1 -1
  87. package/src/modes/components/hook-editor.ts +18 -3
  88. package/src/modes/components/model-hub.ts +138 -42
  89. package/src/modes/components/session-selector.ts +4 -0
  90. package/src/modes/components/status-line/component.test.ts +1 -0
  91. package/src/modes/components/status-line/segments.ts +21 -6
  92. package/src/modes/controllers/command-controller.ts +167 -47
  93. package/src/modes/controllers/event-controller.ts +5 -0
  94. package/src/modes/controllers/extension-ui-controller.ts +4 -22
  95. package/src/modes/controllers/input-controller.ts +12 -12
  96. package/src/modes/controllers/selector-controller.ts +191 -31
  97. package/src/modes/interactive-mode.ts +139 -54
  98. package/src/modes/print-mode.ts +3 -3
  99. package/src/modes/rpc/host-tools.ts +2 -1
  100. package/src/modes/rpc/rpc-mode.ts +19 -4
  101. package/src/modes/warp-events.test.ts +794 -0
  102. package/src/modes/warp-events.ts +232 -0
  103. package/src/plan-mode/model-transition.test.ts +60 -0
  104. package/src/plan-mode/model-transition.ts +51 -0
  105. package/src/registry/agent-lifecycle.ts +133 -18
  106. package/src/sdk.ts +221 -42
  107. package/src/session/agent-session.ts +1285 -348
  108. package/src/session/session-history-format.ts +20 -5
  109. package/src/session/session-manager.ts +48 -0
  110. package/src/slash-commands/builtin-registry.ts +7 -0
  111. package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
  112. package/src/task/executor.ts +1 -1
  113. package/src/task/label.ts +2 -0
  114. package/src/telemetry-export.ts +453 -97
  115. package/src/tools/approval.ts +11 -0
  116. package/src/tools/bash.ts +71 -38
  117. package/src/tools/essential-tools.ts +45 -0
  118. package/src/tools/gh.ts +169 -2
  119. package/src/tools/image-gen.ts +69 -7
  120. package/src/tools/index.ts +7 -5
  121. package/src/tools/read.ts +48 -3
  122. package/src/tools/write.ts +22 -4
  123. package/src/tools/xdev.ts +14 -3
  124. package/src/utils/title-generator.ts +15 -4
  125. package/src/web/search/providers/kimi.ts +18 -12
  126. package/src/web/search/types.ts +6 -1
package/src/sdk.ts CHANGED
@@ -2,13 +2,21 @@ import {
2
2
  Agent,
3
3
  type AgentEvent,
4
4
  type AgentMessage,
5
+ type AgentOptions,
5
6
  type AgentTelemetryConfig,
6
7
  type AgentTool,
7
8
  AppendOnlyContextManager,
8
9
  filterProviderReplayMessages,
9
10
  type ThinkingLevel,
10
11
  } from "@oh-my-pi/pi-agent-core";
11
- import type { Context, CredentialDisabledEvent, Message, Model, SimpleStreamOptions } from "@oh-my-pi/pi-ai";
12
+ import type {
13
+ Context,
14
+ CredentialDisabledEvent,
15
+ Message,
16
+ Model,
17
+ ProviderSessionState,
18
+ SimpleStreamOptions,
19
+ } from "@oh-my-pi/pi-ai";
12
20
  import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
13
21
  import {
14
22
  getOpenAICodexTransportDetails,
@@ -154,6 +162,7 @@ import {
154
162
  createTools,
155
163
  createVibeTools,
156
164
  type DeferredDiagnosticsEntry,
165
+ defaultLoadModeForToolName,
157
166
  discoverStartupLspServers,
158
167
  EditTool,
159
168
  EvalTool,
@@ -890,7 +899,7 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
890
899
  description: tool.description,
891
900
  parameters: tool.parameters,
892
901
  hidden: tool.hidden,
893
- loadMode: tool.loadMode ?? "discoverable",
902
+ loadMode: defaultLoadModeForToolName(tool.name, tool.loadMode),
894
903
  deferrable: tool.deferrable,
895
904
  approval: typeof tool.approval === "function" ? tool.approval.bind(tool) : tool.approval,
896
905
  mcpServerName: tool.mcpServerName,
@@ -1055,6 +1064,88 @@ function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
1055
1064
  }
1056
1065
  return commands;
1057
1066
  }
1067
+
1068
+ /** Dependencies used to construct an isolated auto-learn capture agent. */
1069
+ export interface AutoLearnCaptureRunnerOptions {
1070
+ sourceAgent: Agent;
1071
+ captureTools: AgentTool[];
1072
+ createAgent: (options: AgentOptions) => Agent;
1073
+ onPayload?: SimpleStreamOptions["onPayload"];
1074
+ onResponse?: SimpleStreamOptions["onResponse"];
1075
+ createSessionId?: () => string;
1076
+ }
1077
+
1078
+ /** Build a private capture runner over a detached message snapshot and provider session. */
1079
+ export function createAutoLearnCaptureRunner(
1080
+ options: AutoLearnCaptureRunnerOptions,
1081
+ ): (content: string, signal?: AbortSignal) => Promise<void> {
1082
+ return async (content, signal) => {
1083
+ if (options.captureTools.length === 0 || signal?.aborted) return;
1084
+ const captureModel = options.sourceAgent.state.model;
1085
+ if (!captureModel) return;
1086
+
1087
+ const captureSessionId = options.createSessionId?.() ?? Bun.randomUUIDv7();
1088
+ const captureProviderSessionState = new Map<string, ProviderSessionState>();
1089
+ const captureMessages = options.sourceAgent.state.messages.map((message): AgentMessage => {
1090
+ if (message.role === "assistant") {
1091
+ return { ...message, responseId: undefined, providerPayload: undefined };
1092
+ }
1093
+ if (message.role === "user" || message.role === "developer") {
1094
+ return { ...message, providerPayload: undefined };
1095
+ }
1096
+ return message;
1097
+ });
1098
+ const captureAgent = options.createAgent({
1099
+ initialState: {
1100
+ systemPrompt: [...options.sourceAgent.state.systemPrompt],
1101
+ model: captureModel,
1102
+ thinkingLevel: options.sourceAgent.state.thinkingLevel,
1103
+ disableReasoning: options.sourceAgent.state.disableReasoning,
1104
+ tools: options.captureTools,
1105
+ messages: captureMessages,
1106
+ },
1107
+ sessionId: captureSessionId,
1108
+ promptCacheKey: captureSessionId,
1109
+ providerSessionState: captureProviderSessionState,
1110
+ getApiKey: requestModel => options.sourceAgent.getApiKey?.(requestModel),
1111
+ onPayload: options.onPayload,
1112
+ onResponse: options.onResponse,
1113
+ });
1114
+ captureAgent.setMetadataResolver(provider => options.sourceAgent.metadataForProvider(provider));
1115
+ const captureMessage: CustomMessage = {
1116
+ role: "custom",
1117
+ customType: "autolearn-nudge",
1118
+ content,
1119
+ display: false,
1120
+ attribution: "agent",
1121
+ timestamp: Date.now(),
1122
+ };
1123
+ const abortCapture = () => captureAgent.abort(signal?.reason);
1124
+ signal?.addEventListener("abort", abortCapture, { once: true });
1125
+ try {
1126
+ if (signal?.aborted) {
1127
+ abortCapture();
1128
+ return;
1129
+ }
1130
+ await captureAgent.prompt(captureMessage);
1131
+ } catch (error) {
1132
+ if (!signal?.aborted) throw error;
1133
+ } finally {
1134
+ signal?.removeEventListener("abort", abortCapture);
1135
+ for (const [providerKey, state] of captureProviderSessionState) {
1136
+ try {
1137
+ state.close();
1138
+ } catch (error) {
1139
+ logger.warn("Failed to close auto-learn capture provider state", {
1140
+ providerKey,
1141
+ error: String(error),
1142
+ });
1143
+ }
1144
+ }
1145
+ captureProviderSessionState.clear();
1146
+ }
1147
+ };
1148
+ }
1058
1149
  /**
1059
1150
  * Create an AgentSession with the specified options.
1060
1151
  *
@@ -2247,31 +2338,50 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2247
2338
  builtInRegistryToolNames.delete("edit");
2248
2339
  }
2249
2340
 
2250
- // Staged actions (tool previews, plan approval) resolve through `write`
2251
- // to the resolution devices (`xd://resolve`, `xd://reject`,
2252
- // `xd://propose`), so `write` must stay in the registry whenever any
2253
- // code path can stage one: a deferrable tool, or plan mode installing a
2254
- // plan-proposal handler (issue #1428). Dropping it on read-only sessions
2255
- // (e.g. plan-mode toolset `read`, `search`, `find`, `web_search`) leaves
2256
- // plan mode unable to exit through the intended path.
2257
- const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
2258
- const hasXdevTools = (toolSession.xdevRegistry?.size ?? 0) > 0;
2259
- const planModeAvailable = settings.get("plan.enabled");
2260
- if ((hasDeferrableTools || hasXdevTools || planModeAvailable) && !toolRegistry.has("write")) {
2261
- const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
2262
- if (writeTool) {
2341
+ let writeRegistration: Promise<boolean> | undefined;
2342
+ const ensureWriteRegistered = (): Promise<boolean> => {
2343
+ if (toolRegistry.has("write")) return Promise.resolve(builtInRegistryToolNames.has("write"));
2344
+ writeRegistration ??= (async () => {
2345
+ const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
2346
+ if (!writeTool || toolRegistry.has("write")) return builtInRegistryToolNames.has("write");
2263
2347
  toolRegistry.set(
2264
2348
  writeTool.name,
2265
2349
  new ExtensionToolWrapper(wrapToolWithMetaNotice(writeTool), extensionRunner) as Tool,
2266
2350
  );
2267
2351
  builtInRegistryToolNames.add(writeTool.name);
2268
- }
2352
+ return true;
2353
+ })().finally(() => {
2354
+ writeRegistration = undefined;
2355
+ });
2356
+ return writeRegistration;
2357
+ };
2358
+
2359
+ // Existing staged/device paths need write registered before active-set assembly.
2360
+ // Deferred MCP also registers it now, but refresh activates it only after a server connects.
2361
+ const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
2362
+ const hasXdevTools = (toolSession.xdevRegistry?.size ?? 0) > 0;
2363
+ const planModeAvailable = settings.get("plan.enabled");
2364
+ if (hasDeferrableTools || hasXdevTools || planModeAvailable || deferMCPDiscoveryForUI) {
2365
+ await ensureWriteRegistered();
2269
2366
  }
2270
2367
 
2271
2368
  let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
2369
+ // Built-in xd:// devices (ast_edit, debug, browser, lsp, web_search) are
2370
+ // mounted in createTools BEFORE this loop wraps registry entries in
2371
+ // ExtensionToolWrapper, so the registry holds them unwrapped. The normal
2372
+ // `write xd://<tool>` path runs approval through the wrapped `write` tool's
2373
+ // tier gate, but Cursor invokes advertised devices via `tool.execute()`
2374
+ // directly — so wrap unwrapped devices here to keep the approval/deny/prompt
2375
+ // gate. Dynamic mounts (custom/MCP) already come from the wrapped registry.
2376
+ const resolveCursorDevice = (name: string): AgentTool | undefined => {
2377
+ const device = toolSession.xdevRegistry?.get(name);
2378
+ if (!device) return undefined;
2379
+ return device instanceof ExtensionToolWrapper ? device : new ExtensionToolWrapper(device, extensionRunner);
2380
+ };
2272
2381
  const cursorExecHandlers = new CursorExecHandlers({
2273
2382
  cwd,
2274
2383
  tools: toolRegistry,
2384
+ getTool: resolveCursorDevice,
2275
2385
  getToolContext: () => toolContextStore.getContext(),
2276
2386
  emitEvent: event => cursorEventEmitter?.(event),
2277
2387
  });
@@ -2413,11 +2523,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2413
2523
  }
2414
2524
  const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
2415
2525
  const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
2416
- const requestedToolNameSet = new Set(normalizedRequested);
2417
2526
  const defaultInactiveToolNames = new Set(
2418
2527
  registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
2419
2528
  );
2420
2529
  const requestedActiveToolNames = normalizedRequested.filter(name => name !== "goal");
2530
+ const explicitlyRequestedToolNameSet = explicitlyRequestedToolNames
2531
+ ? new Set(explicitlyRequestedToolNames)
2532
+ : undefined;
2533
+ const xdevReadAvailable =
2534
+ builtInRegistryToolNames.has("read") &&
2535
+ (explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("read"));
2421
2536
  const initialRequestedActiveToolNames = options.toolNames
2422
2537
  ? requestedActiveToolNames
2423
2538
  : requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
@@ -2449,24 +2564,32 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2449
2564
  });
2450
2565
  hasRegistered = true;
2451
2566
 
2452
- // Partition the initial enabled set for the xd:// transport: discoverable
2453
- // tools become mounted devices; the rest stay top-level. The registry
2454
- // already holds the built-in devices (mounted in createTools); this
2455
- // reconciles the initial dynamic mounts (image-gen, TTS, startup MCP,
2456
- // active extension tools) and drops them from the top-level names so they
2457
- // never ship a schema. Presentation only — selection already happened.
2567
+ // Partition the initial enabled set for the xd:// transport: ambient
2568
+ // discoverable tools become mounted devices, while explicitly requested
2569
+ // tools keep their top-level presentation. The registry already holds the
2570
+ // default-set built-in devices from createTools; this reconciles dynamic
2571
+ // mounts (image-gen, TTS, startup MCP, active extension tools).
2458
2572
  let initialMountedXdevToolNames: string[] = [];
2459
2573
  if (toolSession.xdevRegistry) {
2460
2574
  const topLevelToolNames: string[] = [];
2461
2575
  const mountedTools: Tool[] = [];
2462
2576
  for (const name of initialToolNames) {
2463
2577
  const tool = toolRegistry.get(name);
2464
- if (tool && isMountableUnderXdev(tool)) mountedTools.push(tool);
2578
+ const explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;
2579
+ if (tool && xdevReadAvailable && !explicitlyRequested && isMountableUnderXdev(tool))
2580
+ mountedTools.push(tool);
2465
2581
  else topLevelToolNames.push(name);
2466
2582
  }
2467
- toolSession.xdevRegistry.reconcile(mountedTools);
2468
- initialMountedXdevToolNames = mountedTools.map(tool => tool.name);
2469
- initialToolNames = topLevelToolNames;
2583
+ const writeTransportAvailable = mountedTools.length === 0 || (await ensureWriteRegistered());
2584
+ if (writeTransportAvailable) {
2585
+ toolSession.xdevRegistry.reconcile(mountedTools);
2586
+ initialMountedXdevToolNames = mountedTools.map(tool => tool.name);
2587
+ initialToolNames = topLevelToolNames;
2588
+ if (initialMountedXdevToolNames.length > 0 && !initialToolNames.includes("write"))
2589
+ initialToolNames.push("write");
2590
+ } else {
2591
+ toolSession.xdevRegistry.reconcile([]);
2592
+ }
2470
2593
  }
2471
2594
 
2472
2595
  setActiveToolNames(initialToolNames);
@@ -2551,6 +2674,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2551
2674
  const initialTools = initialToolNames
2552
2675
  .map(name => toolRegistry.get(name))
2553
2676
  .filter((tool): tool is AgentTool => tool !== undefined);
2677
+ const autoLearnCaptureTools = initialTools.filter(tool => tool.name === "manage_skill" || tool.name === "learn");
2554
2678
 
2555
2679
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
2556
2680
  const preferOpenAICodexWebsockets =
@@ -2577,6 +2701,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2577
2701
  settings,
2578
2702
  createSettingsAwareStreamFn(settings),
2579
2703
  );
2704
+ const transformToolCallArguments = (args: Record<string, unknown>): Record<string, unknown> => {
2705
+ let result = args;
2706
+ const maxTimeout = settings.get("tools.maxTimeout");
2707
+ if (maxTimeout > 0 && typeof result.timeout === "number") {
2708
+ result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
2709
+ }
2710
+ if (obfuscator?.hasSecrets()) {
2711
+ result = deobfuscateToolArguments(obfuscator, result);
2712
+ }
2713
+ return result;
2714
+ };
2580
2715
  agent = new Agent({
2581
2716
  initialState: {
2582
2717
  systemPrompt,
@@ -2629,17 +2764,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2629
2764
  return settingsAwareStreamFn(streamModel, context, streamOptions);
2630
2765
  },
2631
2766
  cursorExecHandlers,
2632
- transformToolCallArguments: (args, _toolName) => {
2633
- let result = args;
2634
- const maxTimeout = settings.get("tools.maxTimeout");
2635
- if (maxTimeout > 0 && typeof result.timeout === "number") {
2636
- result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
2637
- }
2638
- if (obfuscator?.hasSecrets()) {
2639
- result = deobfuscateToolArguments(obfuscator, result);
2640
- }
2641
- return result;
2642
- },
2767
+ getCursorTools: () => [...(toolSession.xdevRegistry?.list() ?? [])],
2768
+ transformToolCallArguments,
2643
2769
  intentTracing: !!intentField,
2644
2770
  pruneToolDescriptions: inlineToolDescriptors,
2645
2771
  dialect: resolveDialect(settings.get("tools.format"), model),
@@ -2761,8 +2887,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2761
2887
  getXdevToolEntries: () => toolSession.xdevRegistry?.entries() ?? [],
2762
2888
  xdevRegistry: toolSession.xdevRegistry,
2763
2889
  initialMountedXdevToolNames,
2764
- requestedToolNames: requestedToolNameSet,
2890
+ presentationPinnedToolNames: explicitlyRequestedToolNameSet,
2765
2891
  setActiveToolNames,
2892
+ ensureWriteRegistered,
2766
2893
  getMcpServerInstructions: mcpManager
2767
2894
  ? () => {
2768
2895
  const raw = mcpManager.getServerInstructions();
@@ -2922,8 +3049,56 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2922
3049
  });
2923
3050
  };
2924
3051
 
2925
- // Auto-learn can immediately trigger a synthetic capture turn after the
2926
- // first real stop. When a memory backend is selected, install that backend's
3052
+ const runAutoLearnCapture = createAutoLearnCaptureRunner({
3053
+ sourceAgent: agent,
3054
+ captureTools: autoLearnCaptureTools,
3055
+ onPayload,
3056
+ onResponse,
3057
+ createAgent: captureOptions => {
3058
+ const captureModel = captureOptions.initialState?.model;
3059
+ const captureSessionId = captureOptions.sessionId;
3060
+ if (!captureModel || !captureSessionId) throw new Error("Auto-learn capture identity is incomplete");
3061
+ return new Agent({
3062
+ ...captureOptions,
3063
+ cwd: sessionManager.getCwd(),
3064
+ cwdResolver: () => sessionManager.getCwd(),
3065
+ convertToLlm: convertToLlmFinal,
3066
+ transformContext: async messages => wrapSteeringForModel(messages),
3067
+ transformProviderContext: async (context, transformModel) => {
3068
+ const transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
3069
+ return clampProviderContextImages(transformed, transformModel);
3070
+ },
3071
+ thinkingBudgets: agent.thinkingBudgets,
3072
+ temperature: agent.temperature,
3073
+ topP: agent.topP,
3074
+ topK: agent.topK,
3075
+ minP: agent.minP,
3076
+ presencePenalty: agent.presencePenalty,
3077
+ repetitionPenalty: agent.repetitionPenalty,
3078
+ serviceTierResolver: agent.serviceTierResolver,
3079
+ hideThinkingSummary: agent.hideThinkingSummary,
3080
+ maxRetryDelayMs: agent.maxRetryDelayMs,
3081
+ kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
3082
+ preferWebsockets: preferOpenAICodexWebsockets,
3083
+ getToolContext: toolCall => toolContextStore.getContext(toolCall),
3084
+ streamFn: settingsAwareStreamFn,
3085
+ transformToolCallArguments,
3086
+ intentTracing: !!intentField,
3087
+ pruneToolDescriptions: inlineToolDescriptors,
3088
+ dialect: resolveDialect(settings.get("tools.format"), captureModel),
3089
+ abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
3090
+ appendOnlyContext: shouldEnableAppendOnlyContext(
3091
+ settings.get("provider.appendOnlyContext"),
3092
+ captureModel,
3093
+ )
3094
+ ? new AppendOnlyContextManager()
3095
+ : undefined,
3096
+ });
3097
+ },
3098
+ });
3099
+
3100
+ // Auto-learn can immediately trigger a private capture after the first real
3101
+ // stop. When a memory backend is selected, install that backend's
2927
3102
  // per-session state first so the capture turn's `learn` tool observes the
2928
3103
  // same initialized state as normal memory tools. Other sessions keep memory
2929
3104
  // startup in the background to preserve the existing startup profile.
@@ -2938,7 +3113,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2938
3113
  // reference is intentionally discarded (the listener retains it).
2939
3114
  if (settings.get("autolearn.enabled") && taskDepth === 0) {
2940
3115
  await logger.time("startMemoryStartupTask", startMemoryBackend);
2941
- new AutoLearnController({ session, settings });
3116
+ new AutoLearnController({
3117
+ session,
3118
+ settings,
3119
+ capture: content => session.runAutolearnCapture(signal => runAutoLearnCapture(content, signal)),
3120
+ });
2942
3121
  } else {
2943
3122
  void logger.time("startMemoryStartupTask", startMemoryBackend);
2944
3123
  }