@oh-my-pi/pi-coding-agent 17.0.0 → 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 (202) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/dist/cli.js +4006 -3966
  3. package/dist/types/advisor/advise-tool.d.ts +5 -2
  4. package/dist/types/advisor/config.d.ts +14 -6
  5. package/dist/types/advisor/runtime.d.ts +20 -11
  6. package/dist/types/autolearn/controller.d.ts +1 -0
  7. package/dist/types/cli-commands.d.ts +14 -1
  8. package/dist/types/config/model-discovery.d.ts +17 -0
  9. package/dist/types/config/model-registry.d.ts +4 -0
  10. package/dist/types/config/model-resolver.d.ts +6 -2
  11. package/dist/types/config/models-config-schema.d.ts +10 -0
  12. package/dist/types/config/models-config.d.ts +6 -0
  13. package/dist/types/config/settings-schema.d.ts +32 -7
  14. package/dist/types/config/settings.d.ts +50 -0
  15. package/dist/types/cursor.d.ts +11 -0
  16. package/dist/types/discovery/helpers.d.ts +5 -2
  17. package/dist/types/discovery/substitute-plugin-root.d.ts +20 -7
  18. package/dist/types/exec/bash-executor.d.ts +2 -0
  19. package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
  20. package/dist/types/extensibility/extensions/runner.d.ts +7 -0
  21. package/dist/types/extensibility/extensions/types.d.ts +18 -0
  22. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
  23. package/dist/types/extensibility/shared-events.d.ts +6 -0
  24. package/dist/types/extensibility/utils.d.ts +2 -2
  25. package/dist/types/internal-urls/agent-protocol.d.ts +1 -0
  26. package/dist/types/lib/xai-http.d.ts +11 -0
  27. package/dist/types/mcp/transports/stdio.d.ts +13 -1
  28. package/dist/types/modes/acp/acp-agent.d.ts +3 -0
  29. package/dist/types/modes/acp/acp-mode.d.ts +6 -2
  30. package/dist/types/modes/components/advisor-config.d.ts +8 -1
  31. package/dist/types/modes/components/assistant-message.d.ts +2 -0
  32. package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +1 -0
  33. package/dist/types/modes/components/custom-editor.d.ts +23 -1
  34. package/dist/types/modes/components/hook-editor.d.ts +16 -2
  35. package/dist/types/modes/components/login-dialog.test.d.ts +1 -0
  36. package/dist/types/modes/components/model-hub.d.ts +5 -2
  37. package/dist/types/modes/components/session-selector.d.ts +4 -0
  38. package/dist/types/modes/components/tool-execution.d.ts +8 -1
  39. package/dist/types/modes/components/tool-execution.test.d.ts +1 -0
  40. package/dist/types/modes/controllers/command-controller.d.ts +8 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  42. package/dist/types/modes/interactive-mode.d.ts +6 -0
  43. package/dist/types/modes/noninteractive-dispose.test.d.ts +1 -0
  44. package/dist/types/modes/print-mode.d.ts +1 -1
  45. package/dist/types/modes/types.d.ts +10 -0
  46. package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -2
  47. package/dist/types/modes/warp-events.d.ts +24 -0
  48. package/dist/types/modes/warp-events.test.d.ts +1 -0
  49. package/dist/types/plan-mode/model-transition.d.ts +47 -0
  50. package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
  51. package/dist/types/registry/agent-lifecycle.d.ts +26 -1
  52. package/dist/types/sdk.d.ts +13 -2
  53. package/dist/types/session/agent-session.d.ts +43 -11
  54. package/dist/types/session/session-history-format.d.ts +10 -0
  55. package/dist/types/session/session-manager.d.ts +14 -0
  56. package/dist/types/session/streaming-output.d.ts +2 -0
  57. package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
  58. package/dist/types/slash-commands/types.d.ts +4 -4
  59. package/dist/types/system-prompt.d.ts +1 -1
  60. package/dist/types/task/label.d.ts +1 -1
  61. package/dist/types/telemetry-export.d.ts +34 -9
  62. package/dist/types/tools/approval.d.ts +8 -0
  63. package/dist/types/tools/bash.d.ts +2 -0
  64. package/dist/types/tools/browser/registry.d.ts +7 -3
  65. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  66. package/dist/types/tools/essential-tools.d.ts +29 -0
  67. package/dist/types/tools/image-gen.d.ts +2 -1
  68. package/dist/types/tools/index.d.ts +4 -1
  69. package/dist/types/tools/manage-skill.d.ts +2 -0
  70. package/dist/types/tools/xdev.d.ts +11 -2
  71. package/dist/types/utils/title-generator.d.ts +2 -1
  72. package/dist/types/web/search/index.d.ts +2 -0
  73. package/dist/types/web/search/providers/base.d.ts +3 -0
  74. package/dist/types/web/search/providers/kimi.d.ts +4 -1
  75. package/dist/types/web/search/types.d.ts +1 -1
  76. package/package.json +21 -16
  77. package/src/advisor/__tests__/advisor.test.ts +1358 -86
  78. package/src/advisor/__tests__/config.test.ts +58 -2
  79. package/src/advisor/advise-tool.ts +7 -3
  80. package/src/advisor/config.ts +76 -24
  81. package/src/advisor/runtime.ts +445 -92
  82. package/src/autolearn/controller.ts +23 -28
  83. package/src/cli/bench-cli.ts +4 -1
  84. package/src/cli/grep-cli.ts +2 -1
  85. package/src/cli-commands.ts +57 -23
  86. package/src/cli.ts +5 -1
  87. package/src/config/model-discovery.ts +81 -21
  88. package/src/config/model-registry.ts +34 -6
  89. package/src/config/model-resolver.ts +57 -12
  90. package/src/config/models-config-schema.ts +1 -0
  91. package/src/config/settings-schema.ts +43 -6
  92. package/src/config/settings.ts +405 -25
  93. package/src/cursor.ts +20 -3
  94. package/src/debug/report-bundle.ts +40 -4
  95. package/src/discovery/codex.ts +14 -4
  96. package/src/discovery/helpers.ts +28 -5
  97. package/src/discovery/substitute-plugin-root.ts +23 -7
  98. package/src/exec/bash-executor.ts +14 -5
  99. package/src/export/html/template.js +2 -0
  100. package/src/extensibility/custom-tools/loader.ts +3 -3
  101. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  102. package/src/extensibility/extensions/loader.ts +3 -3
  103. package/src/extensibility/extensions/managed-timers.ts +83 -0
  104. package/src/extensibility/extensions/runner.ts +26 -0
  105. package/src/extensibility/extensions/types.ts +18 -0
  106. package/src/extensibility/extensions/wrapper.ts +2 -1
  107. package/src/extensibility/hooks/loader.ts +3 -3
  108. package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
  109. package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
  110. package/src/extensibility/plugins/manager.ts +2 -2
  111. package/src/extensibility/shared-events.ts +6 -0
  112. package/src/extensibility/utils.ts +91 -25
  113. package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +73 -0
  114. package/src/internal-urls/agent-protocol.ts +73 -39
  115. package/src/irc/bus.ts +22 -3
  116. package/src/launch/broker.ts +3 -2
  117. package/src/launch/client.ts +2 -2
  118. package/src/launch/presence.ts +2 -2
  119. package/src/lib/xai-http.ts +28 -2
  120. package/src/lsp/client.ts +1 -1
  121. package/src/main.ts +11 -8
  122. package/src/mcp/manager.ts +9 -3
  123. package/src/mcp/transports/stdio.ts +103 -23
  124. package/src/modes/acp/acp-agent.ts +28 -9
  125. package/src/modes/acp/acp-event-mapper.ts +8 -0
  126. package/src/modes/acp/acp-mode.ts +18 -4
  127. package/src/modes/components/advisor-config.ts +65 -3
  128. package/src/modes/components/ask-dialog.ts +1 -1
  129. package/src/modes/components/assistant-message.ts +35 -14
  130. package/src/modes/components/chat-transcript-builder.ts +2 -0
  131. package/src/modes/components/custom-editor-plugin-ctor.test.ts +36 -0
  132. package/src/modes/components/custom-editor.ts +62 -1
  133. package/src/modes/components/hook-editor.ts +35 -5
  134. package/src/modes/components/login-dialog.test.ts +56 -0
  135. package/src/modes/components/login-dialog.ts +7 -3
  136. package/src/modes/components/model-hub.ts +138 -42
  137. package/src/modes/components/plan-review-overlay.ts +51 -10
  138. package/src/modes/components/session-selector.ts +10 -0
  139. package/src/modes/components/status-line/component.test.ts +1 -0
  140. package/src/modes/components/status-line/component.ts +2 -4
  141. package/src/modes/components/status-line/segments.ts +21 -6
  142. package/src/modes/components/tool-execution.test.ts +101 -0
  143. package/src/modes/components/tool-execution.ts +117 -9
  144. package/src/modes/components/transcript-container.ts +30 -2
  145. package/src/modes/controllers/command-controller-shared.ts +1 -1
  146. package/src/modes/controllers/command-controller.ts +168 -48
  147. package/src/modes/controllers/event-controller.ts +6 -0
  148. package/src/modes/controllers/extension-ui-controller.ts +4 -22
  149. package/src/modes/controllers/input-controller.ts +12 -12
  150. package/src/modes/controllers/selector-controller.ts +208 -46
  151. package/src/modes/interactive-mode.ts +205 -65
  152. package/src/modes/noninteractive-dispose.test.ts +60 -0
  153. package/src/modes/print-mode.ts +10 -5
  154. package/src/modes/rpc/host-tools.ts +2 -1
  155. package/src/modes/rpc/rpc-mode.ts +31 -7
  156. package/src/modes/theme/theme.ts +2 -2
  157. package/src/modes/types.ts +10 -0
  158. package/src/modes/utils/interactive-context-helpers.ts +3 -1
  159. package/src/modes/utils/transcript-render-helpers.ts +3 -2
  160. package/src/modes/warp-events.test.ts +794 -0
  161. package/src/modes/warp-events.ts +232 -0
  162. package/src/plan-mode/model-transition.test.ts +60 -0
  163. package/src/plan-mode/model-transition.ts +51 -0
  164. package/src/prompts/system/system-prompt.md +1 -1
  165. package/src/registry/agent-lifecycle.ts +133 -18
  166. package/src/sdk.ts +227 -44
  167. package/src/session/agent-session.ts +1364 -382
  168. package/src/session/session-history-format.ts +20 -5
  169. package/src/session/session-listing.ts +6 -5
  170. package/src/session/session-loader.ts +9 -0
  171. package/src/session/session-manager.ts +48 -0
  172. package/src/session/session-persistence.ts +11 -0
  173. package/src/session/streaming-output.ts +3 -1
  174. package/src/slash-commands/builtin-registry.ts +9 -0
  175. package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
  176. package/src/slash-commands/types.ts +4 -4
  177. package/src/system-prompt.ts +2 -2
  178. package/src/task/executor.ts +1 -1
  179. package/src/task/label.ts +2 -0
  180. package/src/telemetry-export.ts +453 -97
  181. package/src/tools/approval.ts +11 -0
  182. package/src/tools/bash.ts +71 -38
  183. package/src/tools/browser/registry.ts +9 -2
  184. package/src/tools/browser/tab-supervisor.ts +65 -10
  185. package/src/tools/browser.ts +4 -3
  186. package/src/tools/essential-tools.ts +45 -0
  187. package/src/tools/eval.ts +3 -0
  188. package/src/tools/gh.ts +169 -2
  189. package/src/tools/image-gen.ts +624 -513
  190. package/src/tools/index.ts +10 -6
  191. package/src/tools/manage-skill.ts +5 -3
  192. package/src/tools/output-meta.ts +7 -0
  193. package/src/tools/path-utils.ts +8 -5
  194. package/src/tools/read.ts +48 -3
  195. package/src/tools/write.ts +22 -4
  196. package/src/tools/xdev.ts +14 -3
  197. package/src/utils/title-generator.ts +15 -4
  198. package/src/web/search/index.ts +21 -6
  199. package/src/web/search/providers/base.ts +3 -0
  200. package/src/web/search/providers/kimi.ts +18 -12
  201. package/src/web/search/providers/xai.ts +40 -9
  202. 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
  *
@@ -1557,7 +1648,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1557
1648
  skipPythonPreflight: options.skipPythonPreflight,
1558
1649
  contextFiles,
1559
1650
  workspaceTree: resolvedWorkspaceTree,
1560
- skills,
1651
+ get skills() {
1652
+ return session?.skills ?? skills;
1653
+ },
1654
+ refreshSkills: () => session.refreshSkills(),
1561
1655
  rules: allRules,
1562
1656
  eventBus,
1563
1657
  outputSchema: options.outputSchema,
@@ -2244,31 +2338,50 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2244
2338
  builtInRegistryToolNames.delete("edit");
2245
2339
  }
2246
2340
 
2247
- // Staged actions (tool previews, plan approval) resolve through `write`
2248
- // to the resolution devices (`xd://resolve`, `xd://reject`,
2249
- // `xd://propose`), so `write` must stay in the registry whenever any
2250
- // code path can stage one: a deferrable tool, or plan mode installing a
2251
- // plan-proposal handler (issue #1428). Dropping it on read-only sessions
2252
- // (e.g. plan-mode toolset `read`, `search`, `find`, `web_search`) leaves
2253
- // plan mode unable to exit through the intended path.
2254
- const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
2255
- const hasXdevTools = (toolSession.xdevRegistry?.size ?? 0) > 0;
2256
- const planModeAvailable = settings.get("plan.enabled");
2257
- if ((hasDeferrableTools || hasXdevTools || planModeAvailable) && !toolRegistry.has("write")) {
2258
- const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
2259
- 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");
2260
2347
  toolRegistry.set(
2261
2348
  writeTool.name,
2262
2349
  new ExtensionToolWrapper(wrapToolWithMetaNotice(writeTool), extensionRunner) as Tool,
2263
2350
  );
2264
2351
  builtInRegistryToolNames.add(writeTool.name);
2265
- }
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();
2266
2366
  }
2267
2367
 
2268
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
+ };
2269
2381
  const cursorExecHandlers = new CursorExecHandlers({
2270
2382
  cwd,
2271
2383
  tools: toolRegistry,
2384
+ getTool: resolveCursorDevice,
2272
2385
  getToolContext: () => toolContextStore.getContext(),
2273
2386
  emitEvent: event => cursorEventEmitter?.(event),
2274
2387
  });
@@ -2341,7 +2454,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2341
2454
  xdevDocs: toolSession.xdevRegistry?.docsAll() ?? "",
2342
2455
  autoQaEnabled: isAutoQaEnabled(settings),
2343
2456
  resolvedCustomPrompt: options.customSystemPrompt,
2344
- skills,
2457
+ skills: session?.skills ?? skills,
2345
2458
  contextFiles,
2346
2459
  tools: promptTools,
2347
2460
  toolNames,
@@ -2410,11 +2523,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2410
2523
  }
2411
2524
  const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
2412
2525
  const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
2413
- const requestedToolNameSet = new Set(normalizedRequested);
2414
2526
  const defaultInactiveToolNames = new Set(
2415
2527
  registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
2416
2528
  );
2417
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"));
2418
2536
  const initialRequestedActiveToolNames = options.toolNames
2419
2537
  ? requestedActiveToolNames
2420
2538
  : requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
@@ -2446,24 +2564,32 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2446
2564
  });
2447
2565
  hasRegistered = true;
2448
2566
 
2449
- // Partition the initial enabled set for the xd:// transport: discoverable
2450
- // tools become mounted devices; the rest stay top-level. The registry
2451
- // already holds the built-in devices (mounted in createTools); this
2452
- // reconciles the initial dynamic mounts (image-gen, TTS, startup MCP,
2453
- // active extension tools) and drops them from the top-level names so they
2454
- // 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).
2455
2572
  let initialMountedXdevToolNames: string[] = [];
2456
2573
  if (toolSession.xdevRegistry) {
2457
2574
  const topLevelToolNames: string[] = [];
2458
2575
  const mountedTools: Tool[] = [];
2459
2576
  for (const name of initialToolNames) {
2460
2577
  const tool = toolRegistry.get(name);
2461
- 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);
2462
2581
  else topLevelToolNames.push(name);
2463
2582
  }
2464
- toolSession.xdevRegistry.reconcile(mountedTools);
2465
- initialMountedXdevToolNames = mountedTools.map(tool => tool.name);
2466
- 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
+ }
2467
2593
  }
2468
2594
 
2469
2595
  setActiveToolNames(initialToolNames);
@@ -2548,6 +2674,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2548
2674
  const initialTools = initialToolNames
2549
2675
  .map(name => toolRegistry.get(name))
2550
2676
  .filter((tool): tool is AgentTool => tool !== undefined);
2677
+ const autoLearnCaptureTools = initialTools.filter(tool => tool.name === "manage_skill" || tool.name === "learn");
2551
2678
 
2552
2679
  const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
2553
2680
  const preferOpenAICodexWebsockets =
@@ -2574,6 +2701,17 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2574
2701
  settings,
2575
2702
  createSettingsAwareStreamFn(settings),
2576
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
+ };
2577
2715
  agent = new Agent({
2578
2716
  initialState: {
2579
2717
  systemPrompt,
@@ -2626,17 +2764,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2626
2764
  return settingsAwareStreamFn(streamModel, context, streamOptions);
2627
2765
  },
2628
2766
  cursorExecHandlers,
2629
- transformToolCallArguments: (args, _toolName) => {
2630
- let result = args;
2631
- const maxTimeout = settings.get("tools.maxTimeout");
2632
- if (maxTimeout > 0 && typeof result.timeout === "number") {
2633
- result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
2634
- }
2635
- if (obfuscator?.hasSecrets()) {
2636
- result = deobfuscateToolArguments(obfuscator, result);
2637
- }
2638
- return result;
2639
- },
2767
+ getCursorTools: () => [...(toolSession.xdevRegistry?.list() ?? [])],
2768
+ transformToolCallArguments,
2640
2769
  intentTracing: !!intentField,
2641
2770
  pruneToolDescriptions: inlineToolDescriptors,
2642
2771
  dialect: resolveDialect(settings.get("tools.format"), model),
@@ -2737,6 +2866,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2737
2866
  customCommands: customCommandsResult.commands,
2738
2867
  skills,
2739
2868
  skillWarnings,
2869
+ skillsReloadable: options.skills === undefined,
2740
2870
  skillsSettings: settings.getGroup("skills"),
2741
2871
  modelRegistry,
2742
2872
  toolRegistry,
@@ -2757,8 +2887,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2757
2887
  getXdevToolEntries: () => toolSession.xdevRegistry?.entries() ?? [],
2758
2888
  xdevRegistry: toolSession.xdevRegistry,
2759
2889
  initialMountedXdevToolNames,
2760
- requestedToolNames: requestedToolNameSet,
2890
+ presentationPinnedToolNames: explicitlyRequestedToolNameSet,
2761
2891
  setActiveToolNames,
2892
+ ensureWriteRegistered,
2762
2893
  getMcpServerInstructions: mcpManager
2763
2894
  ? () => {
2764
2895
  const raw = mcpManager.getServerInstructions();
@@ -2918,8 +3049,56 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2918
3049
  });
2919
3050
  };
2920
3051
 
2921
- // Auto-learn can immediately trigger a synthetic capture turn after the
2922
- // 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
2923
3102
  // per-session state first so the capture turn's `learn` tool observes the
2924
3103
  // same initialized state as normal memory tools. Other sessions keep memory
2925
3104
  // startup in the background to preserve the existing startup profile.
@@ -2934,7 +3113,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2934
3113
  // reference is intentionally discarded (the listener retains it).
2935
3114
  if (settings.get("autolearn.enabled") && taskDepth === 0) {
2936
3115
  await logger.time("startMemoryStartupTask", startMemoryBackend);
2937
- new AutoLearnController({ session, settings });
3116
+ new AutoLearnController({
3117
+ session,
3118
+ settings,
3119
+ capture: content => session.runAutolearnCapture(signal => runAutoLearnCapture(content, signal)),
3120
+ });
2938
3121
  } else {
2939
3122
  void logger.time("startMemoryStartupTask", startMemoryBackend);
2940
3123
  }