pi-ui-extend 1.0.42 → 1.0.45

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 (233) hide show
  1. package/README.md +41 -12
  2. package/dist/app/app.d.ts +10 -0
  3. package/dist/app/app.js +132 -8
  4. package/dist/app/cli/install.js +2 -2
  5. package/dist/app/commands/command-controller.d.ts +4 -0
  6. package/dist/app/commands/command-controller.js +11 -0
  7. package/dist/app/commands/command-git-actions.d.ts +25 -0
  8. package/dist/app/commands/command-git-actions.js +381 -0
  9. package/dist/app/commands/command-host.d.ts +3 -0
  10. package/dist/app/commands/command-host.js +5 -2
  11. package/dist/app/commands/command-model-actions.d.ts +1 -0
  12. package/dist/app/commands/command-model-actions.js +51 -34
  13. package/dist/app/commands/command-navigation-actions.d.ts +1 -0
  14. package/dist/app/commands/command-navigation-actions.js +16 -3
  15. package/dist/app/commands/command-registry.d.ts +2 -0
  16. package/dist/app/commands/command-registry.js +14 -0
  17. package/dist/app/commands/reload-context-inventory.js +12 -1
  18. package/dist/app/input/autocomplete-controller.js +61 -34
  19. package/dist/app/input/input-action-controller.d.ts +2 -0
  20. package/dist/app/input/input-action-controller.js +8 -1
  21. package/dist/app/input/input-controller.d.ts +2 -1
  22. package/dist/app/input/input-controller.js +7 -2
  23. package/dist/app/input/prompt-enhancer-controller.js +53 -35
  24. package/dist/app/input/voice-controller.d.ts +41 -45
  25. package/dist/app/input/voice-controller.js +351 -419
  26. package/dist/app/model/model-usage-controller.js +17 -7
  27. package/dist/app/model/model-usage-status.d.ts +4 -1
  28. package/dist/app/model/model-usage-status.js +270 -46
  29. package/dist/app/popup/menu-items-controller.d.ts +13 -3
  30. package/dist/app/popup/menu-items-controller.js +37 -21
  31. package/dist/app/popup/popup-action-controller.d.ts +12 -2
  32. package/dist/app/popup/popup-action-controller.js +77 -24
  33. package/dist/app/popup/popup-menu-controller.d.ts +36 -14
  34. package/dist/app/popup/popup-menu-controller.js +239 -69
  35. package/dist/app/rendering/dcp-stats.d.ts +9 -4
  36. package/dist/app/rendering/dcp-stats.js +40 -425
  37. package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
  38. package/dist/app/rendering/popup-menu-renderer.js +42 -37
  39. package/dist/app/rendering/render-controller.js +23 -2
  40. package/dist/app/rendering/status-line-renderer.d.ts +5 -0
  41. package/dist/app/rendering/status-line-renderer.js +41 -5
  42. package/dist/app/rendering/tab-line-renderer.js +26 -20
  43. package/dist/app/runtime.d.ts +12 -1
  44. package/dist/app/runtime.js +120 -12
  45. package/dist/app/screen/mouse-controller.d.ts +2 -0
  46. package/dist/app/screen/mouse-controller.js +17 -7
  47. package/dist/app/screen/status-controller.d.ts +4 -0
  48. package/dist/app/screen/status-controller.js +5 -0
  49. package/dist/app/session/lazy-session-manager.js +34 -0
  50. package/dist/app/session/session-event-controller.d.ts +1 -0
  51. package/dist/app/session/session-event-controller.js +10 -1
  52. package/dist/app/session/session-history.d.ts +1 -0
  53. package/dist/app/session/session-history.js +12 -1
  54. package/dist/app/session/session-lifecycle-controller.d.ts +6 -1
  55. package/dist/app/session/session-lifecycle-controller.js +10 -1
  56. package/dist/app/session/tabs-controller.d.ts +25 -1
  57. package/dist/app/session/tabs-controller.js +442 -153
  58. package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
  59. package/dist/app/subagents/subagents-widget-controller.js +6 -0
  60. package/dist/app/types.d.ts +11 -1
  61. package/dist/app/workspace/workspace-actions-controller.js +10 -2
  62. package/dist/app/workspace/workspace-undo.d.ts +1 -0
  63. package/dist/app/workspace/workspace-undo.js +1 -0
  64. package/dist/bundled-extensions/question/index.js +9 -1
  65. package/dist/bundled-extensions/question/remote.d.ts +4 -0
  66. package/dist/bundled-extensions/question/remote.js +33 -0
  67. package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
  68. package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
  69. package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
  70. package/dist/bundled-extensions/telegram-connector/config.js +87 -0
  71. package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
  72. package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
  73. package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
  74. package/dist/bundled-extensions/telegram-connector/index.js +195 -0
  75. package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
  76. package/dist/bundled-extensions/terminal-bell/index.js +0 -76
  77. package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
  78. package/dist/bundled-extensions/workspace-undo/index.js +191 -0
  79. package/dist/config.d.ts +21 -2
  80. package/dist/config.js +159 -32
  81. package/dist/default-pix-config.js +20 -10
  82. package/dist/schemas/pi-tools-suite-schema.d.ts +8 -4
  83. package/dist/schemas/pi-tools-suite-schema.js +11 -5
  84. package/dist/schemas/pix-schema.d.ts +12 -2
  85. package/dist/schemas/pix-schema.js +37 -9
  86. package/dist/schemas/tasks-schema.js +1 -1
  87. package/docs/concurrency.md +3 -1
  88. package/docs/desktop-mvp.md +10 -5
  89. package/docs/desktop-task-manager.md +81 -28
  90. package/docs/release.md +30 -6
  91. package/external/pi-tools-suite/README.md +165 -39
  92. package/external/pi-tools-suite/docs/session-recovery.md +7 -0
  93. package/external/pi-tools-suite/docs/subagent-model-pools.md +12 -5
  94. package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
  95. package/external/pi-tools-suite/package.json +6 -1
  96. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
  97. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
  98. package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
  99. package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
  100. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
  101. package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
  102. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
  103. package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
  104. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
  105. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
  106. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
  107. package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
  108. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
  109. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
  110. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
  111. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
  112. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
  113. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
  114. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
  115. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
  116. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
  117. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
  118. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
  119. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
  120. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
  121. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
  122. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
  123. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
  124. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
  125. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
  126. package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
  127. package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
  128. package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
  129. package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +0 -2
  130. package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
  131. package/external/pi-tools-suite/src/async-subagents/core/config.ts +40 -17
  132. package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
  133. package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
  134. package/external/pi-tools-suite/src/async-subagents/core/routing.ts +7 -4
  135. package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +34 -24
  136. package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
  137. package/external/pi-tools-suite/src/async-subagents/lib.ts +9 -1
  138. package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +0 -1
  139. package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
  140. package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
  141. package/external/pi-tools-suite/src/config.ts +14 -0
  142. package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
  143. package/external/pi-tools-suite/src/context-gateway/config.ts +66 -1
  144. package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
  145. package/external/pi-tools-suite/src/context-gateway/enforcement.ts +123 -3
  146. package/external/pi-tools-suite/src/context-gateway/index.ts +137 -4
  147. package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +5 -5
  148. package/external/pi-tools-suite/src/context-gateway/telemetry.ts +2 -2
  149. package/external/pi-tools-suite/src/context-gateway/types.ts +8 -1
  150. package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
  151. package/external/pi-tools-suite/src/dcp/auto-compress.ts +41 -14
  152. package/external/pi-tools-suite/src/dcp/commands.ts +14 -156
  153. package/external/pi-tools-suite/src/dcp/compress-tool.ts +8 -2
  154. package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
  155. package/external/pi-tools-suite/src/dcp/config.ts +58 -4
  156. package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
  157. package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
  158. package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
  159. package/external/pi-tools-suite/src/dcp/index.ts +338 -66
  160. package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
  161. package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
  162. package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +92 -2
  163. package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
  164. package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
  165. package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
  166. package/external/pi-tools-suite/src/dcp/state.ts +9 -0
  167. package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
  168. package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
  169. package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +68 -16
  170. package/external/pi-tools-suite/src/lib/project.ts +36 -1
  171. package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
  172. package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
  173. package/external/pi-tools-suite/src/resource-registry/index.ts +282 -21
  174. package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
  175. package/external/pi-tools-suite/src/todo/index.ts +21 -0
  176. package/external/pi-tools-suite/src/todo/todo.ts +19 -1
  177. package/external/pi-tools-suite/src/tool-descriptions.ts +44 -15
  178. package/package.json +7 -8
  179. package/schemas/pi-tools-suite.json +44 -12
  180. package/schemas/pix.json +109 -15
  181. package/schemas/tasks.json +1 -2
  182. package/skills/simplify/SKILL.md +33 -5
  183. package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -196
  184. package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
  185. package/skills/playwright-cli/SKILL.md +0 -420
  186. package/skills/playwright-cli/references/element-attributes.md +0 -23
  187. package/skills/playwright-cli/references/playwright-tests.md +0 -50
  188. package/skills/playwright-cli/references/request-mocking.md +0 -87
  189. package/skills/playwright-cli/references/running-code.md +0 -241
  190. package/skills/playwright-cli/references/session-management.md +0 -273
  191. package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
  192. package/skills/playwright-cli/references/storage-state.md +0 -290
  193. package/skills/playwright-cli/references/test-generation.md +0 -142
  194. package/skills/playwright-cli/references/tracing.md +0 -154
  195. package/skills/playwright-cli/references/video-recording.md +0 -147
  196. package/skills/spec-wiki/SKILL.md +0 -55
  197. package/skills/spec-wiki/evals/evals.json +0 -132
  198. package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +0 -14
  199. package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +0 -4
  200. package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +0 -9
  201. package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +0 -17
  202. package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +0 -12
  203. package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +0 -11
  204. package/skills/spec-wiki/evals/files/focused/src/session.ts +0 -5
  205. package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +0 -17
  206. package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +0 -2
  207. package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +0 -3
  208. package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +0 -18
  209. package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +0 -5
  210. package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +0 -24
  211. package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +0 -7
  212. package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +0 -7
  213. package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +0 -17
  214. package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +0 -3
  215. package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +0 -3
  216. package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +0 -4
  217. package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +0 -9
  218. package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +0 -15
  219. package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +0 -6
  220. package/skills/spec-wiki/evals/maintenance_regression.py +0 -311
  221. package/skills/spec-wiki/evals/retrieval-eval.md +0 -37
  222. package/skills/spec-wiki/evals/retrieval-evals.json +0 -37
  223. package/skills/spec-wiki/evals/trigger-evals.json +0 -94
  224. package/skills/spec-wiki/references/bootstrap.md +0 -100
  225. package/skills/spec-wiki/references/focused-spec.md +0 -117
  226. package/skills/spec-wiki/references/index-schema.md +0 -330
  227. package/skills/spec-wiki/references/maintenance.md +0 -134
  228. package/skills/spec-wiki/references/query.md +0 -74
  229. package/skills/spec-wiki/scripts/spec_wiki.py +0 -2076
  230. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
  231. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
  232. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
  233. /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
@@ -0,0 +1,381 @@
1
+ import { existsSync, lstatSync, readFileSync, readlinkSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+ import { parse as parseJsonc } from "jsonc-parser";
5
+ import { getPixConfigPath, getProjectPixConfigPath } from "../../config.js";
6
+ import { createId } from "../id.js";
7
+ import { parseModelRef } from "../model/model-ref.js";
8
+ import { runProcess } from "../process.js";
9
+ import { captureCommandScope, isCommandScopeActive, } from "./command-host.js";
10
+ import { getIdleRuntime } from "./command-runtime.js";
11
+ const GIT_REVIEW_TIMEOUT_MS = 120_000;
12
+ const GIT_COMMIT_MESSAGE_TIMEOUT_MS = 45_000;
13
+ const GIT_REVIEW_MAX_TOKENS = 4_096;
14
+ const GIT_COMMIT_MESSAGE_MAX_TOKENS = 768;
15
+ const GIT_DIFF_MAX_CHARS = 200_000;
16
+ const UNTRACKED_FILE_MAX_BYTES = 128 * 1024;
17
+ const DEFAULT_GIT_REVIEW_MODEL = "openai-codex/gpt-5.6-luna:medium";
18
+ const DEFAULT_GIT_COMMIT_MESSAGE_MODEL = "openai-codex/gpt-5.6-luna:minimal";
19
+ const REVIEW_SYSTEM_PROMPT = `You review Git diffs as a senior software engineer.
20
+
21
+ Focus on correctness, regressions, edge cases, security, data loss, concurrency, and missing tests.
22
+ Report concrete findings in descending severity and include file/line context when the diff provides it.
23
+ Do not invent problems or request unrelated refactors.
24
+ Do not flag a deletion merely because code or functionality was removed. Report a deletion only when the diff gives concrete evidence of a surviving broken reference, violated contract, unintended data loss/security impact, or another specific regression.
25
+ If there are no significant findings, say that clearly and briefly.
26
+ Return concise Markdown suitable for a code-review panel.`;
27
+ const COMMIT_MESSAGE_SYSTEM_PROMPT = `You write Git commit messages from staged diffs.
28
+
29
+ Write an imperative subject line no longer than 72 characters.
30
+ Add a short body only when it materially improves understanding.
31
+ Describe what changed and why, not implementation trivia.
32
+ Output only the final commit message. Do not use Markdown fences or commentary.`;
33
+ export class GitCommandActions {
34
+ host;
35
+ runProcess;
36
+ generateGitAssistantText;
37
+ constructor(host, deps = {}) {
38
+ this.host = host;
39
+ this.runProcess = deps.runProcess ?? runProcess;
40
+ this.generateGitAssistantText = deps.generateGitAssistantText ?? generateGitAssistantText;
41
+ }
42
+ async runCodeReviewCommand() {
43
+ const runtime = getIdleRuntime(this.host, "code-review");
44
+ if (!runtime)
45
+ return;
46
+ const scope = captureCommandScope(this.host);
47
+ this.host.setStatus("collecting Git changes");
48
+ this.host.render();
49
+ const root = await this.gitRoot(runtime.cwd);
50
+ if (!isCommandScopeActive(this.host, scope))
51
+ return;
52
+ const diff = await this.reviewInput(root);
53
+ if (!isCommandScopeActive(this.host, scope))
54
+ return;
55
+ if (!diff.trim()) {
56
+ this.host.addEntry({ id: createId("system"), kind: "system", text: "No Git changes to review." });
57
+ this.host.setSessionStatus(runtime.session);
58
+ return;
59
+ }
60
+ this.host.setStatus("reviewing Git changes");
61
+ this.host.render();
62
+ const review = await this.generateGitAssistantText(runtime, "review", diff);
63
+ if (!isCommandScopeActive(this.host, scope))
64
+ return;
65
+ this.host.addEntry({ id: createId("system"), kind: "system", text: `Code review\n\n${review}` });
66
+ this.host.setSessionStatus(runtime.session);
67
+ this.host.toast.success("Code review complete");
68
+ }
69
+ async runCommitMessageCommand() {
70
+ const runtime = getIdleRuntime(this.host, "commit-message");
71
+ if (!runtime)
72
+ return;
73
+ const scope = captureCommandScope(this.host);
74
+ this.host.setStatus("reading staged changes");
75
+ this.host.render();
76
+ const root = await this.gitRoot(runtime.cwd);
77
+ if (!isCommandScopeActive(this.host, scope))
78
+ return;
79
+ const stagedDiff = await this.trackedDiff(root, true);
80
+ if (!isCommandScopeActive(this.host, scope))
81
+ return;
82
+ if (!stagedDiff.trim()) {
83
+ this.host.addEntry({ id: createId("system"), kind: "system", text: "There are no staged changes to commit." });
84
+ this.host.setSessionStatus(runtime.session);
85
+ this.host.toast.info("No staged changes");
86
+ return;
87
+ }
88
+ this.host.setStatus("generating commit message");
89
+ this.host.render();
90
+ const message = await this.generateGitAssistantText(runtime, "commit-message", capGitInput(stagedDiff));
91
+ if (!isCommandScopeActive(this.host, scope))
92
+ return;
93
+ this.host.addEntry({
94
+ id: createId("system"),
95
+ kind: "system",
96
+ text: `Generated commit message\n\n${message}`,
97
+ });
98
+ this.host.setSessionStatus(runtime.session);
99
+ const action = await this.host.showMenu([
100
+ {
101
+ value: "commit",
102
+ label: "Commit staged changes",
103
+ description: firstLine(message),
104
+ variant: "accent",
105
+ },
106
+ { value: "cancel", label: "Cancel" },
107
+ ], { title: "Create Git commit?", searchable: false, preserveStatus: true });
108
+ if (!isCommandScopeActive(this.host, scope))
109
+ return;
110
+ if (action !== "commit") {
111
+ this.host.addEntry({ id: createId("system"), kind: "system", text: "Commit cancelled." });
112
+ this.host.setSessionStatus(runtime.session);
113
+ return;
114
+ }
115
+ const currentStagedDiff = await this.trackedDiff(root, true);
116
+ if (!isCommandScopeActive(this.host, scope))
117
+ return;
118
+ if (currentStagedDiff !== stagedDiff) {
119
+ this.host.addEntry({
120
+ id: createId("error"),
121
+ kind: "error",
122
+ text: "Staged changes changed after the commit message was generated. Run /commit-message again before committing.",
123
+ });
124
+ this.host.setSessionStatus(runtime.session);
125
+ this.host.toast.warning("Staged changes changed");
126
+ return;
127
+ }
128
+ this.host.setStatus("creating Git commit");
129
+ this.host.render();
130
+ const result = await this.runProcess("git", ["commit", "-F", "-"], {
131
+ cwd: root,
132
+ input: `${message.trim()}\n`,
133
+ timeoutMs: 120_000,
134
+ maxBufferBytes: 256 * 1024,
135
+ });
136
+ if (!isCommandScopeActive(this.host, scope))
137
+ return;
138
+ if (result.status !== 0) {
139
+ throw new Error(gitFailureMessage("git commit", result));
140
+ }
141
+ const detail = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
142
+ this.host.addEntry({
143
+ id: createId("system"),
144
+ kind: "system",
145
+ text: detail ? `Git commit created\n\n${detail}` : "Git commit created.",
146
+ });
147
+ this.host.setSessionStatus(runtime.session);
148
+ this.host.toast.success("Git commit created");
149
+ }
150
+ async gitRoot(cwd) {
151
+ const result = await this.runProcess("git", ["rev-parse", "--show-toplevel"], {
152
+ cwd,
153
+ timeoutMs: 10_000,
154
+ maxBufferBytes: 16 * 1024,
155
+ });
156
+ if (result.status !== 0)
157
+ throw new Error(gitFailureMessage("git rev-parse", result));
158
+ const root = result.stdout.trim();
159
+ if (!root)
160
+ throw new Error("Git repository root is unavailable");
161
+ return root;
162
+ }
163
+ async trackedDiff(root, staged) {
164
+ const args = ["diff", ...(staged ? ["--cached"] : []), "--no-ext-diff", "--no-color", "--minimal"];
165
+ const result = await this.runProcess("git", args, {
166
+ cwd: root,
167
+ timeoutMs: 20_000,
168
+ maxBufferBytes: GIT_DIFF_MAX_CHARS + 16 * 1024,
169
+ });
170
+ if (result.status !== 0)
171
+ throw new Error(gitFailureMessage(staged ? "git diff --cached" : "git diff", result));
172
+ return result.stdout;
173
+ }
174
+ async reviewInput(root) {
175
+ const staged = await this.trackedDiff(root, true);
176
+ const unstaged = await this.trackedDiff(root, false);
177
+ const untracked = await this.untrackedFiles(root);
178
+ const sections = [];
179
+ if (staged.trim())
180
+ sections.push(`# Staged changes\n\n${staged.trimEnd()}`);
181
+ if (unstaged.trim())
182
+ sections.push(`# Working tree changes\n\n${unstaged.trimEnd()}`);
183
+ if (untracked.trim())
184
+ sections.push(`# Untracked files\n\n${untracked.trimEnd()}`);
185
+ return capGitInput(sections.join("\n\n"));
186
+ }
187
+ async untrackedFiles(root) {
188
+ const result = await this.runProcess("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
189
+ cwd: root,
190
+ timeoutMs: 10_000,
191
+ maxBufferBytes: GIT_DIFF_MAX_CHARS,
192
+ });
193
+ if (result.status !== 0)
194
+ throw new Error(gitFailureMessage("git ls-files", result));
195
+ const files = result.stdout.split("\0").filter(Boolean);
196
+ const sections = [];
197
+ let used = 0;
198
+ for (const file of files) {
199
+ const absolutePath = resolve(root, file);
200
+ const relativePath = relative(root, absolutePath);
201
+ if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(`..${sep}`))
202
+ continue;
203
+ const label = displayGitPath(file);
204
+ let body;
205
+ try {
206
+ const info = lstatSync(absolutePath);
207
+ if (info.isSymbolicLink()) {
208
+ body = `new symbolic link -> ${readlinkSync(absolutePath)}`;
209
+ }
210
+ else if (!info.isFile()) {
211
+ body = "non-regular file omitted";
212
+ }
213
+ else if (statSync(absolutePath).size > UNTRACKED_FILE_MAX_BYTES) {
214
+ body = `file omitted: larger than ${UNTRACKED_FILE_MAX_BYTES} bytes`;
215
+ }
216
+ else {
217
+ const bytes = readFileSync(absolutePath);
218
+ body = bytes.includes(0)
219
+ ? "binary file omitted"
220
+ : bytes.toString("utf8").split(/\r?\n/u).map((line) => `+${line}`).join("\n");
221
+ }
222
+ }
223
+ catch {
224
+ body = "file became unavailable while collecting review input";
225
+ }
226
+ const section = `## ${label}\n\n--- /dev/null\n+++ b/${label}\n${body}`;
227
+ if (used + section.length > GIT_DIFF_MAX_CHARS) {
228
+ sections.push("[additional untracked files omitted: review input limit reached]");
229
+ break;
230
+ }
231
+ sections.push(section);
232
+ used += section.length;
233
+ }
234
+ return sections.join("\n\n");
235
+ }
236
+ }
237
+ export async function generateGitAssistantText(runtime, kind, diff) {
238
+ const normalizedDiff = diff.trim();
239
+ if (!normalizedDiff)
240
+ throw new Error("Git diff is empty");
241
+ const timeoutMs = kind === "review" ? GIT_REVIEW_TIMEOUT_MS : GIT_COMMIT_MESSAGE_TIMEOUT_MS;
242
+ const signal = AbortSignal.timeout(timeoutMs);
243
+ const modelRuntime = runtime.services.modelRuntime;
244
+ await modelRuntime.refresh({ signal });
245
+ let lastError;
246
+ for (const modelRef of loadGitAssistantModelRefs(runtime.cwd, kind)) {
247
+ if (signal.aborted)
248
+ throw signal.reason ?? new Error("Git assistant request aborted");
249
+ let parsed;
250
+ try {
251
+ parsed = parseModelRef(modelRef);
252
+ }
253
+ catch (error) {
254
+ lastError = error;
255
+ continue;
256
+ }
257
+ const model = modelRuntime.getModel(parsed.provider, parsed.modelId);
258
+ if (!model) {
259
+ lastError = new Error(`Git assistant model not found: ${parsed.provider}/${parsed.modelId}`);
260
+ continue;
261
+ }
262
+ try {
263
+ const tokenLimit = kind === "review" ? GIT_REVIEW_MAX_TOKENS : GIT_COMMIT_MESSAGE_MAX_TOKENS;
264
+ const maxTokens = model.maxTokens > 0 ? Math.min(model.maxTokens, tokenLimit) : tokenLimit;
265
+ let output = "";
266
+ let streamError;
267
+ const stream = modelRuntime.streamSimple({ ...model, maxTokens }, {
268
+ systemPrompt: kind === "review" ? REVIEW_SYSTEM_PROMPT : COMMIT_MESSAGE_SYSTEM_PROMPT,
269
+ messages: [{
270
+ role: "user",
271
+ content: [
272
+ kind === "review" ? "Review this diff." : "Write a commit message for this staged diff.",
273
+ "<git-diff>",
274
+ normalizedDiff,
275
+ "</git-diff>",
276
+ ].join("\n"),
277
+ timestamp: Date.now(),
278
+ }],
279
+ }, {
280
+ signal,
281
+ reasoning: parsed.thinkingLevel && parsed.thinkingLevel !== "off" ? parsed.thinkingLevel : "minimal",
282
+ cacheRetention: "none",
283
+ maxRetryDelayMs: 0,
284
+ maxRetries: 0,
285
+ maxTokens,
286
+ timeoutMs,
287
+ });
288
+ for await (const event of stream) {
289
+ if (event.type === "text_delta")
290
+ output += event.delta;
291
+ else if (event.type === "done" && !output)
292
+ output = assistantText(event.message);
293
+ else if (event.type === "error")
294
+ streamError = event.error.errorMessage ?? event.reason;
295
+ }
296
+ if (streamError)
297
+ throw new Error(streamError);
298
+ const cleaned = cleanupOutput(output);
299
+ if (!cleaned)
300
+ throw new Error("Git assistant returned an empty response");
301
+ return cleaned;
302
+ }
303
+ catch (error) {
304
+ if (signal.aborted)
305
+ throw signal.reason ?? error;
306
+ lastError = error;
307
+ }
308
+ }
309
+ throw lastError ?? new Error("No Git assistant models are configured");
310
+ }
311
+ export function loadGitAssistantModelRefs(cwd, kind, homeDir = homedir()) {
312
+ const defaults = kind === "review"
313
+ ? { modelRef: DEFAULT_GIT_REVIEW_MODEL, fallbackModels: [] }
314
+ : { modelRef: DEFAULT_GIT_COMMIT_MESSAGE_MODEL, fallbackModels: [] };
315
+ const globalConfig = { ...defaults, ...readGitAssistantModelConfig(getPixConfigPath(homeDir), kind) };
316
+ const projectConfig = readGitAssistantModelConfig(getProjectPixConfigPath(cwd), kind);
317
+ const resolved = { ...globalConfig, ...projectConfig };
318
+ return [...new Set([resolved.modelRef, ...resolved.fallbackModels].map((ref) => ref.trim()).filter(Boolean))];
319
+ }
320
+ function readGitAssistantModelConfig(path, kind) {
321
+ if (!existsSync(path))
322
+ return {};
323
+ try {
324
+ const parsed = parseJsonc(readFileSync(path, "utf8"));
325
+ if (!isRecord(parsed) || !isRecord(parsed.desktop) || !isRecord(parsed.desktop.git))
326
+ return {};
327
+ const git = parsed.desktop.git;
328
+ const modelValue = kind === "review" ? git.reviewModelRef : git.commitMessageModelRef;
329
+ const fallbackKey = kind === "review" ? "reviewFallbackModels" : "commitMessageFallbackModels";
330
+ const modelRef = typeof modelValue === "string" && modelValue.trim() ? modelValue.trim() : undefined;
331
+ const hasFallbackModels = Object.prototype.hasOwnProperty.call(git, fallbackKey);
332
+ return {
333
+ ...(modelRef ? { modelRef } : {}),
334
+ ...(hasFallbackModels ? { fallbackModels: modelFallbackList(git[fallbackKey]) } : {}),
335
+ };
336
+ }
337
+ catch {
338
+ return {};
339
+ }
340
+ }
341
+ function modelFallbackList(value) {
342
+ if (!Array.isArray(value))
343
+ return [];
344
+ return [...new Set(value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean))];
345
+ }
346
+ function capGitInput(value) {
347
+ if (value.length <= GIT_DIFF_MAX_CHARS)
348
+ return value;
349
+ const marker = "\n\n[Git diff truncated to fit the review input limit]";
350
+ return `${value.slice(0, GIT_DIFF_MAX_CHARS - marker.length)}${marker}`;
351
+ }
352
+ function cleanupOutput(value) {
353
+ let text = value.replace(/\r\n/gu, "\n").trim();
354
+ const fenced = /^```[^\n`]*\n([\s\S]*?)\n```$/u.exec(text);
355
+ if (fenced)
356
+ text = fenced[1].trim();
357
+ return text;
358
+ }
359
+ function assistantText(message) {
360
+ if (!isRecord(message))
361
+ return "";
362
+ const content = message.content;
363
+ if (typeof content === "string")
364
+ return content;
365
+ if (!Array.isArray(content))
366
+ return "";
367
+ return content.flatMap((part) => isRecord(part) && part.type === "text" && typeof part.text === "string" ? [part.text] : []).join("");
368
+ }
369
+ function firstLine(value) {
370
+ return value.trim().split(/\r?\n/u, 1)[0] ?? "Use the generated commit message";
371
+ }
372
+ function displayGitPath(value) {
373
+ return value.replaceAll("\n", "\\n").replaceAll("\r", "\\r");
374
+ }
375
+ function gitFailureMessage(command, result) {
376
+ const detail = result.stderr.trim() || result.stdout.trim() || result.error?.message;
377
+ return detail ? `${command} failed: ${detail}` : `${command} failed`;
378
+ }
379
+ function isRecord(value) {
380
+ return typeof value === "object" && value !== null && !Array.isArray(value);
381
+ }
@@ -7,6 +7,7 @@ export type DirectPopupMenu = Exclude<ActivePopupMenu, "slash">;
7
7
  export type CommandControllerHost = {
8
8
  readonly options: AppOptions;
9
9
  runtime(): AgentSessionRuntime | undefined;
10
+ inputScopeKey?(): string | undefined;
10
11
  subagentTypes?(runtime: AgentSessionRuntime): readonly string[] | undefined;
11
12
  awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
12
13
  requestHistory(): AppRequestHistory;
@@ -42,6 +43,7 @@ export type CommandControllerHost = {
42
43
  setDirectPopupMenuPreserveStatus(preserveStatus: boolean): void;
43
44
  getDirectPopupMenuQuery(): string;
44
45
  setDirectPopupMenuQuery(query: string): void;
46
+ setResumeMenuMode?(mode: "resume" | "draft"): void;
45
47
  refreshUserMessageJumpMenuItems(): Promise<void>;
46
48
  getResumeLoading(): boolean;
47
49
  getResumeSessions(): readonly SessionInfo[];
@@ -55,6 +57,7 @@ export type CommandControllerHost = {
55
57
  export type CommandScope = {
56
58
  readonly runtime: AgentSessionRuntime | undefined;
57
59
  readonly session: AgentSession | undefined;
60
+ readonly inputScopeKey: string | undefined;
58
61
  };
59
62
  export declare function captureCommandScope(host: CommandControllerHost): CommandScope;
60
63
  export declare function isCommandRuntimeActive(host: CommandControllerHost, runtime: AgentSessionRuntime): boolean;
@@ -1,10 +1,13 @@
1
1
  export function captureCommandScope(host) {
2
2
  const runtime = host.runtime();
3
- return { runtime, session: runtime?.session };
3
+ return { runtime, session: runtime?.session, inputScopeKey: host.inputScopeKey?.() };
4
4
  }
5
5
  export function isCommandRuntimeActive(host, runtime) {
6
6
  return host.isRunning() && host.runtime() === runtime;
7
7
  }
8
8
  export function isCommandScopeActive(host, scope) {
9
- return host.isRunning() && host.runtime() === scope.runtime && scope.runtime?.session === scope.session;
9
+ return host.isRunning()
10
+ && host.runtime() === scope.runtime
11
+ && scope.runtime?.session === scope.session
12
+ && (host.inputScopeKey === undefined || host.inputScopeKey() === scope.inputScopeKey);
10
13
  }
@@ -12,6 +12,7 @@ export declare class ModelCommandActions {
12
12
  runThinkingSlashCommand(argumentsText: string): Promise<void>;
13
13
  runDefaultThinkingSlashCommand(argumentsText: string): Promise<void>;
14
14
  runModelCommand(model: SessionModel): Promise<void>;
15
+ runModelThinkingCommand(model: SessionModel, level: ThinkingLevel): Promise<void>;
15
16
  runThinkingCommand(level: ThinkingLevel): Promise<void>;
16
17
  private addPersistentSystemEntry;
17
18
  private reloadAfterModelChange;
@@ -46,19 +46,7 @@ export class ModelCommandActions {
46
46
  const scope = captureCommandScope(this.host);
47
47
  const modelRef = argumentsText.trim();
48
48
  if (!modelRef) {
49
- const selected = await this.host.showMenu(this.host.getModelMenuItems(""), {
50
- title: "Select model",
51
- placeholder: "Search models",
52
- emptyText: "No matching models",
53
- });
54
- if (!isCommandScopeActive(this.host, scope))
55
- return;
56
- if (!selected) {
57
- this.host.setSessionStatus(this.host.runtime()?.session);
58
- this.host.render();
59
- return;
60
- }
61
- await this.runModelCommand(selected.model);
49
+ this.host.openDirectPopupMenu("model");
62
50
  this.host.render();
63
51
  return;
64
52
  }
@@ -74,14 +62,10 @@ export class ModelCommandActions {
74
62
  const model = runtime.services.modelRuntime.getModel(parsed.provider, parsed.modelId);
75
63
  if (!model)
76
64
  throw new Error(`Model not found: ${parsed.provider}/${parsed.modelId}`);
77
- await this.runModelCommand(model);
78
- if (!isCommandScopeActive(this.host, scope))
79
- return;
80
- if (parsed.thinkingLevel !== undefined) {
81
- runtime.session.setThinkingLevel(parsed.thinkingLevel);
82
- this.addPersistentSystemEntry(runtime.session, `Selected thinking level ${runtime.session.thinkingLevel}`);
83
- this.host.setSessionStatus(runtime.session);
84
- }
65
+ if (parsed.thinkingLevel !== undefined)
66
+ await this.runModelThinkingCommand(model, parsed.thinkingLevel);
67
+ else
68
+ await this.runModelCommand(model);
85
69
  }
86
70
  async runDefaultModelSlashCommand(argumentsText) {
87
71
  const scope = captureCommandScope(this.host);
@@ -242,19 +226,7 @@ export class ModelCommandActions {
242
226
  const scope = captureCommandScope(this.host);
243
227
  const level = argumentsText.trim();
244
228
  if (!level) {
245
- const selected = await this.host.showMenu(this.host.getThinkingMenuItems(""), {
246
- title: "Select thinking level",
247
- placeholder: "Search thinking levels",
248
- emptyText: "No matching thinking levels",
249
- });
250
- if (!isCommandScopeActive(this.host, scope))
251
- return;
252
- if (!selected) {
253
- this.host.setSessionStatus(this.host.runtime()?.session);
254
- this.host.render();
255
- return;
256
- }
257
- await this.runThinkingCommand(selected.level);
229
+ this.host.openDirectPopupMenu("thinking");
258
230
  this.host.render();
259
231
  return;
260
232
  }
@@ -313,6 +285,51 @@ export class ModelCommandActions {
313
285
  return;
314
286
  this.host.setSessionStatus(runtime.session);
315
287
  }
288
+ async runModelThinkingCommand(model, level) {
289
+ const runtime = getRuntime(this.host, "model");
290
+ if (!runtime)
291
+ return;
292
+ const scope = captureCommandScope(this.host);
293
+ const session = runtime.session;
294
+ const ref = this.host.modelRef(model);
295
+ const currentModel = session.model;
296
+ const modelChanged = currentModel?.provider !== model.provider || currentModel.id !== model.id;
297
+ const initialThinking = session.thinkingLevel;
298
+ if (!modelChanged && initialThinking === level) {
299
+ this.host.setSessionStatus(session);
300
+ return;
301
+ }
302
+ this.host.setStatus(`selecting ${ref} · thinking ${level}`);
303
+ this.host.render();
304
+ if (modelChanged) {
305
+ await session.setModel(model);
306
+ if (!isCommandScopeActive(this.host, scope))
307
+ return;
308
+ this.host.addEntry({ id: createId("system"), kind: "system", text: `Selected model ${ref}` });
309
+ }
310
+ session.setThinkingLevel(level);
311
+ if (modelChanged || session.thinkingLevel !== initialThinking) {
312
+ this.addPersistentSystemEntry(session, `Selected thinking level ${session.thinkingLevel}`);
313
+ }
314
+ if (!modelChanged) {
315
+ this.host.setSessionStatus(session);
316
+ return;
317
+ }
318
+ if (session.isStreaming) {
319
+ this.host.addEntry({
320
+ id: createId("system"),
321
+ kind: "system",
322
+ text: "Skipped reload because the agent is still running. Run /reload when idle to refresh model-specific tools.",
323
+ });
324
+ this.host.toast.warning("Model changed; reload skipped while the agent is running");
325
+ this.host.setSessionStatus(session);
326
+ return;
327
+ }
328
+ await this.reloadAfterModelChange(session, ref, scope);
329
+ if (!isCommandScopeActive(this.host, scope))
330
+ return;
331
+ this.host.setSessionStatus(session);
332
+ }
316
333
  async runThinkingCommand(level) {
317
334
  const runtime = getRuntime(this.host, "thinking");
318
335
  if (!runtime)
@@ -26,6 +26,7 @@ export declare class NavigationCommandActions {
26
26
  runResumeCommand(queryOrOptions?: string | {
27
27
  preserveStatus?: boolean;
28
28
  placement?: PopupMenuPlacement;
29
+ draft?: boolean;
29
30
  }): Promise<void>;
30
31
  private loadResumeSessionsInBackground;
31
32
  private formatSessionTree;
@@ -277,14 +277,15 @@ export class NavigationCommandActions {
277
277
  async runResumeCommand(queryOrOptions = "") {
278
278
  const preserveStatus = typeof queryOrOptions === "object" && queryOrOptions.preserveStatus === true;
279
279
  const placement = typeof queryOrOptions === "object" ? queryOrOptions.placement : undefined;
280
+ const draft = typeof queryOrOptions === "object" && queryOrOptions.draft === true;
280
281
  const initialQuery = typeof queryOrOptions === "string" ? queryOrOptions : "";
281
- const runtime = preserveStatus ? this.host.runtime() : getIdleRuntime(this.host, "resume");
282
- if (!runtime) {
282
+ const runtime = draft ? this.host.runtime() : preserveStatus ? this.host.runtime() : getIdleRuntime(this.host, "resume");
283
+ if (!runtime && !draft) {
283
284
  if (!preserveStatus)
284
285
  this.host.render();
285
286
  return;
286
287
  }
287
- if (runtime.session.isStreaming) {
288
+ if (runtime?.session.isStreaming) {
288
289
  if (!preserveStatus) {
289
290
  this.host.toast.warning("/resume is unavailable while the agent is running");
290
291
  this.host.render();
@@ -292,6 +293,12 @@ export class NavigationCommandActions {
292
293
  return;
293
294
  }
294
295
  if (this.host.getResumeLoading()) {
296
+ if (draft) {
297
+ this.host.setResumeMenuMode?.("draft");
298
+ this.host.openDirectPopupMenu("resume", { preserveStatus: true, ...(placement === undefined ? {} : { placement }) });
299
+ this.host.setDirectPopupMenuQuery(initialQuery);
300
+ this.host.openResumeMenuWithQuery(initialQuery);
301
+ }
295
302
  if (!preserveStatus)
296
303
  this.host.setStatus("loading sessions…");
297
304
  this.host.render();
@@ -300,6 +307,7 @@ export class NavigationCommandActions {
300
307
  this.host.setResumeLoading(true);
301
308
  if (!preserveStatus)
302
309
  this.host.setStatus("loading sessions…");
310
+ this.host.setResumeMenuMode?.(draft ? "draft" : "resume");
303
311
  this.host.openDirectPopupMenu("resume", { preserveStatus, ...(placement === undefined ? {} : { placement }) });
304
312
  this.host.setDirectPopupMenuQuery(initialQuery);
305
313
  if (this.host.getResumeSessions().length > 0) {
@@ -344,6 +352,11 @@ export class NavigationCommandActions {
344
352
  this.host.setSessionStatus(options.scope.session);
345
353
  this.host.render();
346
354
  }
355
+ finally {
356
+ if (options.loadId === this.resumeLoadId && !isCommandScopeActive(this.host, options.scope)) {
357
+ this.host.setResumeLoading(false);
358
+ }
359
+ }
347
360
  }
348
361
  formatSessionTree(runtime) {
349
362
  const tree = runtime.session.sessionManager.getTree();
@@ -10,6 +10,8 @@ export type CommandRegistryActions = {
10
10
  runThinkingSlashCommand(argumentsText: string): Promise<void>;
11
11
  runDefaultThinkingSlashCommand(argumentsText: string): Promise<void>;
12
12
  runEnhanceCommand(): Promise<void>;
13
+ runCodeReviewCommand(): Promise<void>;
14
+ runCommitMessageCommand(): Promise<void>;
13
15
  runExportCommand(argumentsText: string): Promise<void>;
14
16
  runImportCommand(argumentsText: string): Promise<void>;
15
17
  runShareCommand(): Promise<void>;
@@ -71,6 +71,20 @@ export function createSlashCommands(actions, host) {
71
71
  keywords: ["prompt", "rewrite", "improve", "kilocode"],
72
72
  run: () => actions.runEnhanceCommand(),
73
73
  },
74
+ {
75
+ name: "code-review",
76
+ description: "Review staged, unstaged, and untracked Git changes",
77
+ kind: "builtin",
78
+ keywords: ["git", "review", "diff", "changes"],
79
+ run: () => actions.runCodeReviewCommand(),
80
+ },
81
+ {
82
+ name: "commit-message",
83
+ description: "Generate a message for staged changes and commit after confirmation",
84
+ kind: "builtin",
85
+ keywords: ["git", "commit", "message", "staged"],
86
+ run: () => actions.runCommitMessageCommand(),
87
+ },
74
88
  {
75
89
  name: "export",
76
90
  description: "Export session (HTML default, or .jsonl path)",
@@ -1,7 +1,7 @@
1
1
  export function createReloadContextInventory(runtime, agentTypes) {
2
2
  const session = runtime.session;
3
3
  const tools = unique(session.getActiveToolNames());
4
- const skillsReadable = tools.includes("read") || tools.includes("bash");
4
+ const skillsReadable = tools.some(isSkillFileAccessTool);
5
5
  const subagentsActive = tools.includes("subagents");
6
6
  const skills = skillsReadable
7
7
  ? unique(session.resourceLoader.getSkills().skills.map((skill) => skill.name)).sort(compareText)
@@ -20,6 +20,17 @@ export function createReloadContextInventory(runtime, agentTypes) {
20
20
  subagentsActive,
21
21
  };
22
22
  }
23
+ function isSkillFileAccessTool(toolName) {
24
+ switch (toolName.trim().toLowerCase()) {
25
+ case "read":
26
+ case "bash":
27
+ case "shell":
28
+ case "shell_command":
29
+ return true;
30
+ default:
31
+ return false;
32
+ }
33
+ }
23
34
  export function formatReloadContextInventory(inventory, heading = "Reloaded keybindings, extensions, skills, prompts, themes") {
24
35
  return [
25
36
  heading,