@oh-my-pi/pi-coding-agent 17.2.11 → 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 (152) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/{CHANGELOG-d9xenpn9.md → CHANGELOG-k9ghy5sn.md} +13 -0
  3. package/dist/cli.js +10795 -10791
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/config/custom-models.d.ts +31 -0
  6. package/dist/types/config/model-config-values.d.ts +19 -0
  7. package/dist/types/config/model-patch.d.ts +93 -0
  8. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  9. package/dist/types/config/model-registry.d.ts +5 -52
  10. package/dist/types/config/settings.d.ts +5 -3
  11. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  12. package/dist/types/discovery/agent-plugin-format.d.ts +6 -13
  13. package/dist/types/discovery/contained-path.d.ts +2 -3
  14. package/dist/types/eval/executor-base.d.ts +8 -2
  15. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  16. package/dist/types/export/share.d.ts +1 -1
  17. package/dist/types/lsp/diagnostics.d.ts +96 -0
  18. package/dist/types/lsp/index.d.ts +7 -128
  19. package/dist/types/lsp/servers.d.ts +72 -0
  20. package/dist/types/lsp/tool.d.ts +42 -0
  21. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  22. package/dist/types/lsp/writethrough.d.ts +33 -0
  23. package/dist/types/mcp/transports/header-policy.d.ts +2 -1
  24. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  25. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  26. package/dist/types/modes/theme/color.d.ts +19 -0
  27. package/dist/types/modes/theme/loader.d.ts +19 -0
  28. package/dist/types/modes/theme/schema.d.ts +175 -0
  29. package/dist/types/modes/theme/symbols.d.ts +28 -0
  30. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  31. package/dist/types/modes/theme/theme.d.ts +9 -280
  32. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  33. package/dist/types/registry/agent-registry.d.ts +1 -3
  34. package/dist/types/secrets/index.d.ts +3 -1
  35. package/dist/types/secrets/message-transform.d.ts +40 -0
  36. package/dist/types/secrets/obfuscator.d.ts +0 -108
  37. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  38. package/dist/types/secrets/placeholder.d.ts +94 -0
  39. package/dist/types/secrets/replacement.d.ts +82 -0
  40. package/dist/types/session/agent-session-types.d.ts +6 -0
  41. package/dist/types/session/agent-session.d.ts +1 -1
  42. package/dist/types/session/session-handoff.d.ts +3 -3
  43. package/dist/types/session/session-manager.d.ts +32 -0
  44. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  45. package/dist/types/session/turn-recovery.d.ts +2 -2
  46. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  47. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  48. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  49. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  50. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  51. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  52. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  53. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  54. package/dist/types/tools/gh-common.d.ts +69 -0
  55. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  56. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  57. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  58. package/dist/types/tools/gh-search.d.ts +61 -0
  59. package/dist/types/tools/gh-types.d.ts +340 -0
  60. package/dist/types/tools/gh-view.d.ts +88 -0
  61. package/dist/types/tools/gh.d.ts +4 -201
  62. package/dist/types/tools/read-archive.d.ts +16 -0
  63. package/dist/types/tools/read-format.d.ts +104 -0
  64. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  65. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  66. package/dist/types/tools/read-renderer.d.ts +24 -0
  67. package/dist/types/tools/read-selector.d.ts +27 -0
  68. package/dist/types/tools/read-sqlite.d.ts +16 -0
  69. package/dist/types/tools/read-summary.d.ts +19 -0
  70. package/dist/types/tools/read.d.ts +1 -23
  71. package/dist/types/tools/shell-tokenize.d.ts +2 -1
  72. package/dist/types/utils/changelog.d.ts +0 -5
  73. package/package.json +13 -13
  74. package/src/advisor/runtime.ts +12 -7
  75. package/src/config/custom-models.ts +188 -0
  76. package/src/config/model-config-values.ts +128 -0
  77. package/src/config/model-patch.ts +252 -0
  78. package/src/config/model-provider-discovery.ts +132 -0
  79. package/src/config/model-registry.ts +64 -704
  80. package/src/config/settings.ts +7 -3
  81. package/src/debug/raw-sse-buffer.ts +20 -0
  82. package/src/discovery/agent-plugin-format.ts +7 -7
  83. package/src/discovery/contained-path.ts +2 -5
  84. package/src/edit/hashline/diff.ts +5 -1
  85. package/src/eval/executor-base.ts +39 -6
  86. package/src/eval/jl/executor.ts +82 -371
  87. package/src/eval/kernel-session-registry.ts +398 -0
  88. package/src/eval/py/executor.ts +53 -261
  89. package/src/eval/rb/executor.ts +36 -279
  90. package/src/export/share.ts +2 -1
  91. package/src/lsp/diagnostics.ts +516 -0
  92. package/src/lsp/index.ts +20 -2819
  93. package/src/lsp/servers.ts +296 -0
  94. package/src/lsp/tool.ts +1352 -0
  95. package/src/lsp/workspace-diagnostics.ts +170 -0
  96. package/src/lsp/writethrough.ts +561 -0
  97. package/src/mcp/transports/header-policy.ts +1 -1
  98. package/src/modes/components/agent-hub-projection.ts +2 -2
  99. package/src/modes/components/agent-hub-renderer.ts +3 -7
  100. package/src/modes/controllers/command-controller.ts +4 -1
  101. package/src/modes/theme/color.ts +133 -0
  102. package/src/modes/theme/loader.ts +178 -0
  103. package/src/modes/theme/schema.ts +263 -0
  104. package/src/modes/theme/symbols.ts +1000 -0
  105. package/src/modes/theme/theme-class.ts +611 -0
  106. package/src/modes/theme/theme.ts +27 -2453
  107. package/src/modes/theme/tui-adapters.ts +279 -0
  108. package/src/registry/agent-registry.ts +2 -2
  109. package/src/secrets/index.ts +6 -12
  110. package/src/secrets/message-transform.ts +287 -0
  111. package/src/secrets/obfuscator.ts +39 -1303
  112. package/src/secrets/placeholder-scan.ts +506 -0
  113. package/src/secrets/placeholder.ts +309 -0
  114. package/src/secrets/replacement.ts +216 -0
  115. package/src/session/agent-session-types.ts +6 -0
  116. package/src/session/agent-session.ts +113 -7
  117. package/src/session/indexed-session-storage.ts +7 -2
  118. package/src/session/session-advisors.ts +32 -34
  119. package/src/session/session-handoff.ts +39 -20
  120. package/src/session/session-manager.ts +67 -3
  121. package/src/session/session-provider-boundary.ts +3 -6
  122. package/src/session/turn-recovery.ts +21 -12
  123. package/src/slash-commands/builtin-collaboration.ts +504 -0
  124. package/src/slash-commands/builtin-completions.ts +291 -0
  125. package/src/slash-commands/builtin-control.ts +78 -0
  126. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  127. package/src/slash-commands/builtin-marketplace.ts +567 -0
  128. package/src/slash-commands/builtin-modes.ts +518 -0
  129. package/src/slash-commands/builtin-registry.ts +21 -3000
  130. package/src/slash-commands/builtin-session.ts +592 -0
  131. package/src/task/executor.ts +17 -14
  132. package/src/tools/gh-common.ts +288 -0
  133. package/src/tools/gh-pr-checkout.ts +679 -0
  134. package/src/tools/gh-pr-diff.ts +473 -0
  135. package/src/tools/gh-run-watch.ts +1015 -0
  136. package/src/tools/gh-search.ts +500 -0
  137. package/src/tools/gh-types.ts +379 -0
  138. package/src/tools/gh-view.ts +612 -0
  139. package/src/tools/gh.ts +109 -3821
  140. package/src/tools/read-archive.ts +209 -0
  141. package/src/tools/read-format.ts +593 -0
  142. package/src/tools/read-path-resolution.ts +36 -0
  143. package/src/tools/read-pdf-images.ts +250 -0
  144. package/src/tools/read-renderer.ts +289 -0
  145. package/src/tools/read-selector.ts +84 -0
  146. package/src/tools/read-sqlite.ts +215 -0
  147. package/src/tools/read-summary.ts +199 -0
  148. package/src/tools/read.ts +77 -1820
  149. package/src/tools/shell-tokenize.ts +1 -1
  150. package/src/utils/changelog.ts +1 -1
  151. package/src/utils/title-generator.ts +3 -1
  152. package/src/web/search/providers/zai.ts +12 -3
@@ -0,0 +1,506 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { setProjectDir } from "@oh-my-pi/pi-utils";
4
+ import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
5
+ import { memoryStatsUnavailableMessage, resolveMemoryBackend } from "../memory-backend";
6
+ import type { FreshSessionResult } from "../session/agent-session";
7
+ import { COMPACT_MODES, parseCompactArgs } from "../session/compact-modes";
8
+ import { resolveResumableSession } from "../session/session-listing";
9
+ import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
10
+ import { resolveToCwd } from "../tools/path-utils";
11
+ import { commandConsumed, errorMessage, usage } from "./helpers/parse";
12
+ import { handleSshAcp } from "./helpers/ssh";
13
+ import type {
14
+ ParsedSlashCommand,
15
+ SlashCommandResult,
16
+ SlashCommandRuntime,
17
+ SlashCommandSpec,
18
+ TuiSlashCommandRuntime,
19
+ } from "./types";
20
+
21
+ function formatFreshSessionResult(result: FreshSessionResult): string {
22
+ const stateLabel = result.closedProviderSessions === 1 ? "provider state" : "provider states";
23
+ return `Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`;
24
+ }
25
+
26
+ export const shutdownHandlerTui = (
27
+ _command: ParsedSlashCommand,
28
+ runtime: TuiSlashCommandRuntime,
29
+ ): SlashCommandResult => {
30
+ runtime.ctx.editor.setText("");
31
+ void runtime.ctx.shutdown();
32
+ return commandConsumed();
33
+ };
34
+
35
+ /** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
36
+ function parseShakeMode(args: string): ShakeMode | { error: string } {
37
+ const verb = args.trim().toLowerCase();
38
+ if (verb === "" || verb === "elide") return "elide";
39
+ if (verb === "images") return "images";
40
+ return { error: `Unknown /shake mode "${verb}". Use elide or images.` };
41
+ }
42
+
43
+ /** Format the session's workspace directories (cwd + additional) for display. */
44
+ function formatWorkspaceDirectories(runtime: SlashCommandRuntime, note?: string): string {
45
+ const cwd = runtime.sessionManager.getCwd();
46
+ const additional = runtime.sessionManager.getAdditionalDirectories();
47
+ const lines = ["Workspace directories:", ` ${cwd} (working directory)`, ...additional.map(d => ` ${d}`)];
48
+ return note ? `${note}\n${lines.join("\n")}` : lines.join("\n");
49
+ }
50
+
51
+ export const BUILTIN_LIFECYCLE_SLASH_COMMANDS: ReadonlyArray<SlashCommandSpec> = [
52
+ {
53
+ name: "ssh",
54
+ description: "Manage SSH hosts (add, list, remove)",
55
+ acpDescription: "Manage SSH connections",
56
+ inlineHint: "<subcommand>",
57
+ subcommands: [
58
+ {
59
+ name: "add",
60
+ description: "Add an SSH host",
61
+ usage: "<name> --host <host> [--user <user>] [--port <port>] [--key <keyPath>]",
62
+ },
63
+ { name: "list", description: "List all configured SSH hosts" },
64
+ { name: "remove", description: "Remove an SSH host", usage: "<name> [--scope project|user]" },
65
+ { name: "help", description: "Show help message" },
66
+ ],
67
+ allowArgs: true,
68
+ handle: handleSshAcp,
69
+ handleTui: async (command, runtime) => {
70
+ runtime.ctx.editor.setText("");
71
+ await runtime.ctx.handleSSHCommand(command.text);
72
+ },
73
+ },
74
+ {
75
+ name: "new",
76
+ description: "Start a new session",
77
+ handleTui: async (_command, runtime) => {
78
+ runtime.ctx.editor.setText("");
79
+ await runtime.ctx.handleClearCommand();
80
+ },
81
+ },
82
+ {
83
+ name: "fresh",
84
+ description: "Reset provider stream state without changing the local transcript",
85
+ getTuiAutocompleteDescription: runtime =>
86
+ runtime.ctx.session.isStreaming ? "Fresh: unavailable while streaming" : "Fresh: ready",
87
+ handle: async (_command, runtime) => {
88
+ const result = runtime.session.freshSession();
89
+ if (!result) {
90
+ await runtime.output(
91
+ "Wait for the current response to finish or abort it before refreshing provider state.",
92
+ );
93
+ return commandConsumed();
94
+ }
95
+ await runtime.output(formatFreshSessionResult(result));
96
+ return commandConsumed();
97
+ },
98
+ handleTui: async (_command, runtime) => {
99
+ runtime.ctx.editor.setText("");
100
+ await runtime.ctx.handleFreshCommand();
101
+ },
102
+ },
103
+ {
104
+ name: "clear",
105
+ description: "Clear the conversation context in place, keeping the session",
106
+ getTuiAutocompleteDescription: runtime =>
107
+ runtime.ctx.session.isStreaming ? "Clear: unavailable while streaming" : "Clear: drop context, keep session",
108
+ handleTui: async (_command, runtime) => {
109
+ runtime.ctx.editor.setText("");
110
+ await runtime.ctx.handleResetContextCommand();
111
+ },
112
+ },
113
+ {
114
+ name: "drop",
115
+ description: "Delete the current session and start a new one",
116
+ handleTui: async (_command, runtime) => {
117
+ runtime.ctx.editor.setText("");
118
+ await runtime.ctx.handleDropCommand();
119
+ },
120
+ },
121
+ {
122
+ name: "compact",
123
+ description: "Manually compact the session context",
124
+ acpDescription: "Compact the conversation",
125
+ subcommands: COMPACT_MODES.map(mode => ({
126
+ name: mode.name,
127
+ description: mode.description,
128
+ usage: mode.rejectsFocus ? undefined : "[focus]",
129
+ })),
130
+ acpInputHint: `[${COMPACT_MODES.map(mode => mode.name).join("|")}] [focus]`,
131
+ allowArgs: true,
132
+ getTuiAutocompleteDescription: runtime => {
133
+ const usage = runtime.ctx.session.getContextUsage();
134
+ return usage ? `Compact: context ${Math.round(usage.percent)}% used` : "Compact: context unavailable";
135
+ },
136
+ handle: async (command, runtime) => {
137
+ const parsed = parseCompactArgs(command.args);
138
+ if ("error" in parsed) return usage(parsed.error, runtime);
139
+ const before = runtime.session.getContextUsage?.();
140
+ const beforeTokens = before?.tokens;
141
+ try {
142
+ await runtime.session.compact(parsed.instructions, parsed.mode ? { mode: parsed.mode } : undefined);
143
+ } catch (err) {
144
+ // Compaction precondition failures (no model, already compacted, too
145
+ // small) and provider errors propagate as plain Errors; surface them
146
+ // via runtime.output so they don't fail the ACP prompt turn.
147
+ return usage(`Compaction failed: ${errorMessage(err)}`, runtime);
148
+ }
149
+ const after = runtime.session.getContextUsage?.();
150
+ const afterTokens = after?.tokens;
151
+ if (beforeTokens != null && afterTokens != null) {
152
+ const saved = beforeTokens - afterTokens;
153
+ await runtime.output(`Compaction complete. Tokens: ${beforeTokens} -> ${afterTokens} (saved ${saved}).`);
154
+ } else {
155
+ await runtime.output("Compaction complete.");
156
+ }
157
+ return commandConsumed();
158
+ },
159
+ handleTui: async (command, runtime) => {
160
+ const parsed = parseCompactArgs(command.args);
161
+ runtime.ctx.editor.setText("");
162
+ if ("error" in parsed) {
163
+ runtime.ctx.showWarning(parsed.error);
164
+ return;
165
+ }
166
+ await runtime.ctx.handleCompactCommand(parsed.instructions, parsed.mode);
167
+ },
168
+ },
169
+ {
170
+ name: "shake",
171
+ description: "Drop heavy content from context (tool results, large blocks)",
172
+ acpDescription: "Shake heavy content out of the conversation context",
173
+ subcommands: [
174
+ { name: "elide", description: "Strip tool results + large blocks (default)" },
175
+ { name: "images", description: "Strip image blocks" },
176
+ ],
177
+ acpInputHint: "[elide|images]",
178
+ allowArgs: true,
179
+ handle: async (command, runtime) => {
180
+ const mode = parseShakeMode(command.args);
181
+ if (typeof mode !== "string") return usage(mode.error, runtime);
182
+ const result = await runtime.session.shake(mode);
183
+ await runtime.output(formatShakeSummary(result));
184
+ return commandConsumed();
185
+ },
186
+ handleTui: async (command, runtime) => {
187
+ runtime.ctx.editor.setText("");
188
+ const mode = parseShakeMode(command.args);
189
+ if (typeof mode !== "string") {
190
+ runtime.ctx.showWarning(mode.error);
191
+ return;
192
+ }
193
+ await runtime.ctx.handleShakeCommand(mode);
194
+ },
195
+ },
196
+ {
197
+ name: "handoff",
198
+ description: "Hand off session context to a new session",
199
+ inlineHint: "[focus instructions]",
200
+ allowArgs: true,
201
+ handleTui: async (command, runtime) => {
202
+ const customInstructions = command.args || undefined;
203
+ runtime.ctx.editor.setText("");
204
+ await runtime.ctx.handleHandoffCommand(customInstructions);
205
+ },
206
+ },
207
+ {
208
+ name: "resume",
209
+ description: "Resume a different session",
210
+ inlineHint: "[session id|@claude|@codex]",
211
+ allowArgs: true,
212
+ handleTui: async (command, runtime) => {
213
+ const sessionArg = command.args.trim();
214
+ runtime.ctx.editor.setText("");
215
+ const foreignSource = sessionArg === "@claude" ? "claude" : sessionArg === "@codex" ? "codex" : undefined;
216
+ if (foreignSource) {
217
+ runtime.ctx.showSessionSelector(foreignSource);
218
+ return;
219
+ }
220
+ if (!sessionArg) {
221
+ runtime.ctx.showSessionSelector();
222
+ return;
223
+ }
224
+ const match = await resolveResumableSession(
225
+ sessionArg,
226
+ runtime.ctx.sessionManager.getCwd(),
227
+ runtime.ctx.sessionManager.getSessionDir(),
228
+ { allowGlobalFallback: true },
229
+ );
230
+ if (!match) {
231
+ runtime.ctx.showError(`Session "${sessionArg}" not found`);
232
+ return;
233
+ }
234
+ await runtime.ctx.handleResumeSession(match.session.path);
235
+ },
236
+ },
237
+ {
238
+ name: "btw",
239
+ description: "Ask an ephemeral side question using the current session context",
240
+ inlineHint: "<question>",
241
+ allowArgs: true,
242
+ handleTui: async (command, runtime) => {
243
+ const question = command.text.slice(`/${command.name}`.length).trim();
244
+ runtime.ctx.editor.setText("");
245
+ await runtime.ctx.handleBtwCommand(question);
246
+ },
247
+ },
248
+ {
249
+ name: "tan",
250
+ description: "Run a full background agent on tangential work",
251
+ inlineHint: "<work>",
252
+ allowArgs: true,
253
+ handleTui: async (command, runtime) => {
254
+ const work = command.text.slice(`/${command.name}`.length).trim();
255
+ runtime.ctx.editor.setText("");
256
+ await runtime.ctx.handleTanCommand(work);
257
+ },
258
+ },
259
+ {
260
+ name: "omfg",
261
+ description: "Forge a TTSR rule from a complaint to stop a recurring behavior",
262
+ inlineHint: "<complaint>",
263
+ allowArgs: true,
264
+ handleTui: async (command, runtime) => {
265
+ const complaint = command.text.slice(`/${command.name}`.length).trim();
266
+ runtime.ctx.editor.setText("");
267
+ await runtime.ctx.handleOmfgCommand(complaint);
268
+ },
269
+ },
270
+ {
271
+ name: "retry",
272
+ description: "Retry the last failed agent turn",
273
+ handleTui: async (_command, runtime) => {
274
+ const didRetry = await runtime.ctx.session.retry();
275
+ if (!didRetry) {
276
+ runtime.ctx.showStatus("Nothing to retry");
277
+ }
278
+ runtime.ctx.editor.setText("");
279
+ },
280
+ },
281
+ {
282
+ name: "debug",
283
+ description: "Open debug tools selector",
284
+ handleTui: async (_command, runtime) => {
285
+ await runtime.ctx.showDebugSelector();
286
+ runtime.ctx.editor.setText("");
287
+ },
288
+ },
289
+ {
290
+ name: "memory",
291
+ description: "Inspect and operate memory maintenance",
292
+ acpDescription: "Manage memory",
293
+ acpInputHint: "<subcommand>",
294
+ subcommands: [
295
+ { name: "view", description: "Show current memory injection payload" },
296
+ { name: "stats", description: "Show memory backend statistics" },
297
+ { name: "diagnose", description: "Run memory backend diagnostics" },
298
+ { name: "clear", description: "Clear persisted memory data and artifacts" },
299
+ { name: "reset", description: "Alias for clear" },
300
+ { name: "enqueue", description: "Enqueue memory consolidation maintenance" },
301
+ { name: "rebuild", description: "Alias for enqueue" },
302
+ { name: "mm list", description: "List mental models on the active bank" },
303
+ { name: "mm show", description: "Show one mental model (id required)" },
304
+ {
305
+ name: "mm refresh",
306
+ description: "Refresh auto-refresh models bank-wide, or one model by id",
307
+ },
308
+ { name: "mm history", description: "Diff the change history of a mental model" },
309
+ { name: "mm seed", description: "Create any built-in mental models that are missing" },
310
+ { name: "mm delete", description: "Delete a mental model from the bank (id required)" },
311
+ { name: "mm reload", description: "Re-pull the cached <mental_models> block" },
312
+ ],
313
+ allowArgs: true,
314
+ handle: async (command, runtime) => {
315
+ const verb = (command.args.trim().split(/\s+/)[0] ?? "").toLowerCase() || "view";
316
+ const backend = await resolveMemoryBackend(runtime.settings);
317
+ switch (verb) {
318
+ case "view": {
319
+ const payload = await backend.buildDeveloperInstructions(
320
+ runtime.settings.getAgentDir(),
321
+ runtime.settings,
322
+ runtime.session,
323
+ );
324
+ await runtime.output(payload || "Memory payload is empty.");
325
+ return commandConsumed();
326
+ }
327
+ case "clear":
328
+ case "reset": {
329
+ await backend.clear(runtime.settings.getAgentDir(), runtime.cwd, runtime.session);
330
+ await runtime.session.refreshBaseSystemPrompt();
331
+ await runtime.output("Memory cleared.");
332
+ return commandConsumed();
333
+ }
334
+ case "enqueue":
335
+ case "rebuild": {
336
+ await backend.enqueue(runtime.settings.getAgentDir(), runtime.cwd, runtime.session);
337
+ await runtime.output("Memory consolidation enqueued.");
338
+ return commandConsumed();
339
+ }
340
+ case "stats":
341
+ case "diagnose": {
342
+ const hook = verb === "stats" ? backend.stats : backend.diagnose;
343
+ const payload = await hook?.(runtime.settings.getAgentDir(), runtime.cwd, runtime.session);
344
+ await runtime.output(payload ?? memoryStatsUnavailableMessage(backend.id, verb));
345
+ return commandConsumed();
346
+ }
347
+ case "mm":
348
+ return usage(
349
+ "Mental-model maintenance via /memory mm is unsupported in ACP mode; use the hindsight HTTP API directly.",
350
+ runtime,
351
+ );
352
+ default:
353
+ return usage("Usage: /memory <view|stats|diagnose|clear|reset|enqueue|rebuild>", runtime);
354
+ }
355
+ },
356
+ handleTui: async (command, runtime) => {
357
+ runtime.ctx.editor.setText("");
358
+ await runtime.ctx.handleMemoryCommand(command.text);
359
+ },
360
+ },
361
+ {
362
+ name: "rename",
363
+ description: "Rename the current session",
364
+ inlineHint: "<title>",
365
+ allowArgs: true,
366
+ handle: async (command, runtime) => {
367
+ if (!command.args) return usage("Usage: /rename <title>", runtime);
368
+ const ok = await runtime.sessionManager.setSessionName(command.args, "user");
369
+ if (!ok) {
370
+ await runtime.output("Session name not changed (a user-set name takes precedence).");
371
+ return commandConsumed();
372
+ }
373
+ await runtime.notifyTitleChanged?.();
374
+ await runtime.output(`Session renamed to ${command.args}.`);
375
+ return commandConsumed();
376
+ },
377
+ handleTui: async (command, runtime) => {
378
+ const title = command.args.trim();
379
+ if (!title) {
380
+ runtime.ctx.showError("Usage: /rename <title>");
381
+ runtime.ctx.editor.setText("");
382
+ return;
383
+ }
384
+ runtime.ctx.editor.setText("");
385
+ await runtime.ctx.handleRenameCommand(title);
386
+ },
387
+ },
388
+ {
389
+ name: "move",
390
+ description: "Move the current session to a different directory",
391
+ acpDescription: "Move the current session to a different directory",
392
+ inlineHint: "[<path>]",
393
+ allowArgs: true,
394
+ handle: async (command, runtime) => {
395
+ if (runtime.session.isStreaming) return usage("Cannot move while streaming.", runtime);
396
+ if (!command.args) return usage("Usage: /move <path>", runtime);
397
+ const resolvedPath = resolveToCwd(command.args, runtime.cwd);
398
+ try {
399
+ const stat = await fs.stat(resolvedPath);
400
+ if (!stat.isDirectory()) {
401
+ return usage(`Not a directory: ${resolvedPath}`, runtime);
402
+ }
403
+ } catch {
404
+ return usage(`Directory does not exist: ${resolvedPath}`, runtime);
405
+ }
406
+ try {
407
+ await runtime.settings.flush();
408
+ } catch (err) {
409
+ return usage(`Failed to save pending settings: ${errorMessage(err)}`, runtime);
410
+ }
411
+ try {
412
+ await runtime.session.moveSession(resolvedPath);
413
+ } catch (err) {
414
+ return usage(`Move failed: ${errorMessage(err)}`, runtime);
415
+ }
416
+ setProjectDir(resolvedPath);
417
+ await runtime.settings.reloadForCwd(resolvedPath);
418
+ applyProviderGlobalsFromSettings(runtime.settings);
419
+ // Reload plugin/capability caches so the next prompt sees commands and
420
+ // capabilities scoped to the new cwd.
421
+ await runtime.reloadPlugins();
422
+ await runtime.notifyConfigChanged?.();
423
+ await runtime.notifyTitleChanged?.();
424
+ await runtime.output(`Moved to ${runtime.sessionManager.getCwd()}.`);
425
+ return commandConsumed();
426
+ },
427
+ handleTui: async (command, runtime) => {
428
+ runtime.ctx.editor.addToHistory(command.text);
429
+ runtime.ctx.editor.setText("");
430
+ await runtime.ctx.handleMoveCommand(command.args || undefined);
431
+ },
432
+ },
433
+ {
434
+ name: "add-dir",
435
+ description: "Add a workspace directory to this session (multi-root)",
436
+ acpDescription: "Add a workspace directory to this session",
437
+ inlineHint: "<path>",
438
+ allowArgs: true,
439
+ handle: async (command, runtime) => {
440
+ if (runtime.session.isStreaming) return usage("Cannot add a directory while streaming.", runtime);
441
+ if (!command.args) return usage(formatWorkspaceDirectories(runtime, "Usage: /add-dir <path>"), runtime);
442
+ const resolved = resolveToCwd(command.args, runtime.cwd);
443
+ try {
444
+ const stat = await fs.stat(resolved);
445
+ if (!stat.isDirectory()) return usage(`Not a directory: ${resolved}`, runtime);
446
+ } catch {
447
+ return usage(`Directory does not exist: ${resolved}`, runtime);
448
+ }
449
+ let added: string | null;
450
+ try {
451
+ added = await runtime.sessionManager.addWorkspaceDirectory(resolved);
452
+ } catch (err) {
453
+ return usage(errorMessage(err), runtime);
454
+ }
455
+ if (added === null) {
456
+ await runtime.output(`Already in the workspace: ${resolved}`);
457
+ return commandConsumed();
458
+ }
459
+ await runtime.session.refreshBaseSystemPrompt();
460
+ await runtime.output(formatWorkspaceDirectories(runtime, `Added ${added}.`));
461
+ return commandConsumed();
462
+ },
463
+ },
464
+ {
465
+ name: "remove-dir",
466
+ description: "Remove a workspace directory from this session",
467
+ acpDescription: "Remove a workspace directory from this session",
468
+ inlineHint: "<path>",
469
+ allowArgs: true,
470
+ handle: async (command, runtime) => {
471
+ if (runtime.session.isStreaming) return usage("Cannot remove a directory while streaming.", runtime);
472
+ if (!command.args) return usage("Usage: /remove-dir <path>", runtime);
473
+ const resolved = resolveToCwd(command.args, runtime.cwd);
474
+ if (resolved === path.resolve(runtime.cwd)) {
475
+ return usage("Cannot remove the working directory; use /move to change it.", runtime);
476
+ }
477
+ let removed: string | null;
478
+ try {
479
+ removed = await runtime.sessionManager.removeWorkspaceDirectory(resolved);
480
+ } catch (err) {
481
+ return usage(errorMessage(err), runtime);
482
+ }
483
+ if (removed === null) {
484
+ await runtime.output(`Not a workspace directory: ${resolved}`);
485
+ return commandConsumed();
486
+ }
487
+ await runtime.session.refreshBaseSystemPrompt();
488
+ await runtime.output(formatWorkspaceDirectories(runtime, `Removed ${removed}.`));
489
+ return commandConsumed();
490
+ },
491
+ },
492
+ {
493
+ name: "dirs",
494
+ description: "List this session's workspace directories",
495
+ acpDescription: "List this session's workspace directories",
496
+ handle: async (_command, runtime) => {
497
+ await runtime.output(formatWorkspaceDirectories(runtime));
498
+ return commandConsumed();
499
+ },
500
+ },
501
+ {
502
+ name: "exit",
503
+ description: "Exit the application",
504
+ handleTui: shutdownHandlerTui,
505
+ },
506
+ ];