@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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 +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { type AgentMessage, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
4
|
-
import type
|
|
4
|
+
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@sayknow-cli/tui";
|
|
5
5
|
import { $env, sanitizeText } from "@sayknow-cli/utils";
|
|
6
6
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
7
|
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/skc-plugins";
|
|
@@ -9,6 +9,7 @@ import { buildSkillPromptMessage, parseSkillInvocations } from "../../extensibil
|
|
|
9
9
|
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
10
10
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
11
11
|
import { theme } from "../../modes/theme/theme";
|
|
12
|
+
import { scrollTmuxToPreviousUserInput as scrollTmuxPaneToPreviousUserInput } from "../../modes/tmux-scroll";
|
|
12
13
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
13
14
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
14
15
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
@@ -41,6 +42,127 @@ export class InputController {
|
|
|
41
42
|
* so abort cleanup going idle cannot turn the second Esc into an idle action. */
|
|
42
43
|
#steerConsumePending = false;
|
|
43
44
|
|
|
45
|
+
#globalInterruptUnsubscribe: (() => void) | undefined;
|
|
46
|
+
|
|
47
|
+
#matchesInterruptKey(data: string): boolean {
|
|
48
|
+
return this.ctx.keybindings.getKeys("app.interrupt").some(key => matchesKey(data, key));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#hasHookDialog(): boolean {
|
|
52
|
+
return Boolean(this.ctx.hookSelector || this.ctx.hookInput || this.ctx.hookEditor);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#isRetryBackoffActive(): boolean {
|
|
56
|
+
return Boolean(
|
|
57
|
+
this.ctx.retryLoader ||
|
|
58
|
+
this.ctx.retryEscapeHandler ||
|
|
59
|
+
(this.ctx.session.isRetrying && !this.ctx.session.isStreaming),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#handleCancellableWorkEscape(options: {
|
|
64
|
+
loading?: boolean;
|
|
65
|
+
processes?: boolean;
|
|
66
|
+
modes?: boolean;
|
|
67
|
+
maintenance?: boolean;
|
|
68
|
+
retry?: boolean;
|
|
69
|
+
streaming?: boolean;
|
|
70
|
+
}): boolean {
|
|
71
|
+
if (options.loading && this.ctx.loadingAnimation) {
|
|
72
|
+
if (this.ctx.cancelPendingSubmission()) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (options.processes && this.ctx.session.isBashRunning) {
|
|
79
|
+
this.ctx.session.abortBash();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (options.modes && this.ctx.isBashMode) {
|
|
83
|
+
this.ctx.editor.setText("");
|
|
84
|
+
this.ctx.isBashMode = false;
|
|
85
|
+
this.ctx.isBashNoContext = false;
|
|
86
|
+
this.ctx.updateEditorBorderColor();
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (options.processes && this.ctx.session.isEvalRunning) {
|
|
90
|
+
this.ctx.session.abortEval();
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (options.modes && this.ctx.isPythonMode) {
|
|
94
|
+
this.ctx.editor.setText("");
|
|
95
|
+
this.ctx.isPythonMode = false;
|
|
96
|
+
this.ctx.updateEditorBorderColor();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
options.maintenance &&
|
|
101
|
+
(this.ctx.session.isCompacting || this.ctx.autoCompactionLoader || this.ctx.autoCompactionEscapeHandler)
|
|
102
|
+
) {
|
|
103
|
+
this.ctx.session.abortCompaction();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (options.maintenance && this.ctx.session.isGeneratingHandoff) {
|
|
107
|
+
this.ctx.session.abortHandoff();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (options.retry) {
|
|
111
|
+
if (this.#isRetryBackoffActive()) {
|
|
112
|
+
if (this.ctx.retryEscapePrimed) {
|
|
113
|
+
this.ctx.session.abortRetry();
|
|
114
|
+
} else {
|
|
115
|
+
this.ctx.retryEscapePrimed = true;
|
|
116
|
+
this.ctx.session.retryNow();
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
this.ctx.retryEscapePrimed = false;
|
|
121
|
+
}
|
|
122
|
+
if (options.streaming && this.ctx.session.isStreaming) {
|
|
123
|
+
if (this.ctx.session.hasQueuedSteering && !this.#steerConsumePending) {
|
|
124
|
+
// First Esc with a queued steer: silently consume it and
|
|
125
|
+
// auto-continue via steer-on-interrupt instead of stalling on
|
|
126
|
+
// "Operation aborted".
|
|
127
|
+
this.#steerConsumePending = true;
|
|
128
|
+
void this.#abortInteractive({ silent: true });
|
|
129
|
+
} else {
|
|
130
|
+
void this.#abortInteractive();
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#installGlobalInterruptListener(): void {
|
|
138
|
+
if (typeof this.ctx.ui.addInputListener !== "function") {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.#globalInterruptUnsubscribe?.();
|
|
142
|
+
this.#globalInterruptUnsubscribe = this.ctx.ui.addInputListener(data => {
|
|
143
|
+
if (!this.#matchesInterruptKey(data)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
147
|
+
return { consume: true };
|
|
148
|
+
}
|
|
149
|
+
const hookDialogActive = this.#hasHookDialog();
|
|
150
|
+
if (
|
|
151
|
+
this.#handleCancellableWorkEscape({
|
|
152
|
+
loading: hookDialogActive,
|
|
153
|
+
processes: hookDialogActive,
|
|
154
|
+
modes: false,
|
|
155
|
+
maintenance: true,
|
|
156
|
+
retry: true,
|
|
157
|
+
streaming: hookDialogActive,
|
|
158
|
+
})
|
|
159
|
+
) {
|
|
160
|
+
return { consume: true };
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
44
166
|
#abortInteractive(options?: { silent?: boolean }): Promise<void> {
|
|
45
167
|
return this.ctx.session.abort({
|
|
46
168
|
timeoutMs: INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS,
|
|
@@ -59,6 +181,7 @@ export class InputController {
|
|
|
59
181
|
this.ctx.session.isStreaming ||
|
|
60
182
|
this.ctx.session.isCompacting ||
|
|
61
183
|
this.ctx.session.isGeneratingHandoff ||
|
|
184
|
+
this.ctx.session.isRetrying ||
|
|
62
185
|
this.ctx.session.isBashRunning ||
|
|
63
186
|
this.ctx.session.isEvalRunning ||
|
|
64
187
|
this.ctx.autoCompactionLoader ||
|
|
@@ -66,6 +189,8 @@ export class InputController {
|
|
|
66
189
|
this.ctx.autoCompactionEscapeHandler ||
|
|
67
190
|
this.ctx.retryEscapeHandler,
|
|
68
191
|
);
|
|
192
|
+
this.#installGlobalInterruptListener();
|
|
193
|
+
|
|
69
194
|
// An open btw panel must stay dismissable with Esc even while another
|
|
70
195
|
// controller (auto-compaction, auto-retry, manual compaction, etc.) has
|
|
71
196
|
// temporarily replaced editor.onEscape. This priority hook is never
|
|
@@ -86,6 +211,9 @@ export class InputController {
|
|
|
86
211
|
}
|
|
87
212
|
this.#steerConsumePending = false;
|
|
88
213
|
}
|
|
214
|
+
if (this.#handleCancellableWorkEscape({ maintenance: true, retry: true })) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
89
217
|
// Normal input state with user-typed text: Esc must not interrupt a
|
|
90
218
|
// running task (streaming turn, bash/eval). A double Esc within the
|
|
91
219
|
// 500ms window clears the composer instead. Bash/Python input modes
|
|
@@ -100,35 +228,19 @@ export class InputController {
|
|
|
100
228
|
}
|
|
101
229
|
return;
|
|
102
230
|
}
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
this.ctx.session.abortEval();
|
|
117
|
-
} else if (this.ctx.isPythonMode) {
|
|
118
|
-
this.ctx.editor.setText("");
|
|
119
|
-
this.ctx.isPythonMode = false;
|
|
120
|
-
this.ctx.updateEditorBorderColor();
|
|
121
|
-
} else if (this.ctx.session.isStreaming) {
|
|
122
|
-
if (this.ctx.session.hasQueuedSteering && !this.#steerConsumePending) {
|
|
123
|
-
// First Esc with a queued steer: silently consume it and
|
|
124
|
-
// auto-continue via steer-on-interrupt instead of stalling on
|
|
125
|
-
// "Operation aborted".
|
|
126
|
-
this.#steerConsumePending = true;
|
|
127
|
-
void this.#abortInteractive({ silent: true });
|
|
128
|
-
} else {
|
|
129
|
-
void this.#abortInteractive();
|
|
130
|
-
}
|
|
131
|
-
} else if (!this.ctx.editor.getText().trim()) {
|
|
231
|
+
if (
|
|
232
|
+
this.#handleCancellableWorkEscape({
|
|
233
|
+
loading: true,
|
|
234
|
+
processes: true,
|
|
235
|
+
modes: true,
|
|
236
|
+
maintenance: true,
|
|
237
|
+
retry: true,
|
|
238
|
+
streaming: true,
|
|
239
|
+
})
|
|
240
|
+
) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!this.ctx.editor.getText().trim()) {
|
|
132
244
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
133
245
|
const action = settings.get("doubleEscapeAction");
|
|
134
246
|
if (action !== "none") {
|
|
@@ -155,6 +267,8 @@ export class InputController {
|
|
|
155
267
|
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
156
268
|
this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
|
|
157
269
|
this.ctx.editor.onCycleThinkingLevel = () => this.cycleThinkingLevel();
|
|
270
|
+
this.ctx.editor.setActionKeys("app.commandPalette.open", this.ctx.keybindings.getKeys("app.commandPalette.open"));
|
|
271
|
+
this.ctx.editor.onOpenCommandPalette = () => this.openCommandPalette();
|
|
158
272
|
this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
|
|
159
273
|
this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel();
|
|
160
274
|
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
@@ -199,8 +313,17 @@ export class InputController {
|
|
|
199
313
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
200
314
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
201
315
|
this.ctx.editor.onQueue = () => void this.handleQueueSubmit();
|
|
316
|
+
this.ctx.editor.onTab = () => {
|
|
317
|
+
if (!this.ctx.session.isStreaming && !this.ctx.session.isCompacting) return false;
|
|
318
|
+
void this.handleQueueSubmit();
|
|
319
|
+
return true;
|
|
320
|
+
};
|
|
202
321
|
this.ctx.editor.onTabDeclined = () => {
|
|
203
|
-
if (this.ctx.session.isStreaming) void this.handleQueueSubmit();
|
|
322
|
+
if (this.ctx.session.isStreaming || this.ctx.session.isCompacting) void this.handleQueueSubmit();
|
|
323
|
+
};
|
|
324
|
+
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
325
|
+
this.ctx.editor.onViewportFollowLive = () => {
|
|
326
|
+
this.ctx.ui.followLiveViewport();
|
|
204
327
|
};
|
|
205
328
|
|
|
206
329
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
@@ -491,7 +614,7 @@ export class InputController {
|
|
|
491
614
|
}
|
|
492
615
|
|
|
493
616
|
handleDequeue(): void {
|
|
494
|
-
const restored = this.
|
|
617
|
+
const restored = this.restoreLatestQueuedMessageToEditor();
|
|
495
618
|
if (restored === 0) {
|
|
496
619
|
this.ctx.showStatus("No queued messages to restore");
|
|
497
620
|
} else {
|
|
@@ -521,19 +644,18 @@ export class InputController {
|
|
|
521
644
|
|
|
522
645
|
/**
|
|
523
646
|
* Dispatch skill slash invocation(s) (`/skill:<name>`) through custom messages
|
|
524
|
-
* using the supplied `streamingBehavior`. Returns true if the text
|
|
525
|
-
* recognised skill command chain and was dispatched. A
|
|
526
|
-
* file is surfaced via `showError` but still returns
|
|
527
|
-
* already cleared on the success path, so falling
|
|
528
|
-
* handling at that point would double-submit. Returns
|
|
529
|
-
*
|
|
530
|
-
*
|
|
647
|
+
* using the supplied `streamingBehavior`. Returns true if the text contains a
|
|
648
|
+
* recognised canonical skill command or command chain and was dispatched. A
|
|
649
|
+
* failure to load a skill file is surfaced via `showError` but still returns
|
|
650
|
+
* true — the editor was already cleared on the success path, so falling
|
|
651
|
+
* through to plain-text handling at that point would double-submit. Returns
|
|
652
|
+
* false when the text has no registered canonical skill invocation, so the
|
|
653
|
+
* caller can fall through to plain-text handling (this branch
|
|
531
654
|
* leaves the editor state untouched). `streamingBehavior` is only consulted
|
|
532
655
|
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
533
656
|
* ignores it.
|
|
534
657
|
*/
|
|
535
658
|
async #invokeSkillCommand(text: string, streamingBehavior: "steer" | "followUp"): Promise<boolean> {
|
|
536
|
-
if (!text.startsWith("/")) return false;
|
|
537
659
|
const invocations = parseSkillInvocations(text, this.ctx.skillCommands ?? new Map());
|
|
538
660
|
if (invocations.length === 0) return false;
|
|
539
661
|
this.ctx.editor.addToHistory(text);
|
|
@@ -602,13 +724,15 @@ export class InputController {
|
|
|
602
724
|
const text = this.ctx.editor.getText().trim();
|
|
603
725
|
if (!text) return;
|
|
604
726
|
|
|
605
|
-
// Compaction first: while compacting, free text
|
|
606
|
-
//
|
|
607
|
-
// skill
|
|
608
|
-
//
|
|
609
|
-
// the queued entry is later re-parsed into a skill invocation is a
|
|
610
|
-
// separate concern owned by the compaction-resume path.
|
|
727
|
+
// Compaction first: while compacting, queue free text and `/skill:*`
|
|
728
|
+
// commands in the compaction-local queue. `flushCompactionQueue`
|
|
729
|
+
// replays skill entries through the custom-message skill path after
|
|
730
|
+
// compaction finishes so they are not degraded into plain prompts.
|
|
611
731
|
if (this.ctx.session.isCompacting) {
|
|
732
|
+
if (this.ctx.pendingImages.length > 0) {
|
|
733
|
+
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
612
736
|
this.ctx.queueCompactionMessage(text, "followUp");
|
|
613
737
|
return;
|
|
614
738
|
}
|
|
@@ -642,10 +766,28 @@ export class InputController {
|
|
|
642
766
|
return this.handleFollowUp();
|
|
643
767
|
}
|
|
644
768
|
|
|
769
|
+
restoreLatestQueuedMessageToEditor(options?: { currentText?: string }): number {
|
|
770
|
+
const compactionQueued = this.ctx.compactionQueuedMessages.pop();
|
|
771
|
+
const queuedText = compactionQueued?.text ?? this.ctx.session.popLastQueuedMessage();
|
|
772
|
+
if (!queuedText) {
|
|
773
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
774
|
+
return 0;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
778
|
+
const currentText = options?.currentText ?? this.ctx.editor.getText();
|
|
779
|
+
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
780
|
+
this.ctx.editor.setText(combinedText);
|
|
781
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
782
|
+
return 1;
|
|
783
|
+
}
|
|
784
|
+
|
|
645
785
|
restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {
|
|
646
786
|
this.ctx.locallySubmittedUserSignatures.clear();
|
|
647
787
|
const { steering, followUp } = this.ctx.session.clearQueue();
|
|
648
|
-
const
|
|
788
|
+
const compactionQueued = (this.ctx.compactionQueuedMessages ?? []).map(entry => entry.text);
|
|
789
|
+
this.ctx.compactionQueuedMessages = [];
|
|
790
|
+
const allQueued = [...steering, ...followUp, ...compactionQueued];
|
|
649
791
|
if (allQueued.length === 0) {
|
|
650
792
|
this.ctx.updatePendingMessagesDisplay();
|
|
651
793
|
if (options?.abort) {
|
|
@@ -704,6 +846,7 @@ export class InputController {
|
|
|
704
846
|
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
705
847
|
this.ctx.retryEscapeHandler = undefined;
|
|
706
848
|
}
|
|
849
|
+
this.ctx.retryEscapePrimed = false;
|
|
707
850
|
this.ctx.statusContainer.clear();
|
|
708
851
|
this.ctx.statusLine.dispose();
|
|
709
852
|
|
|
@@ -858,6 +1001,9 @@ export class InputController {
|
|
|
858
1001
|
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
859
1002
|
copyPrompt: () => this.handleCopyPrompt(),
|
|
860
1003
|
pasteImage: () => void this.handleImagePaste(),
|
|
1004
|
+
newSession: () => void this.ctx.handleClearCommand(),
|
|
1005
|
+
showHelp: () => this.ctx.handleHelpCommand(),
|
|
1006
|
+
scrollTmuxToPreviousUserInput: () => this.scrollTmuxToPreviousUserInput(),
|
|
861
1007
|
undo: prefix => this.ctx.editor.undoPastTransientText(prefix),
|
|
862
1008
|
moveCursorToMessageEnd: () => this.ctx.editor.moveToMessageEnd(),
|
|
863
1009
|
moveCursorToMessageStart: () => this.ctx.editor.moveToMessageStart(),
|
|
@@ -901,6 +1047,14 @@ export class InputController {
|
|
|
901
1047
|
}
|
|
902
1048
|
}
|
|
903
1049
|
|
|
1050
|
+
openCommandPalette(): void {
|
|
1051
|
+
if (this.ctx.editor.getText().trim().length > 0) {
|
|
1052
|
+
this.ctx.showStatus("Command palette opens from an empty prompt. Type / for inline commands.");
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
this.ctx.editor.handleInput("/");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
904
1058
|
cycleThinkingLevel(): void {
|
|
905
1059
|
const newLevel = this.ctx.session.cycleThinkingLevel();
|
|
906
1060
|
if (newLevel === undefined) {
|
|
@@ -952,6 +1106,19 @@ export class InputController {
|
|
|
952
1106
|
this.setToolsExpanded(!this.ctx.toolOutputExpanded);
|
|
953
1107
|
}
|
|
954
1108
|
|
|
1109
|
+
scrollTmuxToPreviousUserInput(): void {
|
|
1110
|
+
const result = scrollTmuxPaneToPreviousUserInput();
|
|
1111
|
+
if (result.ok) return;
|
|
1112
|
+
|
|
1113
|
+
if (result.reason === "not_inside_tmux") {
|
|
1114
|
+
this.ctx.showWarning("Previous-input scroll works only inside tmux.");
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const detail = result.error ? `: ${result.error}` : ".";
|
|
1119
|
+
this.ctx.showWarning(`Failed to scroll tmux to previous user input${detail}`);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
955
1122
|
setToolsExpanded(expanded: boolean): void {
|
|
956
1123
|
this.ctx.toolOutputExpanded = expanded;
|
|
957
1124
|
for (const child of this.ctx.chatContainer.children) {
|
|
@@ -8,7 +8,8 @@ import { activateModelProfile, materializeActiveModelProfileAssignment } from ".
|
|
|
8
8
|
import { recommendModelProfileForProvider } from "../../config/model-profiles";
|
|
9
9
|
import { SKC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
10
10
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
11
|
-
import {
|
|
11
|
+
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
12
|
+
import { type Settings, settings } from "../../config/settings";
|
|
12
13
|
import { DebugSelectorComponent } from "../../debug";
|
|
13
14
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
14
15
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -74,6 +75,7 @@ import {
|
|
|
74
75
|
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
75
76
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
76
77
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
78
|
+
import type { StatusLineSettings } from "../components/status-line";
|
|
77
79
|
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
78
80
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
79
81
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
@@ -97,6 +99,24 @@ function isThemePreviewSuperseded(result: { success: boolean; error?: string }):
|
|
|
97
99
|
return !result.success && result.error?.includes("superseded by a newer request") === true;
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Snapshot the persisted status-line settings that the status-line component
|
|
104
|
+
* cares about. Preview, cancel-restore, and commit paths all share this so the
|
|
105
|
+
* previewed row count (and every other field) can never drift out of sync.
|
|
106
|
+
*/
|
|
107
|
+
export function buildStatusLineSettings(settingsInstance: Settings): StatusLineSettings {
|
|
108
|
+
return {
|
|
109
|
+
preset: settingsInstance.get("statusLine.preset"),
|
|
110
|
+
leftSegments: settingsInstance.get("statusLine.leftSegments"),
|
|
111
|
+
rightSegments: settingsInstance.get("statusLine.rightSegments"),
|
|
112
|
+
separator: settingsInstance.get("statusLine.separator"),
|
|
113
|
+
showHookStatus: settingsInstance.get("statusLine.showHookStatus"),
|
|
114
|
+
sessionAccent: settingsInstance.get("statusLine.sessionAccent"),
|
|
115
|
+
maxRows: settingsInstance.get("statusLine.maxRows"),
|
|
116
|
+
segmentOptions: settingsInstance.get("statusLine.segmentOptions"),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
function formatProviderOnboardingCommandGuide(): string {
|
|
101
121
|
return [
|
|
102
122
|
"Provider preset setup:",
|
|
@@ -243,7 +263,7 @@ export class SelectorController {
|
|
|
243
263
|
this.ctx.ui.requestRender();
|
|
244
264
|
}
|
|
245
265
|
|
|
246
|
-
showCustomModelPresetWizard(): void {
|
|
266
|
+
showCustomModelPresetWizard(snapshot: ModelProfileConfig): void {
|
|
247
267
|
this.showSelector(done => {
|
|
248
268
|
let wizard: CustomModelPresetWizardComponent;
|
|
249
269
|
const submit = async (input: CustomModelPresetWizardSubmit): Promise<void> => {
|
|
@@ -260,6 +280,7 @@ export class SelectorController {
|
|
|
260
280
|
}
|
|
261
281
|
};
|
|
262
282
|
wizard = new CustomModelPresetWizardComponent(
|
|
283
|
+
snapshot,
|
|
263
284
|
input => {
|
|
264
285
|
void submit(input);
|
|
265
286
|
},
|
|
@@ -334,21 +355,17 @@ export class SelectorController {
|
|
|
334
355
|
onStatusLinePreview: previewSettings => {
|
|
335
356
|
// Update status line with preview settings
|
|
336
357
|
this.ctx.statusLine.updateSettings({
|
|
337
|
-
|
|
338
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
339
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
340
|
-
separator: settings.get("statusLine.separator"),
|
|
341
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
342
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
358
|
+
...buildStatusLineSettings(settings),
|
|
343
359
|
...previewSettings,
|
|
344
360
|
});
|
|
345
361
|
this.ctx.updateEditorTopBorder();
|
|
346
362
|
this.ctx.ui.requestRender();
|
|
347
363
|
},
|
|
348
|
-
getStatusLinePreview: () => {
|
|
364
|
+
getStatusLinePreview: (width?: number) => {
|
|
349
365
|
// Return the rendered status line for inline preview
|
|
350
|
-
const availableWidth =
|
|
351
|
-
|
|
366
|
+
const availableWidth =
|
|
367
|
+
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
368
|
+
return this.ctx.statusLine.getPreviewContent(availableWidth);
|
|
352
369
|
},
|
|
353
370
|
onPluginsChanged: () => {
|
|
354
371
|
this.ctx.ui.requestRender();
|
|
@@ -356,14 +373,7 @@ export class SelectorController {
|
|
|
356
373
|
onCancel: () => {
|
|
357
374
|
done();
|
|
358
375
|
// Restore status line to saved settings
|
|
359
|
-
this.ctx.statusLine.updateSettings(
|
|
360
|
-
preset: settings.get("statusLine.preset"),
|
|
361
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
362
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
363
|
-
separator: settings.get("statusLine.separator"),
|
|
364
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
365
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
366
|
-
});
|
|
376
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
367
377
|
this.ctx.updateEditorTopBorder();
|
|
368
378
|
this.ctx.ui.requestRender();
|
|
369
379
|
},
|
|
@@ -607,6 +617,10 @@ export class SelectorController {
|
|
|
607
617
|
case "statusLineShowHooks":
|
|
608
618
|
case "statusLine.showHookStatus":
|
|
609
619
|
case "statusLine.sessionAccent":
|
|
620
|
+
case "statusLine.maxRows":
|
|
621
|
+
case "statusLine.leftSegments":
|
|
622
|
+
case "statusLine.rightSegments":
|
|
623
|
+
case "statusLine.segmentOptions":
|
|
610
624
|
case "statusLineSegments":
|
|
611
625
|
case "statusLineModelThinking":
|
|
612
626
|
case "statusLinePathAbbreviate":
|
|
@@ -618,16 +632,7 @@ export class SelectorController {
|
|
|
618
632
|
case "statusLineGitShowUntracked":
|
|
619
633
|
case "statusLineTimeFormat":
|
|
620
634
|
case "statusLineTimeShowSeconds": {
|
|
621
|
-
|
|
622
|
-
preset: settings.get("statusLine.preset"),
|
|
623
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
624
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
625
|
-
separator: settings.get("statusLine.separator"),
|
|
626
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
627
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
628
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
629
|
-
};
|
|
630
|
-
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
635
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
631
636
|
this.ctx.updateEditorTopBorder();
|
|
632
637
|
this.ctx.ui.requestRender();
|
|
633
638
|
break;
|
|
@@ -685,9 +690,6 @@ export class SelectorController {
|
|
|
685
690
|
async selection => {
|
|
686
691
|
try {
|
|
687
692
|
if (selection.kind === "login") {
|
|
688
|
-
// User picked an unauthenticated preset/model. Close the model
|
|
689
|
-
// selector and start OAuth for the provider if it supports it;
|
|
690
|
-
// otherwise guide them to the right command.
|
|
691
693
|
const oauth = getOAuthProviders().find(p => p.id === selection.providerId);
|
|
692
694
|
done();
|
|
693
695
|
if (oauth) {
|
|
@@ -702,7 +704,7 @@ export class SelectorController {
|
|
|
702
704
|
}
|
|
703
705
|
if (selection.kind === "createProfile") {
|
|
704
706
|
done();
|
|
705
|
-
this.showCustomModelPresetWizard();
|
|
707
|
+
this.showCustomModelPresetWizard(selection.profile);
|
|
706
708
|
return;
|
|
707
709
|
}
|
|
708
710
|
if (selection.kind === "profile") {
|
|
@@ -72,7 +72,6 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
72
72
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
73
73
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
74
74
|
import { ToolError } from "../tools/tool-errors";
|
|
75
|
-
|
|
76
75
|
import type { EventBus } from "../utils/event-bus";
|
|
77
76
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
78
77
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
@@ -104,6 +103,7 @@ import { JobsObserver } from "./jobs-observer";
|
|
|
104
103
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
105
104
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
106
105
|
import { interruptHint } from "./shared";
|
|
106
|
+
import { shouldShowExtensionCommand } from "./slash-command-visibility";
|
|
107
107
|
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
108
108
|
import type { Theme } from "./theme/theme";
|
|
109
109
|
import {
|
|
@@ -322,6 +322,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
322
322
|
}
|
|
323
323
|
autoCompactionEscapeHandler?: () => void;
|
|
324
324
|
retryEscapeHandler?: () => void;
|
|
325
|
+
retryEscapePrimed = false;
|
|
325
326
|
retryCountdownTimer?: NodeJS.Timeout;
|
|
326
327
|
unsubscribe?: () => void;
|
|
327
328
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
@@ -423,7 +424,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
423
424
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
424
425
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
425
426
|
this.editor.onAutocompleteCancel = () => {
|
|
426
|
-
this.ui.requestRender(
|
|
427
|
+
this.ui.requestRender();
|
|
427
428
|
};
|
|
428
429
|
this.editor.onAutocompleteUpdate = () => {
|
|
429
430
|
this.ui.requestRender();
|
|
@@ -433,7 +434,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
433
434
|
this.#syncEditorMaxHeight();
|
|
434
435
|
this.updateEditorChrome();
|
|
435
436
|
this.editor.invalidate();
|
|
436
|
-
this.ui.
|
|
437
|
+
this.ui.requestResizeRender();
|
|
437
438
|
};
|
|
438
439
|
process.stdout.on("resize", this.#resizeHandler);
|
|
439
440
|
try {
|
|
@@ -453,13 +454,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
453
454
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
454
455
|
|
|
455
456
|
const builtinCommandNames = new Set(BUILTIN_SLASH_COMMANDS.map(c => c.name));
|
|
457
|
+
const activeProvider = this.session.model?.provider;
|
|
456
458
|
const hookCommands: SlashCommand[] = (
|
|
457
459
|
this.session.extensionRunner?.getRegisteredCommands(builtinCommandNames) ?? []
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
460
|
+
)
|
|
461
|
+
.filter(cmd => shouldShowExtensionCommand(cmd.name, activeProvider))
|
|
462
|
+
.map(cmd => ({
|
|
463
|
+
name: cmd.name,
|
|
464
|
+
description: cmd.description ?? "(hook command)",
|
|
465
|
+
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
466
|
+
}));
|
|
463
467
|
|
|
464
468
|
// Convert custom commands (TypeScript) to SlashCommand format
|
|
465
469
|
const customCommands: SlashCommand[] = this.session.customCommands.map(loaded => ({
|
|
@@ -528,6 +532,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
528
532
|
}
|
|
529
533
|
|
|
530
534
|
if (!startupQuiet) {
|
|
535
|
+
const getWelcomeReservedBottomRows = (width: number): number =>
|
|
536
|
+
[
|
|
537
|
+
this.statusLine,
|
|
538
|
+
this.hookWidgetContainerAbove,
|
|
539
|
+
this.editorContainer,
|
|
540
|
+
this.hookWidgetContainerBelow,
|
|
541
|
+
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
542
|
+
|
|
531
543
|
// Add welcome header
|
|
532
544
|
this.#welcomeComponent = new WelcomeComponent(
|
|
533
545
|
this.#version,
|
|
@@ -536,30 +548,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
536
548
|
recentSessions,
|
|
537
549
|
this.#getWelcomeLspServers(),
|
|
538
550
|
welcomeLogoMode,
|
|
551
|
+
{
|
|
552
|
+
getViewportRows: () => this.ui.terminal.rows,
|
|
553
|
+
getReservedBottomRows: getWelcomeReservedBottomRows,
|
|
554
|
+
changelogMarkdown: this.#changelogMarkdown,
|
|
555
|
+
collapseChangelog: settings.get("collapseChangelog"),
|
|
556
|
+
},
|
|
539
557
|
);
|
|
540
558
|
|
|
541
|
-
// Setup UI layout
|
|
542
|
-
this.ui.addChild(new Spacer(1));
|
|
543
559
|
this.ui.addChild(this.#welcomeComponent);
|
|
544
|
-
this.ui.addChild(new Spacer(1));
|
|
545
560
|
this.#welcomeComponent.playIntro(() => this.ui.requestRender());
|
|
546
|
-
|
|
547
|
-
// Add changelog if provided
|
|
548
|
-
if (this.#changelogMarkdown) {
|
|
549
|
-
this.ui.addChild(new DynamicBorder());
|
|
550
|
-
if (settings.get("collapseChangelog")) {
|
|
551
|
-
const versionMatch = this.#changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
552
|
-
const latestVersion = versionMatch ? versionMatch[1] : this.#version;
|
|
553
|
-
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
554
|
-
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
555
|
-
} else {
|
|
556
|
-
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
557
|
-
this.ui.addChild(new Spacer(1));
|
|
558
|
-
this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
559
|
-
this.ui.addChild(new Spacer(1));
|
|
560
|
-
}
|
|
561
|
-
this.ui.addChild(new DynamicBorder());
|
|
562
|
-
}
|
|
563
561
|
}
|
|
564
562
|
|
|
565
563
|
this.ui.addChild(this.chatContainer);
|
|
@@ -1961,6 +1959,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1961
1959
|
this.loadingAnimation.stop();
|
|
1962
1960
|
this.loadingAnimation = undefined;
|
|
1963
1961
|
}
|
|
1962
|
+
this.#welcomeComponent?.dispose();
|
|
1963
|
+
this.#welcomeComponent = undefined;
|
|
1964
1964
|
this.#cleanupMicAnimation();
|
|
1965
1965
|
this.#cancelGoalContinuation();
|
|
1966
1966
|
if (this.#sttController) {
|
|
@@ -2036,7 +2036,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2036
2036
|
const sessionId = this.sessionManager.getSessionId();
|
|
2037
2037
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
2038
2038
|
if (sessionId && sessionFile) {
|
|
2039
|
-
process.stderr.write(
|
|
2039
|
+
process.stderr.write(
|
|
2040
|
+
`\n${chalk.dim("Resume this session with:")}\n${chalk.dim(`${APP_NAME} --resume ${sessionId}`)}\n`,
|
|
2041
|
+
);
|
|
2040
2042
|
}
|
|
2041
2043
|
|
|
2042
2044
|
await postmortem.quit(0);
|
|
@@ -2072,7 +2074,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2072
2074
|
nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
2073
2075
|
nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible"));
|
|
2074
2076
|
nextEditor.onAutocompleteCancel = () => {
|
|
2075
|
-
this.ui.requestRender(
|
|
2077
|
+
this.ui.requestRender();
|
|
2076
2078
|
};
|
|
2077
2079
|
nextEditor.onAutocompleteUpdate = () => {
|
|
2078
2080
|
this.ui.requestRender();
|
|
@@ -2312,6 +2314,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2312
2314
|
this.#commandController.handleHotkeysCommand();
|
|
2313
2315
|
}
|
|
2314
2316
|
|
|
2317
|
+
handleHelpCommand(): void {
|
|
2318
|
+
this.#commandController.handleHelpCommand();
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2315
2321
|
handleToolsCommand(): void {
|
|
2316
2322
|
this.#commandController.handleToolsCommand();
|
|
2317
2323
|
}
|