@gajae-code/coding-agent 0.7.10 → 0.8.0
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/CHANGELOG.md +58 -0
- package/README.md +2 -2
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/config/keybindings.d.ts +11 -5
- package/dist/types/config/settings-schema.d.ts +25 -2
- package/dist/types/defaults/gjc-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/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line.d.ts +8 -0
- 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 +5 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/types.d.ts +2 -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/session/agent-session.d.ts +8 -5
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/utils/cmux-workspace.d.ts +30 -1
- package/dist/types/web/search/index.d.ts +3 -1
- package/dist/types/web/search/provider.d.ts +9 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +10 -10
- package/src/capability/mcp.ts +2 -0
- package/src/cli/update-cli.ts +24 -0
- package/src/cli/web-search-cli.ts +2 -5
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/config/keybindings.ts +14 -5
- package/src/config/mcp-schema.json +5 -1
- package/src/config/model-profiles.ts +16 -0
- package/src/config/settings-schema.ts +18 -2
- package/src/coordinator-mcp/server.ts +15 -3
- package/src/defaults/gjc/skills/ralplan/SKILL.md +16 -14
- package/src/defaults/gjc-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/gjc-runtime/launch-tmux.ts +164 -42
- package/src/gjc-runtime/ralplan-runtime.ts +22 -6
- package/src/gjc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/gjc-runtime/session-state-sidecar.ts +167 -24
- package/src/gjc-runtime/state-runtime.ts +8 -5
- package/src/gjc-runtime/state-writer.ts +5 -5
- package/src/gjc-runtime/team-runtime.ts +56 -51
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/main.ts +2 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/components/agent-dashboard.ts +1 -34
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/model-selector.ts +64 -4
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +13 -2
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line.ts +215 -86
- 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 +171 -35
- package/src/modes/controllers/command-controller.ts +23 -0
- package/src/modes/controllers/event-controller.ts +4 -0
- package/src/modes/controllers/input-controller.ts +192 -39
- package/src/modes/controllers/selector-controller.ts +25 -28
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +23 -1
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +9 -3
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/index.ts +20 -1
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/telegram-daemon.ts +4 -4
- 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 +15 -1
- 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 +7 -6
- package/src/session/agent-session.ts +81 -18
- package/src/session/session-manager.ts +58 -18
- package/src/slash-commands/builtin-registry.ts +30 -10
- 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/utils/cmux-workspace.ts +134 -7
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +118 -45
- package/src/web/search/provider.ts +65 -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/workflow/workflow-intent-diff.ts +199 -0
- package/dist/types/discovery/vscode.d.ts +0 -1
- package/src/discovery/vscode.ts +0 -105
|
@@ -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 "@gajae-code/agent-core";
|
|
4
|
-
import type
|
|
4
|
+
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@gajae-code/tui";
|
|
5
5
|
import { $env, sanitizeText } from "@gajae-code/utils";
|
|
6
6
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
7
|
import { resolveSubskillActivationForSkillInvocation } from "../../extensibility/gjc-plugins";
|
|
@@ -42,6 +42,127 @@ export class InputController {
|
|
|
42
42
|
* so abort cleanup going idle cannot turn the second Esc into an idle action. */
|
|
43
43
|
#steerConsumePending = false;
|
|
44
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
|
+
|
|
45
166
|
#abortInteractive(options?: { silent?: boolean }): Promise<void> {
|
|
46
167
|
return this.ctx.session.abort({
|
|
47
168
|
timeoutMs: INTERACTIVE_ABORT_CLEANUP_TIMEOUT_MS,
|
|
@@ -60,6 +181,7 @@ export class InputController {
|
|
|
60
181
|
this.ctx.session.isStreaming ||
|
|
61
182
|
this.ctx.session.isCompacting ||
|
|
62
183
|
this.ctx.session.isGeneratingHandoff ||
|
|
184
|
+
this.ctx.session.isRetrying ||
|
|
63
185
|
this.ctx.session.isBashRunning ||
|
|
64
186
|
this.ctx.session.isEvalRunning ||
|
|
65
187
|
this.ctx.autoCompactionLoader ||
|
|
@@ -67,6 +189,8 @@ export class InputController {
|
|
|
67
189
|
this.ctx.autoCompactionEscapeHandler ||
|
|
68
190
|
this.ctx.retryEscapeHandler,
|
|
69
191
|
);
|
|
192
|
+
this.#installGlobalInterruptListener();
|
|
193
|
+
|
|
70
194
|
// An open btw panel must stay dismissable with Esc even while another
|
|
71
195
|
// controller (auto-compaction, auto-retry, manual compaction, etc.) has
|
|
72
196
|
// temporarily replaced editor.onEscape. This priority hook is never
|
|
@@ -87,6 +211,9 @@ export class InputController {
|
|
|
87
211
|
}
|
|
88
212
|
this.#steerConsumePending = false;
|
|
89
213
|
}
|
|
214
|
+
if (this.#handleCancellableWorkEscape({ maintenance: true, retry: true })) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
90
217
|
// Normal input state with user-typed text: Esc must not interrupt a
|
|
91
218
|
// running task (streaming turn, bash/eval). A double Esc within the
|
|
92
219
|
// 500ms window clears the composer instead. Bash/Python input modes
|
|
@@ -101,35 +228,19 @@ export class InputController {
|
|
|
101
228
|
}
|
|
102
229
|
return;
|
|
103
230
|
}
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.ctx.session.abortEval();
|
|
118
|
-
} else if (this.ctx.isPythonMode) {
|
|
119
|
-
this.ctx.editor.setText("");
|
|
120
|
-
this.ctx.isPythonMode = false;
|
|
121
|
-
this.ctx.updateEditorBorderColor();
|
|
122
|
-
} else if (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
|
-
} 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()) {
|
|
133
244
|
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
134
245
|
const action = settings.get("doubleEscapeAction");
|
|
135
246
|
if (action !== "none") {
|
|
@@ -156,6 +267,8 @@ export class InputController {
|
|
|
156
267
|
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
157
268
|
this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
|
|
158
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();
|
|
159
272
|
this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
|
|
160
273
|
this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel();
|
|
161
274
|
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
@@ -200,8 +313,17 @@ export class InputController {
|
|
|
200
313
|
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
201
314
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
202
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
|
+
};
|
|
203
321
|
this.ctx.editor.onTabDeclined = () => {
|
|
204
|
-
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();
|
|
205
327
|
};
|
|
206
328
|
|
|
207
329
|
this.ctx.editor.clearCustomKeyHandlers();
|
|
@@ -492,7 +614,7 @@ export class InputController {
|
|
|
492
614
|
}
|
|
493
615
|
|
|
494
616
|
handleDequeue(): void {
|
|
495
|
-
const restored = this.
|
|
617
|
+
const restored = this.restoreLatestQueuedMessageToEditor();
|
|
496
618
|
if (restored === 0) {
|
|
497
619
|
this.ctx.showStatus("No queued messages to restore");
|
|
498
620
|
} else {
|
|
@@ -603,13 +725,15 @@ export class InputController {
|
|
|
603
725
|
const text = this.ctx.editor.getText().trim();
|
|
604
726
|
if (!text) return;
|
|
605
727
|
|
|
606
|
-
// Compaction first: while compacting, free text
|
|
607
|
-
//
|
|
608
|
-
// skill
|
|
609
|
-
//
|
|
610
|
-
// the queued entry is later re-parsed into a skill invocation is a
|
|
611
|
-
// separate concern owned by the compaction-resume path.
|
|
728
|
+
// Compaction first: while compacting, queue free text and `/skill:*`
|
|
729
|
+
// commands in the compaction-local queue. `flushCompactionQueue`
|
|
730
|
+
// replays skill entries through the custom-message skill path after
|
|
731
|
+
// compaction finishes so they are not degraded into plain prompts.
|
|
612
732
|
if (this.ctx.session.isCompacting) {
|
|
733
|
+
if (this.ctx.pendingImages.length > 0) {
|
|
734
|
+
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
613
737
|
this.ctx.queueCompactionMessage(text, "followUp");
|
|
614
738
|
return;
|
|
615
739
|
}
|
|
@@ -643,10 +767,28 @@ export class InputController {
|
|
|
643
767
|
return this.handleFollowUp();
|
|
644
768
|
}
|
|
645
769
|
|
|
770
|
+
restoreLatestQueuedMessageToEditor(options?: { currentText?: string }): number {
|
|
771
|
+
const compactionQueued = this.ctx.compactionQueuedMessages.pop();
|
|
772
|
+
const queuedText = compactionQueued?.text ?? this.ctx.session.popLastQueuedMessage();
|
|
773
|
+
if (!queuedText) {
|
|
774
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
this.ctx.locallySubmittedUserSignatures.delete(`${queuedText}\u00000`);
|
|
779
|
+
const currentText = options?.currentText ?? this.ctx.editor.getText();
|
|
780
|
+
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
781
|
+
this.ctx.editor.setText(combinedText);
|
|
782
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
783
|
+
return 1;
|
|
784
|
+
}
|
|
785
|
+
|
|
646
786
|
restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {
|
|
647
787
|
this.ctx.locallySubmittedUserSignatures.clear();
|
|
648
788
|
const { steering, followUp } = this.ctx.session.clearQueue();
|
|
649
|
-
const
|
|
789
|
+
const compactionQueued = (this.ctx.compactionQueuedMessages ?? []).map(entry => entry.text);
|
|
790
|
+
this.ctx.compactionQueuedMessages = [];
|
|
791
|
+
const allQueued = [...steering, ...followUp, ...compactionQueued];
|
|
650
792
|
if (allQueued.length === 0) {
|
|
651
793
|
this.ctx.updatePendingMessagesDisplay();
|
|
652
794
|
if (options?.abort) {
|
|
@@ -705,6 +847,7 @@ export class InputController {
|
|
|
705
847
|
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
706
848
|
this.ctx.retryEscapeHandler = undefined;
|
|
707
849
|
}
|
|
850
|
+
this.ctx.retryEscapePrimed = false;
|
|
708
851
|
this.ctx.statusContainer.clear();
|
|
709
852
|
this.ctx.statusLine.dispose();
|
|
710
853
|
|
|
@@ -859,6 +1002,8 @@ export class InputController {
|
|
|
859
1002
|
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
860
1003
|
copyPrompt: () => this.handleCopyPrompt(),
|
|
861
1004
|
pasteImage: () => void this.handleImagePaste(),
|
|
1005
|
+
newSession: () => void this.ctx.handleClearCommand(),
|
|
1006
|
+
showHelp: () => this.ctx.handleHelpCommand(),
|
|
862
1007
|
scrollTmuxToPreviousUserInput: () => this.scrollTmuxToPreviousUserInput(),
|
|
863
1008
|
undo: prefix => this.ctx.editor.undoPastTransientText(prefix),
|
|
864
1009
|
moveCursorToMessageEnd: () => this.ctx.editor.moveToMessageEnd(),
|
|
@@ -903,6 +1048,14 @@ export class InputController {
|
|
|
903
1048
|
}
|
|
904
1049
|
}
|
|
905
1050
|
|
|
1051
|
+
openCommandPalette(): void {
|
|
1052
|
+
if (this.ctx.editor.getText().trim().length > 0) {
|
|
1053
|
+
this.ctx.showStatus("Command palette opens from an empty prompt. Type / for inline commands.");
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
this.ctx.editor.handleInput("/");
|
|
1057
|
+
}
|
|
1058
|
+
|
|
906
1059
|
cycleThinkingLevel(): void {
|
|
907
1060
|
const newLevel = this.ctx.session.cycleThinkingLevel();
|
|
908
1061
|
if (newLevel === undefined) {
|
|
@@ -15,7 +15,7 @@ import { formatModelProfileDisplayLabel, recommendModelProfileForProvider } from
|
|
|
15
15
|
import { GJC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
16
16
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
17
17
|
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
18
|
-
import { settings } from "../../config/settings";
|
|
18
|
+
import { type Settings, settings } from "../../config/settings";
|
|
19
19
|
import { DebugSelectorComponent } from "../../debug";
|
|
20
20
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
21
21
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
|
|
86
86
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
87
87
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
88
|
+
import type { StatusLineSettings } from "../components/status-line";
|
|
88
89
|
import { ThemeSelectorComponent } from "../components/theme-selector";
|
|
89
90
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
90
91
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
@@ -108,6 +109,24 @@ function isThemePreviewSuperseded(result: { success: boolean; error?: string }):
|
|
|
108
109
|
return !result.success && result.error?.includes("superseded by a newer request") === true;
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Snapshot the persisted status-line settings that the status-line component
|
|
114
|
+
* cares about. Preview, cancel-restore, and commit paths all share this so the
|
|
115
|
+
* previewed row count (and every other field) can never drift out of sync.
|
|
116
|
+
*/
|
|
117
|
+
export function buildStatusLineSettings(settingsInstance: Settings): StatusLineSettings {
|
|
118
|
+
return {
|
|
119
|
+
preset: settingsInstance.get("statusLine.preset"),
|
|
120
|
+
leftSegments: settingsInstance.get("statusLine.leftSegments"),
|
|
121
|
+
rightSegments: settingsInstance.get("statusLine.rightSegments"),
|
|
122
|
+
separator: settingsInstance.get("statusLine.separator"),
|
|
123
|
+
showHookStatus: settingsInstance.get("statusLine.showHookStatus"),
|
|
124
|
+
sessionAccent: settingsInstance.get("statusLine.sessionAccent"),
|
|
125
|
+
maxRows: settingsInstance.get("statusLine.maxRows"),
|
|
126
|
+
segmentOptions: settingsInstance.get("statusLine.segmentOptions"),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
function formatProviderOnboardingCommandGuide(): string {
|
|
112
131
|
return [
|
|
113
132
|
"Provider preset setup:",
|
|
@@ -442,13 +461,7 @@ export class SelectorController {
|
|
|
442
461
|
onStatusLinePreview: previewSettings => {
|
|
443
462
|
// Update status line with preview settings
|
|
444
463
|
this.ctx.statusLine.updateSettings({
|
|
445
|
-
|
|
446
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
447
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
448
|
-
separator: settings.get("statusLine.separator"),
|
|
449
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
450
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
451
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
464
|
+
...buildStatusLineSettings(settings),
|
|
452
465
|
...previewSettings,
|
|
453
466
|
});
|
|
454
467
|
this.ctx.updateEditorTopBorder();
|
|
@@ -458,7 +471,7 @@ export class SelectorController {
|
|
|
458
471
|
// Return the rendered status line for inline preview
|
|
459
472
|
const availableWidth =
|
|
460
473
|
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
461
|
-
return this.ctx.statusLine.
|
|
474
|
+
return this.ctx.statusLine.getPreviewContent(availableWidth);
|
|
462
475
|
},
|
|
463
476
|
onPluginsChanged: () => {
|
|
464
477
|
this.ctx.ui.requestRender();
|
|
@@ -466,15 +479,7 @@ export class SelectorController {
|
|
|
466
479
|
onCancel: () => {
|
|
467
480
|
done();
|
|
468
481
|
// Restore status line to saved settings
|
|
469
|
-
this.ctx.statusLine.updateSettings(
|
|
470
|
-
preset: settings.get("statusLine.preset"),
|
|
471
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
472
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
473
|
-
separator: settings.get("statusLine.separator"),
|
|
474
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
475
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
476
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
477
|
-
});
|
|
482
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
478
483
|
this.ctx.updateEditorTopBorder();
|
|
479
484
|
this.ctx.ui.requestRender();
|
|
480
485
|
},
|
|
@@ -718,6 +723,7 @@ export class SelectorController {
|
|
|
718
723
|
case "statusLineShowHooks":
|
|
719
724
|
case "statusLine.showHookStatus":
|
|
720
725
|
case "statusLine.sessionAccent":
|
|
726
|
+
case "statusLine.maxRows":
|
|
721
727
|
case "statusLine.leftSegments":
|
|
722
728
|
case "statusLine.rightSegments":
|
|
723
729
|
case "statusLine.segmentOptions":
|
|
@@ -732,16 +738,7 @@ export class SelectorController {
|
|
|
732
738
|
case "statusLineGitShowUntracked":
|
|
733
739
|
case "statusLineTimeFormat":
|
|
734
740
|
case "statusLineTimeShowSeconds": {
|
|
735
|
-
|
|
736
|
-
preset: settings.get("statusLine.preset"),
|
|
737
|
-
leftSegments: settings.get("statusLine.leftSegments"),
|
|
738
|
-
rightSegments: settings.get("statusLine.rightSegments"),
|
|
739
|
-
separator: settings.get("statusLine.separator"),
|
|
740
|
-
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
741
|
-
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
742
|
-
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
743
|
-
};
|
|
744
|
-
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
741
|
+
this.ctx.statusLine.updateSettings(buildStatusLineSettings(settings));
|
|
745
742
|
this.ctx.updateEditorTopBorder();
|
|
746
743
|
this.ctx.ui.requestRender();
|
|
747
744
|
break;
|
|
@@ -71,7 +71,6 @@ import { normalizeLocalScheme } from "../tools/path-utils";
|
|
|
71
71
|
import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve";
|
|
72
72
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
73
73
|
import { ToolError } from "../tools/tool-errors";
|
|
74
|
-
|
|
75
74
|
import type { EventBus } from "../utils/event-bus";
|
|
76
75
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
77
76
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
@@ -103,6 +102,7 @@ import { JobsObserver } from "./jobs-observer";
|
|
|
103
102
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
104
103
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
105
104
|
import { interruptHint } from "./shared";
|
|
105
|
+
import { shouldShowExtensionCommand } from "./slash-command-visibility";
|
|
106
106
|
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
107
107
|
import type { Theme } from "./theme/theme";
|
|
108
108
|
import {
|
|
@@ -321,6 +321,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
321
321
|
}
|
|
322
322
|
autoCompactionEscapeHandler?: () => void;
|
|
323
323
|
retryEscapeHandler?: () => void;
|
|
324
|
+
retryEscapePrimed = false;
|
|
324
325
|
retryCountdownTimer?: NodeJS.Timeout;
|
|
325
326
|
unsubscribe?: () => void;
|
|
326
327
|
onInputCallback?: (input: SubmittedUserInput) => void;
|
|
@@ -422,7 +423,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
422
423
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
423
424
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
424
425
|
this.editor.onAutocompleteCancel = () => {
|
|
425
|
-
this.ui.requestRender(
|
|
426
|
+
this.ui.requestRender();
|
|
426
427
|
};
|
|
427
428
|
this.editor.onAutocompleteUpdate = () => {
|
|
428
429
|
this.ui.requestRender();
|
|
@@ -432,7 +433,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
432
433
|
this.#syncEditorMaxHeight();
|
|
433
434
|
this.updateEditorChrome();
|
|
434
435
|
this.editor.invalidate();
|
|
435
|
-
this.ui.
|
|
436
|
+
this.ui.requestResizeRender();
|
|
436
437
|
};
|
|
437
438
|
process.stdout.on("resize", this.#resizeHandler);
|
|
438
439
|
try {
|
|
@@ -452,13 +453,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
452
453
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
453
454
|
|
|
454
455
|
const builtinCommandNames = new Set(BUILTIN_SLASH_COMMANDS.map(c => c.name));
|
|
456
|
+
const activeProvider = this.session.model?.provider;
|
|
455
457
|
const hookCommands: SlashCommand[] = (
|
|
456
458
|
this.session.extensionRunner?.getRegisteredCommands(builtinCommandNames) ?? []
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
)
|
|
460
|
+
.filter(cmd => shouldShowExtensionCommand(cmd.name, activeProvider))
|
|
461
|
+
.map(cmd => ({
|
|
462
|
+
name: cmd.name,
|
|
463
|
+
description: cmd.description ?? "(hook command)",
|
|
464
|
+
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
465
|
+
}));
|
|
462
466
|
|
|
463
467
|
// Convert custom commands (TypeScript) to SlashCommand format
|
|
464
468
|
const customCommands: SlashCommand[] = this.session.customCommands.map(loaded => ({
|
|
@@ -527,6 +531,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
527
531
|
}
|
|
528
532
|
|
|
529
533
|
if (!startupQuiet) {
|
|
534
|
+
const getWelcomeReservedBottomRows = (width: number): number =>
|
|
535
|
+
[
|
|
536
|
+
this.statusLine,
|
|
537
|
+
this.hookWidgetContainerAbove,
|
|
538
|
+
this.editorContainer,
|
|
539
|
+
this.hookWidgetContainerBelow,
|
|
540
|
+
].reduce((rows, component) => rows + component.render(width).length, 0);
|
|
541
|
+
|
|
530
542
|
// Add welcome header
|
|
531
543
|
this.#welcomeComponent = new WelcomeComponent(
|
|
532
544
|
this.#version,
|
|
@@ -535,30 +547,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
535
547
|
recentSessions,
|
|
536
548
|
this.#getWelcomeLspServers(),
|
|
537
549
|
welcomeLogoMode,
|
|
550
|
+
{
|
|
551
|
+
getViewportRows: () => this.ui.terminal.rows,
|
|
552
|
+
getReservedBottomRows: getWelcomeReservedBottomRows,
|
|
553
|
+
changelogMarkdown: this.#changelogMarkdown,
|
|
554
|
+
collapseChangelog: settings.get("collapseChangelog"),
|
|
555
|
+
},
|
|
538
556
|
);
|
|
539
557
|
|
|
540
|
-
// Setup UI layout
|
|
541
|
-
this.ui.addChild(new Spacer(1));
|
|
542
558
|
this.ui.addChild(this.#welcomeComponent);
|
|
543
|
-
this.ui.addChild(new Spacer(1));
|
|
544
559
|
this.#welcomeComponent.playIntro(() => this.ui.requestRender());
|
|
545
|
-
|
|
546
|
-
// Add changelog if provided
|
|
547
|
-
if (this.#changelogMarkdown) {
|
|
548
|
-
this.ui.addChild(new DynamicBorder());
|
|
549
|
-
if (settings.get("collapseChangelog")) {
|
|
550
|
-
const versionMatch = this.#changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
|
|
551
|
-
const latestVersion = versionMatch ? versionMatch[1] : this.#version;
|
|
552
|
-
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
|
|
553
|
-
this.ui.addChild(new Text(condensedText, 1, 0));
|
|
554
|
-
} else {
|
|
555
|
-
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
556
|
-
this.ui.addChild(new Spacer(1));
|
|
557
|
-
this.ui.addChild(new Markdown(this.#changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
|
|
558
|
-
this.ui.addChild(new Spacer(1));
|
|
559
|
-
}
|
|
560
|
-
this.ui.addChild(new DynamicBorder());
|
|
561
|
-
}
|
|
562
560
|
}
|
|
563
561
|
|
|
564
562
|
this.ui.addChild(this.chatContainer);
|
|
@@ -1960,6 +1958,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1960
1958
|
this.loadingAnimation.stop();
|
|
1961
1959
|
this.loadingAnimation = undefined;
|
|
1962
1960
|
}
|
|
1961
|
+
this.#welcomeComponent?.dispose();
|
|
1962
|
+
this.#welcomeComponent = undefined;
|
|
1963
1963
|
this.#cleanupMicAnimation();
|
|
1964
1964
|
this.#cancelGoalContinuation();
|
|
1965
1965
|
if (this.#sttController) {
|
|
@@ -2035,7 +2035,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2035
2035
|
const sessionId = this.sessionManager.getSessionId();
|
|
2036
2036
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
2037
2037
|
if (sessionId && sessionFile) {
|
|
2038
|
-
process.stderr.write(
|
|
2038
|
+
process.stderr.write(
|
|
2039
|
+
`\n${chalk.dim("Resume this session with:")}\n${chalk.dim(`${APP_NAME} --resume ${sessionId}`)}\n`,
|
|
2040
|
+
);
|
|
2039
2041
|
}
|
|
2040
2042
|
|
|
2041
2043
|
await postmortem.quit(0);
|
|
@@ -2071,7 +2073,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2071
2073
|
nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
2072
2074
|
nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible"));
|
|
2073
2075
|
nextEditor.onAutocompleteCancel = () => {
|
|
2074
|
-
this.ui.requestRender(
|
|
2076
|
+
this.ui.requestRender();
|
|
2075
2077
|
};
|
|
2076
2078
|
nextEditor.onAutocompleteUpdate = () => {
|
|
2077
2079
|
this.ui.requestRender();
|
|
@@ -2311,6 +2313,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2311
2313
|
this.#commandController.handleHotkeysCommand();
|
|
2312
2314
|
}
|
|
2313
2315
|
|
|
2316
|
+
handleHelpCommand(): void {
|
|
2317
|
+
this.#commandController.handleHelpCommand();
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2314
2320
|
handleToolsCommand(): void {
|
|
2315
2321
|
this.#commandController.handleToolsCommand();
|
|
2316
2322
|
}
|
|
@@ -29,6 +29,8 @@ interface PromptActionAutocompleteOptions {
|
|
|
29
29
|
copyCurrentLine: () => void;
|
|
30
30
|
copyPrompt: () => void;
|
|
31
31
|
pasteImage: () => void;
|
|
32
|
+
newSession: () => void;
|
|
33
|
+
showHelp: () => void;
|
|
32
34
|
scrollTmuxToPreviousUserInput: () => void;
|
|
33
35
|
undo: (prefix: string) => void;
|
|
34
36
|
moveCursorToMessageEnd: () => void;
|
|
@@ -101,6 +103,12 @@ function mergeAutocompleteSuggestions(
|
|
|
101
103
|
|
|
102
104
|
return { items, prefix: primary.prefix };
|
|
103
105
|
}
|
|
106
|
+
const ADVANCED_SLASH_COMMAND_PRIORITIES = new Map<string, number>([["grok-build-usage", -100]]);
|
|
107
|
+
|
|
108
|
+
function getSlashCommandPriority(command: SlashCommand | undefined, item: AutocompleteItem): number {
|
|
109
|
+
if (command?.priority !== undefined) return command.priority;
|
|
110
|
+
return ADVANCED_SLASH_COMMAND_PRIORITIES.get(item.value) ?? 0;
|
|
111
|
+
}
|
|
104
112
|
|
|
105
113
|
function sortSlashCommandSuggestions(
|
|
106
114
|
suggestions: { items: AutocompleteItem[]; prefix: string } | null,
|
|
@@ -123,7 +131,7 @@ function sortSlashCommandSuggestions(
|
|
|
123
131
|
index,
|
|
124
132
|
commandIndex,
|
|
125
133
|
matchRank: getSlashCommandMatchRank(query, lowerName),
|
|
126
|
-
priority: command
|
|
134
|
+
priority: getSlashCommandPriority(command, item),
|
|
127
135
|
score: Math.max(nameScore, descScore),
|
|
128
136
|
};
|
|
129
137
|
})
|
|
@@ -358,6 +366,20 @@ export function createPromptActionAutocompleteProvider(
|
|
|
358
366
|
): PromptActionAutocompleteProvider {
|
|
359
367
|
const editorKeybindings = getKeybindings();
|
|
360
368
|
const actions: PromptActionDefinition[] = [
|
|
369
|
+
{
|
|
370
|
+
id: "new-session",
|
|
371
|
+
label: "Start new session",
|
|
372
|
+
description: formatKeyHints(options.keybindings.getKeys("app.session.new")) || "/new",
|
|
373
|
+
keywords: ["new", "session", "fresh", "clear", "start", "conversation"],
|
|
374
|
+
execute: options.newSession,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: "help",
|
|
378
|
+
label: "Open command help",
|
|
379
|
+
description: "/help",
|
|
380
|
+
keywords: ["help", "commands", "command", "palette", "shortcuts", "beginner"],
|
|
381
|
+
execute: options.showHelp,
|
|
382
|
+
},
|
|
361
383
|
{
|
|
362
384
|
id: "copy-line",
|
|
363
385
|
label: "Copy current line",
|