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.
- package/README.md +41 -12
- package/dist/app/app.d.ts +10 -0
- package/dist/app/app.js +132 -8
- package/dist/app/cli/install.js +2 -2
- package/dist/app/commands/command-controller.d.ts +4 -0
- package/dist/app/commands/command-controller.js +11 -0
- package/dist/app/commands/command-git-actions.d.ts +25 -0
- package/dist/app/commands/command-git-actions.js +381 -0
- package/dist/app/commands/command-host.d.ts +3 -0
- package/dist/app/commands/command-host.js +5 -2
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +51 -34
- package/dist/app/commands/command-navigation-actions.d.ts +1 -0
- package/dist/app/commands/command-navigation-actions.js +16 -3
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +14 -0
- package/dist/app/commands/reload-context-inventory.js +12 -1
- package/dist/app/input/autocomplete-controller.js +61 -34
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/input/input-controller.d.ts +2 -1
- package/dist/app/input/input-controller.js +7 -2
- package/dist/app/input/prompt-enhancer-controller.js +53 -35
- package/dist/app/input/voice-controller.d.ts +41 -45
- package/dist/app/input/voice-controller.js +351 -419
- package/dist/app/model/model-usage-controller.js +17 -7
- package/dist/app/model/model-usage-status.d.ts +4 -1
- package/dist/app/model/model-usage-status.js +270 -46
- package/dist/app/popup/menu-items-controller.d.ts +13 -3
- package/dist/app/popup/menu-items-controller.js +37 -21
- package/dist/app/popup/popup-action-controller.d.ts +12 -2
- package/dist/app/popup/popup-action-controller.js +77 -24
- package/dist/app/popup/popup-menu-controller.d.ts +36 -14
- package/dist/app/popup/popup-menu-controller.js +239 -69
- package/dist/app/rendering/dcp-stats.d.ts +9 -4
- package/dist/app/rendering/dcp-stats.js +40 -425
- package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
- package/dist/app/rendering/popup-menu-renderer.js +42 -37
- package/dist/app/rendering/render-controller.js +23 -2
- package/dist/app/rendering/status-line-renderer.d.ts +5 -0
- package/dist/app/rendering/status-line-renderer.js +41 -5
- package/dist/app/rendering/tab-line-renderer.js +26 -20
- package/dist/app/runtime.d.ts +12 -1
- package/dist/app/runtime.js +120 -12
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +17 -7
- package/dist/app/screen/status-controller.d.ts +4 -0
- package/dist/app/screen/status-controller.js +5 -0
- package/dist/app/session/lazy-session-manager.js +34 -0
- package/dist/app/session/session-event-controller.d.ts +1 -0
- package/dist/app/session/session-event-controller.js +10 -1
- package/dist/app/session/session-history.d.ts +1 -0
- package/dist/app/session/session-history.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +6 -1
- package/dist/app/session/session-lifecycle-controller.js +10 -1
- package/dist/app/session/tabs-controller.d.ts +25 -1
- package/dist/app/session/tabs-controller.js +442 -153
- package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
- package/dist/app/subagents/subagents-widget-controller.js +6 -0
- package/dist/app/types.d.ts +11 -1
- package/dist/app/workspace/workspace-actions-controller.js +10 -2
- package/dist/app/workspace/workspace-undo.d.ts +1 -0
- package/dist/app/workspace/workspace-undo.js +1 -0
- package/dist/bundled-extensions/question/index.js +9 -1
- package/dist/bundled-extensions/question/remote.d.ts +4 -0
- package/dist/bundled-extensions/question/remote.js +33 -0
- package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
- package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
- package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
- package/dist/bundled-extensions/telegram-connector/config.js +87 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
- package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
- package/dist/bundled-extensions/telegram-connector/index.js +195 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
- package/dist/bundled-extensions/terminal-bell/index.js +0 -76
- package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
- package/dist/bundled-extensions/workspace-undo/index.js +191 -0
- package/dist/config.d.ts +21 -2
- package/dist/config.js +159 -32
- package/dist/default-pix-config.js +20 -10
- package/dist/schemas/pi-tools-suite-schema.d.ts +8 -4
- package/dist/schemas/pi-tools-suite-schema.js +11 -5
- package/dist/schemas/pix-schema.d.ts +12 -2
- package/dist/schemas/pix-schema.js +37 -9
- package/dist/schemas/tasks-schema.js +1 -1
- package/docs/concurrency.md +3 -1
- package/docs/desktop-mvp.md +10 -5
- package/docs/desktop-task-manager.md +81 -28
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +165 -39
- package/external/pi-tools-suite/docs/session-recovery.md +7 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +12 -5
- package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
- package/external/pi-tools-suite/package.json +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
- package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +0 -2
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +40 -17
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +7 -4
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +34 -24
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/lib.ts +9 -1
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +0 -1
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +14 -0
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +66 -1
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +123 -3
- package/external/pi-tools-suite/src/context-gateway/index.ts +137 -4
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +5 -5
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +2 -2
- package/external/pi-tools-suite/src/context-gateway/types.ts +8 -1
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +41 -14
- package/external/pi-tools-suite/src/dcp/commands.ts +14 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +8 -2
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +58 -4
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
- package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
- package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
- package/external/pi-tools-suite/src/dcp/index.ts +338 -66
- package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +92 -2
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
- package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -0
- package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
- package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +68 -16
- package/external/pi-tools-suite/src/lib/project.ts +36 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
- package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
- package/external/pi-tools-suite/src/resource-registry/index.ts +282 -21
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/todo/index.ts +21 -0
- package/external/pi-tools-suite/src/todo/todo.ts +19 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +44 -15
- package/package.json +7 -8
- package/schemas/pi-tools-suite.json +44 -12
- package/schemas/pix.json +109 -15
- package/schemas/tasks.json +1 -2
- package/skills/simplify/SKILL.md +33 -5
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -196
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- package/skills/playwright-cli/SKILL.md +0 -420
- package/skills/playwright-cli/references/element-attributes.md +0 -23
- package/skills/playwright-cli/references/playwright-tests.md +0 -50
- package/skills/playwright-cli/references/request-mocking.md +0 -87
- package/skills/playwright-cli/references/running-code.md +0 -241
- package/skills/playwright-cli/references/session-management.md +0 -273
- package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
- package/skills/playwright-cli/references/storage-state.md +0 -290
- package/skills/playwright-cli/references/test-generation.md +0 -142
- package/skills/playwright-cli/references/tracing.md +0 -154
- package/skills/playwright-cli/references/video-recording.md +0 -147
- package/skills/spec-wiki/SKILL.md +0 -55
- package/skills/spec-wiki/evals/evals.json +0 -132
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +0 -14
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +0 -4
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +0 -9
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +0 -17
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +0 -12
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +0 -11
- package/skills/spec-wiki/evals/files/focused/src/session.ts +0 -5
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +0 -17
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +0 -2
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +0 -18
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +0 -5
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +0 -24
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +0 -17
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +0 -3
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +0 -4
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +0 -9
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +0 -15
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +0 -6
- package/skills/spec-wiki/evals/maintenance_regression.py +0 -311
- package/skills/spec-wiki/evals/retrieval-eval.md +0 -37
- package/skills/spec-wiki/evals/retrieval-evals.json +0 -37
- package/skills/spec-wiki/evals/trigger-evals.json +0 -94
- package/skills/spec-wiki/references/bootstrap.md +0 -100
- package/skills/spec-wiki/references/focused-spec.md +0 -117
- package/skills/spec-wiki/references/index-schema.md +0 -330
- package/skills/spec-wiki/references/maintenance.md +0 -134
- package/skills/spec-wiki/references/query.md +0 -74
- package/skills/spec-wiki/scripts/spec_wiki.py +0 -2076
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
- /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()
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|