@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12

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 (247) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
  3. package/dist/cli.js +10775 -10762
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/capability/mcp.d.ts +11 -0
  6. package/dist/types/capability/skill.d.ts +6 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/custom-models.d.ts +31 -0
  14. package/dist/types/config/model-config-values.d.ts +19 -0
  15. package/dist/types/config/model-patch.d.ts +93 -0
  16. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  17. package/dist/types/config/model-registry.d.ts +9 -52
  18. package/dist/types/config/settings-schema.d.ts +1 -34
  19. package/dist/types/config/settings.d.ts +5 -3
  20. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  21. package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
  22. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  23. package/dist/types/discovery/contained-path.d.ts +33 -0
  24. package/dist/types/discovery/index.d.ts +1 -0
  25. package/dist/types/eval/executor-base.d.ts +8 -2
  26. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  27. package/dist/types/export/share.d.ts +1 -1
  28. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +7 -0
  30. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -2
  32. package/dist/types/extensibility/skills.d.ts +5 -0
  33. package/dist/types/lsp/diagnostics.d.ts +96 -0
  34. package/dist/types/lsp/index.d.ts +7 -128
  35. package/dist/types/lsp/servers.d.ts +72 -0
  36. package/dist/types/lsp/tool.d.ts +42 -0
  37. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  38. package/dist/types/lsp/writethrough.d.ts +33 -0
  39. package/dist/types/mcp/transports/header-policy.d.ts +46 -0
  40. package/dist/types/mcp/types.d.ts +15 -0
  41. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  42. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  43. package/dist/types/modes/components/agent-hub.d.ts +2 -0
  44. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  45. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  49. package/dist/types/modes/theme/color.d.ts +19 -0
  50. package/dist/types/modes/theme/loader.d.ts +19 -0
  51. package/dist/types/modes/theme/schema.d.ts +175 -0
  52. package/dist/types/modes/theme/symbols.d.ts +28 -0
  53. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  54. package/dist/types/modes/theme/theme.d.ts +9 -280
  55. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  56. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  57. package/dist/types/registry/agent-registry.d.ts +1 -3
  58. package/dist/types/secrets/index.d.ts +26 -2
  59. package/dist/types/secrets/message-transform.d.ts +40 -0
  60. package/dist/types/secrets/obfuscator.d.ts +0 -108
  61. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  62. package/dist/types/secrets/placeholder.d.ts +94 -0
  63. package/dist/types/secrets/replacement.d.ts +82 -0
  64. package/dist/types/session/agent-session-events.d.ts +2 -2
  65. package/dist/types/session/agent-session-types.d.ts +6 -0
  66. package/dist/types/session/agent-session.d.ts +2 -2
  67. package/dist/types/session/messages.d.ts +1 -0
  68. package/dist/types/session/prewalk.d.ts +3 -1
  69. package/dist/types/session/session-handoff.d.ts +3 -3
  70. package/dist/types/session/session-manager.d.ts +32 -0
  71. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  72. package/dist/types/session/session-tools.d.ts +8 -0
  73. package/dist/types/session/turn-recovery.d.ts +9 -4
  74. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  75. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  76. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  77. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  78. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  79. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  80. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  81. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  82. package/dist/types/task/index.d.ts +2 -0
  83. package/dist/types/tools/gh-common.d.ts +69 -0
  84. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  85. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  86. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  87. package/dist/types/tools/gh-search.d.ts +61 -0
  88. package/dist/types/tools/gh-types.d.ts +340 -0
  89. package/dist/types/tools/gh-view.d.ts +88 -0
  90. package/dist/types/tools/gh.d.ts +4 -201
  91. package/dist/types/tools/read-archive.d.ts +16 -0
  92. package/dist/types/tools/read-format.d.ts +104 -0
  93. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  94. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  95. package/dist/types/tools/read-renderer.d.ts +24 -0
  96. package/dist/types/tools/read-selector.d.ts +27 -0
  97. package/dist/types/tools/read-sqlite.d.ts +16 -0
  98. package/dist/types/tools/read-summary.d.ts +19 -0
  99. package/dist/types/tools/read.d.ts +1 -23
  100. package/dist/types/tools/shell-tokenize.d.ts +18 -1
  101. package/dist/types/utils/changelog.d.ts +0 -5
  102. package/package.json +13 -13
  103. package/scripts/legacy-pi-virtual-module.ts +4 -1
  104. package/src/advisor/runtime.ts +12 -7
  105. package/src/capability/mcp.ts +11 -0
  106. package/src/capability/skill.ts +6 -0
  107. package/src/cli/command-help.ts +4 -0
  108. package/src/cli/gallery-cli.ts +1 -1
  109. package/src/cli-commands.ts +5 -0
  110. package/src/commands/commit.ts +16 -3
  111. package/src/commands/share.ts +71 -0
  112. package/src/commit/agentic/index.ts +39 -21
  113. package/src/commit/execute.ts +56 -0
  114. package/src/commit/index.ts +2 -1
  115. package/src/commit/pipeline.ts +20 -7
  116. package/src/config/custom-models.ts +188 -0
  117. package/src/config/model-config-values.ts +128 -0
  118. package/src/config/model-discovery.ts +1 -1
  119. package/src/config/model-patch.ts +252 -0
  120. package/src/config/model-provider-discovery.ts +132 -0
  121. package/src/config/model-registry.ts +84 -704
  122. package/src/config/settings-schema.ts +2 -33
  123. package/src/config/settings.ts +53 -3
  124. package/src/debug/raw-sse-buffer.ts +20 -0
  125. package/src/discovery/agent-plugin-format.ts +551 -0
  126. package/src/discovery/agent-plugins.ts +341 -0
  127. package/src/discovery/claude-plugins.ts +21 -5
  128. package/src/discovery/contained-path.ts +75 -0
  129. package/src/discovery/helpers.ts +23 -9
  130. package/src/discovery/index.ts +1 -0
  131. package/src/discovery/omp-plugins.ts +20 -7
  132. package/src/edit/hashline/diff.ts +5 -1
  133. package/src/eval/executor-base.ts +39 -6
  134. package/src/eval/jl/executor.ts +82 -371
  135. package/src/eval/kernel-session-registry.ts +398 -0
  136. package/src/eval/py/executor.ts +53 -261
  137. package/src/eval/rb/executor.ts +36 -279
  138. package/src/exec/non-interactive-env.ts +1 -0
  139. package/src/export/share.ts +2 -1
  140. package/src/extensibility/custom-tools/types.ts +2 -2
  141. package/src/extensibility/extensions/loader.ts +78 -14
  142. package/src/extensibility/extensions/runner.ts +6 -0
  143. package/src/extensibility/extensions/types.ts +12 -0
  144. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  145. package/src/extensibility/shared-events.ts +3 -2
  146. package/src/extensibility/skills.ts +9 -0
  147. package/src/internal-urls/skill-protocol.ts +14 -0
  148. package/src/internal-urls/vault-protocol.ts +2 -2
  149. package/src/launch/client.ts +11 -1
  150. package/src/launch/protocol.ts +7 -2
  151. package/src/lsp/diagnostics.ts +516 -0
  152. package/src/lsp/index.ts +20 -2819
  153. package/src/lsp/servers.ts +296 -0
  154. package/src/lsp/tool.ts +1352 -0
  155. package/src/lsp/workspace-diagnostics.ts +170 -0
  156. package/src/lsp/writethrough.ts +561 -0
  157. package/src/main.ts +3 -2
  158. package/src/mcp/config.ts +3 -0
  159. package/src/mcp/manager.ts +12 -9
  160. package/src/mcp/transports/header-policy.ts +95 -0
  161. package/src/mcp/transports/http.ts +35 -52
  162. package/src/mcp/transports/sse.ts +20 -27
  163. package/src/mcp/types.ts +15 -0
  164. package/src/modes/acp/acp-agent.ts +15 -4
  165. package/src/modes/components/agent-dashboard.ts +2 -0
  166. package/src/modes/components/agent-hub-projection.ts +2 -2
  167. package/src/modes/components/agent-hub-renderer.ts +3 -7
  168. package/src/modes/components/agent-hub.ts +5 -0
  169. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  170. package/src/modes/components/chat-transcript-builder.ts +3 -0
  171. package/src/modes/components/custom-editor.ts +0 -9
  172. package/src/modes/components/status-line/component.ts +1 -0
  173. package/src/modes/components/status-line/segments.ts +6 -4
  174. package/src/modes/components/status-line/types.ts +2 -0
  175. package/src/modes/components/tool-execution.ts +13 -16
  176. package/src/modes/components/tree-selector.ts +62 -3
  177. package/src/modes/controllers/command-controller.ts +8 -2
  178. package/src/modes/controllers/event-controller.ts +15 -15
  179. package/src/modes/controllers/input-controller.ts +20 -2
  180. package/src/modes/controllers/selector-controller.ts +1 -0
  181. package/src/modes/theme/color.ts +133 -0
  182. package/src/modes/theme/loader.ts +178 -0
  183. package/src/modes/theme/schema.ts +263 -0
  184. package/src/modes/theme/symbols.ts +1000 -0
  185. package/src/modes/theme/theme-class.ts +611 -0
  186. package/src/modes/theme/theme.ts +27 -2453
  187. package/src/modes/theme/tui-adapters.ts +279 -0
  188. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  189. package/src/modes/utils/ui-helpers.ts +1 -0
  190. package/src/registry/agent-registry.ts +2 -2
  191. package/src/sdk.ts +6 -49
  192. package/src/secrets/index.ts +51 -6
  193. package/src/secrets/message-transform.ts +287 -0
  194. package/src/secrets/obfuscator.ts +39 -1303
  195. package/src/secrets/placeholder-scan.ts +506 -0
  196. package/src/secrets/placeholder.ts +309 -0
  197. package/src/secrets/replacement.ts +216 -0
  198. package/src/session/agent-session-events.ts +2 -2
  199. package/src/session/agent-session-types.ts +6 -0
  200. package/src/session/agent-session.ts +146 -21
  201. package/src/session/indexed-session-storage.ts +7 -2
  202. package/src/session/messages.ts +1 -0
  203. package/src/session/prewalk.ts +57 -17
  204. package/src/session/session-advisors.ts +32 -34
  205. package/src/session/session-context.ts +3 -5
  206. package/src/session/session-handoff.ts +39 -16
  207. package/src/session/session-manager.ts +67 -3
  208. package/src/session/session-provider-boundary.ts +3 -6
  209. package/src/session/session-tools.ts +37 -3
  210. package/src/session/todo-tracker.ts +11 -1
  211. package/src/session/turn-recovery.ts +115 -84
  212. package/src/slash-commands/builtin-collaboration.ts +504 -0
  213. package/src/slash-commands/builtin-completions.ts +291 -0
  214. package/src/slash-commands/builtin-control.ts +78 -0
  215. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  216. package/src/slash-commands/builtin-marketplace.ts +567 -0
  217. package/src/slash-commands/builtin-modes.ts +518 -0
  218. package/src/slash-commands/builtin-registry.ts +21 -2996
  219. package/src/slash-commands/builtin-session.ts +592 -0
  220. package/src/task/executor.ts +17 -14
  221. package/src/task/index.ts +21 -6
  222. package/src/tools/bash-skill-urls.ts +15 -0
  223. package/src/tools/bash.ts +10 -11
  224. package/src/tools/debug.ts +1 -1
  225. package/src/tools/gh-common.ts +288 -0
  226. package/src/tools/gh-pr-checkout.ts +679 -0
  227. package/src/tools/gh-pr-diff.ts +473 -0
  228. package/src/tools/gh-run-watch.ts +1015 -0
  229. package/src/tools/gh-search.ts +500 -0
  230. package/src/tools/gh-types.ts +379 -0
  231. package/src/tools/gh-view.ts +612 -0
  232. package/src/tools/gh.ts +109 -3821
  233. package/src/tools/jtd-to-json-schema.ts +123 -21
  234. package/src/tools/read-archive.ts +209 -0
  235. package/src/tools/read-format.ts +593 -0
  236. package/src/tools/read-path-resolution.ts +36 -0
  237. package/src/tools/read-pdf-images.ts +250 -0
  238. package/src/tools/read-renderer.ts +289 -0
  239. package/src/tools/read-selector.ts +84 -0
  240. package/src/tools/read-sqlite.ts +215 -0
  241. package/src/tools/read-summary.ts +199 -0
  242. package/src/tools/read.ts +82 -1824
  243. package/src/tools/shell-tokenize.ts +99 -1
  244. package/src/utils/changelog.ts +1 -1
  245. package/src/utils/title-generator.ts +3 -1
  246. package/src/web/search/providers/exa.ts +1 -1
  247. package/src/web/search/providers/zai.ts +12 -3
@@ -0,0 +1,592 @@
1
+ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
2
+ import { settings } from "../config/settings";
3
+ import type { AgentSession } from "../session/agent-session";
4
+ import type { SessionOAuthAccountList } from "../session/agent-session-types";
5
+ import {
6
+ getChangelogPath,
7
+ parseChangelog,
8
+ RECENT_CHANGELOG_ENTRY_LIMIT,
9
+ renderChangelogEntries,
10
+ } from "../utils/changelog";
11
+ import { formatTokenCount, refreshStatusLine } from "./builtin-modes";
12
+ import { buildContextReportText } from "./helpers/context-report";
13
+ import { formatDuration } from "./helpers/format";
14
+ import { handleMcpAcp } from "./helpers/mcp";
15
+ import { commandConsumed, errorMessage, parseSubcommand, usage } from "./helpers/parse";
16
+ import { describeRedeemOutcome, type ResetUsageAccount, toResetUsageAccounts } from "./helpers/reset-usage";
17
+ import { matchSessionPinAccounts, toSessionPinAccounts } from "./helpers/session-pin";
18
+ import { launchStatsDashboard, parseStatsDashboardArgs } from "./helpers/stats-dashboard";
19
+ import { handleTodoAcp } from "./helpers/todo";
20
+ import { buildUsageReportText } from "./helpers/usage-report";
21
+ import type { SlashCommandRuntime, SlashCommandSpec } from "./types";
22
+
23
+ async function handleUsageResetCommand(
24
+ arg: string,
25
+ session: AgentSession,
26
+ output: SlashCommandRuntime["output"],
27
+ ): Promise<void> {
28
+ let accounts: ResetUsageAccount[];
29
+ try {
30
+ accounts = toResetUsageAccounts(await session.listResetCredits());
31
+ } catch (error) {
32
+ await output(`Could not load saved resets: ${errorMessage(error)}`);
33
+ return;
34
+ }
35
+ if (accounts.length === 0) {
36
+ await output("No Codex accounts found. Use /login to add one.");
37
+ return;
38
+ }
39
+ const targetArg = arg.trim();
40
+ if (!targetArg) {
41
+ const lines = ["Saved Codex rate-limit resets:"];
42
+ for (const account of accounts) {
43
+ const detail = account.error ? `unavailable (${account.error})` : `${account.availableCount} available`;
44
+ lines.push(`- ${account.label}: ${detail}${account.active ? " (active)" : ""}`);
45
+ }
46
+ lines.push("", "Spend one with `/usage reset <account email>` or `/usage reset active`.");
47
+ await output(lines.join("\n"));
48
+ return;
49
+ }
50
+ const wanted = targetArg.toLowerCase();
51
+ const target =
52
+ wanted === "active"
53
+ ? accounts.find(account => account.active)
54
+ : accounts.find(
55
+ account =>
56
+ account.label.toLowerCase() === wanted ||
57
+ account.target.email?.toLowerCase() === wanted ||
58
+ account.target.accountId?.toLowerCase() === wanted,
59
+ );
60
+ if (!target) {
61
+ await output(`No Codex account matches "${targetArg}".`);
62
+ return;
63
+ }
64
+ if (target.availableCount <= 0) {
65
+ await output(`${target.label}: no saved resets to spend.`);
66
+ return;
67
+ }
68
+ const outcome = await session.redeemResetCredit(target.target);
69
+ await output(describeRedeemOutcome(outcome, target.label));
70
+ }
71
+
72
+ async function handleSessionPinCommand(
73
+ arg: string,
74
+ session: AgentSession,
75
+ output: SlashCommandRuntime["output"],
76
+ ): Promise<void> {
77
+ if (session.isStreaming) {
78
+ await output("Cannot pin an account while the session is streaming.");
79
+ return;
80
+ }
81
+ let accountList: SessionOAuthAccountList | undefined;
82
+ try {
83
+ accountList = await session.listCurrentProviderOAuthAccounts();
84
+ } catch (error) {
85
+ await output(`Could not load provider accounts: ${errorMessage(error)}`);
86
+ return;
87
+ }
88
+ if (!accountList) {
89
+ await output("Select a model before pinning a provider account.");
90
+ return;
91
+ }
92
+ const provider = getOAuthProviders().find(candidate => candidate.id === accountList.provider);
93
+ const providerName = provider?.name ?? accountList.provider;
94
+ const accounts = toSessionPinAccounts(accountList.accounts);
95
+ if (accounts.length === 0) {
96
+ const source = session.modelRegistry.authStorage.describeCredentialSource(
97
+ accountList.provider,
98
+ session.sessionId,
99
+ );
100
+ await output(
101
+ source
102
+ ? `No stored OAuth accounts for ${providerName}. Current auth comes from ${source}.`
103
+ : `No stored OAuth accounts for ${providerName}. Use /login to add one.`,
104
+ );
105
+ return;
106
+ }
107
+
108
+ const selector = arg.trim();
109
+ if (!selector) {
110
+ const lines = [`OAuth accounts for ${providerName}:`];
111
+ for (const account of accounts) {
112
+ lines.push(`${account.position + 1}. ${account.label}${account.active ? " (active)" : ""}`);
113
+ }
114
+ lines.push("", "Pin one with `/session pin <number|email|account id>`.");
115
+ await output(lines.join("\n"));
116
+ return;
117
+ }
118
+
119
+ const matches = matchSessionPinAccounts(accounts, selector);
120
+ if (matches.length === 0) {
121
+ await output(`No ${providerName} account matches "${selector}".`);
122
+ return;
123
+ }
124
+ if (matches.length > 1) {
125
+ await output(
126
+ `"${selector}" matches multiple ${providerName} accounts: ${matches
127
+ .map(account => `${account.position + 1}. ${account.label}`)
128
+ .join(", ")}. Use the account number.`,
129
+ );
130
+ return;
131
+ }
132
+ const account = matches[0];
133
+ if (!account || !session.pinCurrentProviderOAuthAccount(account.credentialId)) {
134
+ await output(`${account?.label ?? selector} is no longer available to pin.`);
135
+ return;
136
+ }
137
+ await output(`Pinned ${account.label} to this session for ${providerName}.`);
138
+ }
139
+
140
+ export const BUILTIN_SESSION_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec> = [
141
+ {
142
+ name: "todo",
143
+ description: "View or modify the agent's todo list",
144
+ acpDescription: "Manage todos",
145
+ acpInputHint: "<subcommand>",
146
+ subcommands: [
147
+ { name: "edit", description: "Open todos in $EDITOR (Markdown round-trip)" },
148
+ { name: "copy", description: "Copy todos as Markdown to clipboard" },
149
+ { name: "export", description: "Write todos as Markdown to a file (default: TODO.md)", usage: "[<path>]" },
150
+ { name: "import", description: "Replace todos from a Markdown file (default: TODO.md)", usage: "[<path>]" },
151
+ {
152
+ name: "append",
153
+ description: "Append a task; phase fuzzy-matched or auto-created",
154
+ usage: "[<phase>] <task...>",
155
+ },
156
+ { name: "start", description: "Mark task in_progress (fuzzy-matched)", usage: "<task>" },
157
+ { name: "done", description: "Mark task/phase/all completed (fuzzy-matched)", usage: "[<task|phase>]" },
158
+ { name: "drop", description: "Mark task/phase/all abandoned (fuzzy-matched)", usage: "[<task|phase>]" },
159
+ { name: "rm", description: "Remove task/phase/all (fuzzy-matched)", usage: "[<task|phase>]" },
160
+ ],
161
+ allowArgs: true,
162
+ getTuiAutocompleteDescription: runtime => {
163
+ const tasks = runtime.ctx.todoPhases.flatMap(phase => phase.tasks);
164
+ if (tasks.length === 0) return "Todos: none";
165
+ const pending = tasks.filter(task => task.status === "pending").length;
166
+ const inProgress = tasks.filter(task => task.status === "in_progress").length;
167
+ const completed = tasks.filter(task => task.status === "completed").length;
168
+ return `Todos: ${pending + inProgress} open (${inProgress} in progress, ${completed} done)`;
169
+ },
170
+ handle: handleTodoAcp,
171
+ handleTui: async (command, runtime) => {
172
+ await runtime.ctx.handleTodoCommand(command.args);
173
+ runtime.ctx.editor.setText("");
174
+ },
175
+ },
176
+ {
177
+ name: "session",
178
+ description: "Session management commands",
179
+ acpDescription: "Show or configure the current session",
180
+ acpInputHint: "[info|delete|pin [account]]",
181
+ subcommands: [
182
+ { name: "info", description: "Show session info and stats" },
183
+ { name: "delete", description: "Delete current session and return to selector" },
184
+ {
185
+ name: "pin",
186
+ description: "Pin the current provider to a stored OAuth account",
187
+ usage: "[account]",
188
+ },
189
+ ],
190
+ allowArgs: true,
191
+ handle: async (command, runtime) => {
192
+ const { verb, rest } = parseSubcommand(command.args);
193
+ if (!verb || (verb === "info" && !rest)) {
194
+ await runtime.output(
195
+ [
196
+ `Session: ${runtime.session.sessionId}`,
197
+ `Title: ${runtime.session.sessionName}`,
198
+ `CWD: ${runtime.cwd}`,
199
+ ].join("\n"),
200
+ );
201
+ return commandConsumed();
202
+ }
203
+ if (verb === "delete" && !rest) {
204
+ if (runtime.session.isStreaming) return usage("Cannot delete the session while streaming.", runtime);
205
+ const sessionFile = runtime.sessionManager.getSessionFile();
206
+ if (!sessionFile) return usage("No session file to delete (in-memory session).", runtime);
207
+ // Route through the active SessionManager so the persist writer is
208
+ // closed before the file is deleted. Constructing a fresh
209
+ // FileSessionStorage and calling deleteSessionWithArtifacts leaves
210
+ // the active writer attached to the now-deleted path, so the next
211
+ // prompt would silently resurrect or corrupt the "deleted" file.
212
+ try {
213
+ await runtime.sessionManager.dropSession(sessionFile);
214
+ } catch (err) {
215
+ return usage(`Failed to delete session: ${errorMessage(err)}`, runtime);
216
+ }
217
+ await runtime.output(
218
+ `Session deleted: ${sessionFile}. Use ACP \`session/load\` to switch to another session.`,
219
+ );
220
+ return commandConsumed();
221
+ }
222
+ if (verb === "pin") {
223
+ await handleSessionPinCommand(rest, runtime.session, runtime.output);
224
+ return commandConsumed();
225
+ }
226
+ return usage("Usage: /session [info|delete|pin [account]]", runtime);
227
+ },
228
+ handleTui: async (command, runtime) => {
229
+ const { verb, rest } = parseSubcommand(command.args);
230
+ if (verb === "delete" && !rest) {
231
+ runtime.ctx.editor.setText("");
232
+ await runtime.ctx.handleSessionDeleteCommand();
233
+ return;
234
+ }
235
+ if (verb === "pin") {
236
+ if (rest) {
237
+ await handleSessionPinCommand(rest, runtime.ctx.session, text => runtime.ctx.showStatus(text));
238
+ refreshStatusLine(runtime.ctx);
239
+ } else {
240
+ await runtime.ctx.showSessionPinSelector();
241
+ }
242
+ runtime.ctx.editor.setText("");
243
+ return;
244
+ }
245
+ if (!verb || (verb === "info" && !rest)) {
246
+ await runtime.ctx.handleSessionCommand();
247
+ } else {
248
+ runtime.ctx.showStatus("Usage: /session [info|delete|pin [account]]");
249
+ }
250
+ runtime.ctx.editor.setText("");
251
+ },
252
+ },
253
+ {
254
+ name: "jobs",
255
+ description: "Show async background jobs status",
256
+ acpDescription: "Show background jobs",
257
+ getTuiAutocompleteDescription: runtime => {
258
+ const snapshot = runtime.ctx.session.getAsyncJobSnapshot({ recentLimit: 5 });
259
+ if (!snapshot || (snapshot.running.length === 0 && snapshot.recent.length === 0)) return "Jobs: none";
260
+ return `Jobs: ${snapshot.running.length} running, ${snapshot.recent.length} recent`;
261
+ },
262
+ handle: async (_command, runtime) => {
263
+ const snapshot = runtime.session.getAsyncJobSnapshot({ recentLimit: 5 });
264
+ if (!snapshot || (snapshot.running.length === 0 && snapshot.recent.length === 0)) {
265
+ await runtime.output(
266
+ "No background jobs running. (Background jobs run async tools — e.g. long-running bash, debug, or task subagents that would otherwise tie up a turn. They appear here while alive and for ~5 minutes after.)",
267
+ );
268
+ return commandConsumed();
269
+ }
270
+ const now = Date.now();
271
+ const lines: string[] = ["Background Jobs", `Running: ${snapshot.running.length}`];
272
+ if (snapshot.running.length > 0) {
273
+ lines.push("", "Running Jobs");
274
+ for (const job of snapshot.running) {
275
+ lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now - job.startTime)}`);
276
+ lines.push(` ${job.label}`);
277
+ }
278
+ }
279
+ if (snapshot.recent.length > 0) {
280
+ lines.push("", "Recent Jobs");
281
+ for (const job of snapshot.recent) {
282
+ lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now - job.startTime)}`);
283
+ lines.push(` ${job.label}`);
284
+ }
285
+ }
286
+ await runtime.output(lines.join("\n"));
287
+ return commandConsumed();
288
+ },
289
+ handleTui: async (_command, runtime) => {
290
+ await runtime.ctx.handleJobsCommand();
291
+ runtime.ctx.editor.setText("");
292
+ },
293
+ },
294
+ {
295
+ name: "usage",
296
+ description: "Show provider usage and limits",
297
+ acpDescription: "Show token usage",
298
+ acpInputHint: "[show|reset [account|active]]",
299
+ subcommands: [
300
+ { name: "show", description: "Show provider usage and limits" },
301
+ { name: "reset", description: "Spend a saved Codex rate-limit reset", usage: "[account|active]" },
302
+ ],
303
+ allowArgs: true,
304
+ handle: async (command, runtime) => {
305
+ const { verb, rest } = parseSubcommand(command.args);
306
+ if (!verb || (verb === "show" && !rest)) {
307
+ await runtime.output(await buildUsageReportText(runtime));
308
+ return commandConsumed();
309
+ }
310
+ if (verb === "reset") {
311
+ await handleUsageResetCommand(rest, runtime.session, runtime.output);
312
+ return commandConsumed();
313
+ }
314
+ return usage("Usage: /usage [show|reset [account|active]]", runtime);
315
+ },
316
+ handleTui: async (command, runtime) => {
317
+ const { verb, rest } = parseSubcommand(command.args);
318
+ if (!verb || (verb === "show" && !rest)) {
319
+ await runtime.ctx.handleUsageCommand();
320
+ runtime.ctx.editor.setText("");
321
+ return;
322
+ }
323
+ if (verb === "reset") {
324
+ if (rest) {
325
+ await handleUsageResetCommand(rest, runtime.ctx.session, text => runtime.ctx.showStatus(text));
326
+ } else {
327
+ await runtime.ctx.showResetUsageSelector();
328
+ }
329
+ runtime.ctx.editor.setText("");
330
+ return;
331
+ }
332
+ runtime.ctx.showStatus("Usage: /usage [show|reset [account|active]]");
333
+ runtime.ctx.editor.setText("");
334
+ },
335
+ },
336
+ {
337
+ name: "stats",
338
+ description: "Launch the local stats dashboard",
339
+ inlineHint: "[--port <port>]",
340
+ allowArgs: true,
341
+ handle: async (command, runtime) => {
342
+ const parsed = parseStatsDashboardArgs(command.args);
343
+ if ("error" in parsed) return usage(parsed.error, runtime);
344
+
345
+ await runtime.output("Syncing session files...");
346
+ try {
347
+ const result = await launchStatsDashboard(parsed);
348
+ await runtime.output(result.message);
349
+ } catch (error) {
350
+ await runtime.output(`Stats dashboard failed: ${errorMessage(error)}`);
351
+ }
352
+ return commandConsumed();
353
+ },
354
+ },
355
+ {
356
+ name: "changelog",
357
+ description: "Show changelog entries",
358
+ acpDescription: "Show changelog",
359
+ acpInputHint: "[full]",
360
+ subcommands: [{ name: "full", description: "Show complete changelog" }],
361
+ allowArgs: true,
362
+ handle: async (command, runtime) => {
363
+ const changelogPath = getChangelogPath();
364
+ const allEntries = await parseChangelog(changelogPath);
365
+ const showFull = command.args.trim().toLowerCase() === "full";
366
+ const entriesToShow = showFull ? allEntries : allEntries.slice(0, RECENT_CHANGELOG_ENTRY_LIMIT);
367
+ if (entriesToShow.length === 0) {
368
+ await runtime.output("No changelog entries found.");
369
+ return commandConsumed();
370
+ }
371
+ await runtime.output(renderChangelogEntries(entriesToShow).markdown);
372
+ return commandConsumed();
373
+ },
374
+ handleTui: async (command, runtime) => {
375
+ const showFull = command.args.split(/\s+/).filter(Boolean).includes("full");
376
+ await runtime.ctx.handleChangelogCommand(showFull);
377
+ runtime.ctx.editor.setText("");
378
+ },
379
+ },
380
+ {
381
+ name: "hotkeys",
382
+ description: "Show all keyboard shortcuts",
383
+ handleTui: (_command, runtime) => {
384
+ runtime.ctx.handleHotkeysCommand();
385
+ runtime.ctx.editor.setText("");
386
+ },
387
+ },
388
+ {
389
+ name: "tools",
390
+ description: "Show tools currently visible to the agent",
391
+ acpDescription: "Show available tools",
392
+ getTuiAutocompleteDescription: runtime => {
393
+ const active = runtime.ctx.session.getActiveToolNames().length;
394
+ const all = runtime.ctx.session.getAllToolNames().length;
395
+ return all === 0 ? "Tools: none available" : `Tools: ${active} active / ${all} available`;
396
+ },
397
+ handle: async (_command, runtime) => {
398
+ const active = runtime.session.getActiveToolNames();
399
+ const all = runtime.session.getAllToolNames();
400
+ if (all.length === 0) {
401
+ await runtime.output("No tools are available.");
402
+ return commandConsumed();
403
+ }
404
+ const lines = all.map(name => `${active.includes(name) ? "*" : "-"} ${name}`);
405
+ for (const mounted of runtime.session.getXdevToolEntries()) {
406
+ lines.push(`~ xd://${mounted.name}`);
407
+ }
408
+ await runtime.output(lines.join("\n"));
409
+ return commandConsumed();
410
+ },
411
+ handleTui: (_command, runtime) => {
412
+ runtime.ctx.handleToolsCommand();
413
+ runtime.ctx.editor.setText("");
414
+ },
415
+ },
416
+ {
417
+ name: "context",
418
+ description: "Show estimated context usage breakdown",
419
+ acpDescription: "Show context usage",
420
+ getTuiAutocompleteDescription: runtime => {
421
+ const usage = runtime.ctx.session.getContextUsage();
422
+ if (!usage) return "Context: unavailable";
423
+ return `Context: ${Math.round(usage.percent)}% (${formatTokenCount(usage.tokens)}/${formatTokenCount(usage.contextWindow)})`;
424
+ },
425
+ handle: async (_command, runtime) => {
426
+ await runtime.output(buildContextReportText(runtime));
427
+ return commandConsumed();
428
+ },
429
+ handleTui: (_command, runtime) => {
430
+ runtime.ctx.handleContextCommand();
431
+ runtime.ctx.editor.setText("");
432
+ },
433
+ },
434
+ {
435
+ name: "extensions",
436
+ aliases: ["status"],
437
+ description: "Open Extension Control Center dashboard",
438
+ handleTui: (_command, runtime) => {
439
+ runtime.ctx.showExtensionsDashboard();
440
+ runtime.ctx.editor.setText("");
441
+ },
442
+ },
443
+ {
444
+ name: "agents",
445
+ description: "Open Agent Control Center dashboard",
446
+ handleTui: (_command, runtime) => {
447
+ runtime.ctx.showAgentsDashboard();
448
+ runtime.ctx.editor.setText("");
449
+ },
450
+ },
451
+ {
452
+ name: "branch",
453
+ description: "Create a new branch from a previous message",
454
+ handleTui: (_command, runtime) => {
455
+ if (settings.get("doubleEscapeAction") === "tree") {
456
+ runtime.ctx.showTreeSelector();
457
+ } else {
458
+ runtime.ctx.showUserMessageSelector();
459
+ }
460
+ runtime.ctx.editor.setText("");
461
+ },
462
+ },
463
+ {
464
+ name: "fork",
465
+ description: "Create a new fork from a previous message",
466
+ handleTui: async (_command, runtime) => {
467
+ runtime.ctx.editor.setText("");
468
+ await runtime.ctx.handleForkCommand();
469
+ },
470
+ },
471
+ {
472
+ name: "tree",
473
+ description: "Navigate session tree (switch branches)",
474
+ handleTui: (_command, runtime) => {
475
+ runtime.ctx.showTreeSelector();
476
+ runtime.ctx.editor.setText("");
477
+ },
478
+ },
479
+ {
480
+ name: "login",
481
+ description: "Login with OAuth provider",
482
+ inlineHint: "[provider|redirect URL]",
483
+ allowArgs: true,
484
+ getTuiAutocompleteDescription: runtime =>
485
+ runtime.ctx.oauthManualInput.hasPending()
486
+ ? `Login: waiting for ${runtime.ctx.oauthManualInput.pendingProviderId ?? "OAuth"} callback`
487
+ : "Login: choose provider",
488
+ handleTui: (command, runtime) => {
489
+ const manualInput = runtime.ctx.oauthManualInput;
490
+ const args = command.args.trim();
491
+ if (args.length > 0) {
492
+ const matchedProvider = getOAuthProviders().find(provider => provider.id === args);
493
+ if (matchedProvider) {
494
+ if (manualInput.hasPending()) {
495
+ const pendingProvider = manualInput.pendingProviderId;
496
+ const message = pendingProvider
497
+ ? `OAuth login already in progress for ${pendingProvider}. Paste the redirect URL with /login <url>.`
498
+ : "OAuth login already in progress. Paste the redirect URL with /login <url>.";
499
+ runtime.ctx.showWarning(message);
500
+ runtime.ctx.editor.setText("");
501
+ return;
502
+ }
503
+ void runtime.ctx.showOAuthSelector("login", matchedProvider.id);
504
+ runtime.ctx.editor.setText("");
505
+ return;
506
+ }
507
+ const submitted = manualInput.submit(args);
508
+ if (submitted) {
509
+ runtime.ctx.showStatus("OAuth callback received; completing login…");
510
+ } else {
511
+ runtime.ctx.showWarning("No OAuth login is waiting for a manual callback.");
512
+ }
513
+ runtime.ctx.editor.setText("");
514
+ return;
515
+ }
516
+
517
+ if (manualInput.hasPending()) {
518
+ const provider = manualInput.pendingProviderId;
519
+ const message = provider
520
+ ? `OAuth login already in progress for ${provider}. Paste the redirect URL with /login <url>.`
521
+ : "OAuth login already in progress. Paste the redirect URL with /login <url>.";
522
+ runtime.ctx.showWarning(message);
523
+ runtime.ctx.editor.setText("");
524
+ return;
525
+ }
526
+
527
+ void runtime.ctx.showOAuthSelector("login");
528
+ runtime.ctx.editor.setText("");
529
+ },
530
+ },
531
+ {
532
+ name: "logout",
533
+ description: "Logout from OAuth provider",
534
+ inlineHint: "[provider]",
535
+ allowArgs: true,
536
+ handleTui: (command, runtime) => {
537
+ const providerId = command.args.trim();
538
+ if (providerId) {
539
+ const matchedProvider = getOAuthProviders().find(provider => provider.id === providerId);
540
+ if (!matchedProvider) {
541
+ runtime.ctx.showWarning(`Unknown OAuth provider: ${providerId}`);
542
+ runtime.ctx.editor.setText("");
543
+ return;
544
+ }
545
+ void runtime.ctx.showOAuthSelector("logout", matchedProvider.id);
546
+ runtime.ctx.editor.setText("");
547
+ return;
548
+ }
549
+ void runtime.ctx.showOAuthSelector("logout");
550
+ runtime.ctx.editor.setText("");
551
+ },
552
+ },
553
+ {
554
+ name: "mcp",
555
+ description: "Manage MCP servers (add, list, remove, test)",
556
+ acpDescription: "Manage MCP servers",
557
+ inlineHint: "<subcommand>",
558
+ subcommands: [
559
+ {
560
+ name: "add",
561
+ description: "Add a new MCP server",
562
+ usage: "<name> [--scope project|user] [--url <url>] [-- <command...>]",
563
+ },
564
+ { name: "list", description: "List all configured MCP servers" },
565
+ { name: "remove", description: "Remove an MCP server", usage: "<name> [--scope project|user]" },
566
+ { name: "test", description: "Test connection to a server", usage: "<name>" },
567
+ { name: "reauth", description: "Reauthorize OAuth for a server", usage: "<name>" },
568
+ { name: "unauth", description: "Remove OAuth auth from a server", usage: "<name>" },
569
+ { name: "enable", description: "Enable an MCP server", usage: "<name>" },
570
+ { name: "disable", description: "Disable an MCP server", usage: "<name>" },
571
+ {
572
+ name: "smithery-search",
573
+ description: "Search Smithery registry and deploy an MCP server",
574
+ usage: "<keyword> [--scope project|user] [--limit <1-100>] [--semantic]",
575
+ },
576
+ { name: "smithery-login", description: "Login to Smithery and cache API key" },
577
+ { name: "smithery-logout", description: "Remove cached Smithery API key" },
578
+ { name: "reconnect", description: "Reconnect to a specific MCP server", usage: "<name>" },
579
+ { name: "reload", description: "Force reload MCP runtime tools" },
580
+ { name: "resources", description: "List available resources from connected servers" },
581
+ { name: "prompts", description: "List available prompts from connected servers" },
582
+ { name: "notifications", description: "Show notification capabilities and subscriptions" },
583
+ { name: "help", description: "Show help message" },
584
+ ],
585
+ allowArgs: true,
586
+ handle: handleMcpAcp,
587
+ handleTui: async (command, runtime) => {
588
+ runtime.ctx.editor.setText("");
589
+ await runtime.ctx.handleMCPCommand(command.text);
590
+ },
591
+ },
592
+ ];
@@ -876,20 +876,23 @@ export function createSubagentSettings(
876
876
  snapshot["tier.openai"] = subagentTiers.openai ?? "none";
877
877
  snapshot["tier.anthropic"] = subagentTiers.anthropic ?? "none";
878
878
  snapshot["tier.google"] = subagentTiers.google ?? "none";
879
- return Settings.isolated({
880
- ...snapshot,
881
- // Async jobs and bash auto-backgrounding are inherited from the parent:
882
- // background jobs are owner-routed to the subagent's own session, and
883
- // the run driver's quiescence barrier + teardown reap guarantee no
884
- // owner job outlives the run, so worktree capture/cleanup stays
885
- // race-free (previously both were force-disabled here).
886
-
887
- // Subagents run headless — there is no UI to confirm prompts against, so
888
- // the parent task approval is the authorization boundary. Use yolo mode
889
- // to preserve unattended subagent execution. User `tools.approval` policies still apply.
890
- "tools.approvalMode": "yolo",
891
- ...overrides,
892
- });
879
+ return Settings.isolated(
880
+ {
881
+ ...snapshot,
882
+ // Async jobs and bash auto-backgrounding are inherited from the parent:
883
+ // background jobs are owner-routed to the subagent's own session, and
884
+ // the run driver's quiescence barrier + teardown reap guarantee no
885
+ // owner job outlives the run, so worktree capture/cleanup stays
886
+ // race-free (previously both were force-disabled here).
887
+
888
+ // Subagents run headless there is no UI to confirm prompts against, so
889
+ // the parent task approval is the authorization boundary. Use yolo mode
890
+ // to preserve unattended subagent execution. User `tools.approval` policies still apply.
891
+ "tools.approvalMode": "yolo",
892
+ ...overrides,
893
+ },
894
+ { storage: baseSettings.getStorage() },
895
+ );
893
896
  }
894
897
 
895
898
  export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
package/src/task/index.ts CHANGED
@@ -441,16 +441,19 @@ export function composeSpawnAdvisory(args: {
441
441
  class TaskJobError extends Error {}
442
442
 
443
443
  /**
444
- * Process-level memo for create-time agent discovery, keyed by resolved cwd.
444
+ * Process-level create-time discovery memo and published reload snapshots,
445
+ * keyed by resolved cwd.
445
446
  *
446
447
  * `TaskTool.create` runs for every (sub)agent session in this process and the
447
448
  * walk-up + plugin-registry scan in `discoverAgents` is identical for a given
448
- * cwd, so repeat creations reuse the first scan. Execution-time discovery
449
- * (`#runSpawn`) intentionally stays fresh. The memo also tracks the live
450
- * `discoverAgents` binding: test spies swap that binding, which invalidates
451
- * the memo automatically.
449
+ * cwd, so repeat creations reuse the first scan. Explicit plugin reloads
450
+ * replace the matching snapshot so already-created tools advertise the latest
451
+ * definitions. Execution-time discovery (`#runSpawn`) intentionally stays
452
+ * fresh. The memo also tracks the live `discoverAgents` binding: test spies
453
+ * swap that binding, which invalidates both caches automatically.
452
454
  */
453
455
  const discoveryMemo = new Map<string, Promise<DiscoveryResult>>();
456
+ const discoverySnapshots = new Map<string, AgentDefinition[]>();
454
457
  let discoveryMemoFn: typeof discoverAgents | undefined;
455
458
 
456
459
  function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
@@ -458,6 +461,7 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
458
461
  if (discoveryMemoFn !== fn) {
459
462
  discoveryMemoFn = fn;
460
463
  discoveryMemo.clear();
464
+ discoverySnapshots.clear();
461
465
  }
462
466
  const key = path.resolve(cwd);
463
467
  let pending = discoveryMemo.get(key);
@@ -471,6 +475,17 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
471
475
  return pending;
472
476
  }
473
477
 
478
+ /** Rescan one cwd and publish its definitions to existing and future task tools. */
479
+ export async function refreshAgentDiscovery(cwd: string): Promise<void> {
480
+ const key = path.resolve(cwd);
481
+ discoveryMemo.delete(key);
482
+ const pending = discoverAgentsForCreate(cwd);
483
+ const { agents } = await pending;
484
+ if (discoveryMemo.get(key) === pending) {
485
+ discoverySnapshots.set(key, agents);
486
+ }
487
+ }
488
+
474
489
  // ═══════════════════════════════════════════════════════════════════════════
475
490
  // Tool Class
476
491
  // ═══════════════════════════════════════════════════════════════════════════
@@ -587,7 +602,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
587
602
  const planMode = this.session.getPlanModeState?.()?.enabled === true;
588
603
  const isolationMode = this.session.settings.get("task.isolation.mode");
589
604
  return renderDescription({
590
- agents: this.#discoveredAgents,
605
+ agents: discoverySnapshots.get(path.resolve(this.session.cwd)) ?? this.#discoveredAgents,
591
606
  isolationEnabled: !planMode && isolationMode !== "none",
592
607
  applyIsolatedChanges: this.session.settings.get("task.isolation.apply"),
593
608
  disabledAgents,