@gajae-code/coding-agent 0.11.6 → 0.11.8
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 +92 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/settings-schema.d.ts +70 -7
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/main.d.ts +32 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +4 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +26 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +30 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
- package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/session/session-storage.d.ts +18 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +52 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +44 -11
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +18 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +73 -7
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +75 -3
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +93 -15
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/internal-urls/local-protocol.ts +64 -21
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +111 -37
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +73 -11
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +25 -4
- package/src/modes/components/welcome.ts +22 -18
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +38 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +149 -63
- package/src/modes/interactive-mode.ts +57 -34
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +19 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +244 -70
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +68 -20
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
- package/src/sdk/bus/telegram-daemon-control.ts +147 -35
- package/src/sdk/bus/telegram-daemon.ts +2463 -450
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +31 -5
- package/src/sdk/startup-capability.ts +9 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +292 -154
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +1163 -137
- package/src/session/internal/managed-session-storage.ts +397 -234
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +620 -403
- package/src/session/session-storage.ts +128 -11
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -89
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +4 -0
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -16,7 +16,13 @@ import {
|
|
|
16
16
|
import { APP_NAME, adjustHsv, getProjectDir, logger, postmortem } from "@gajae-code/utils";
|
|
17
17
|
import chalk from "chalk";
|
|
18
18
|
import { AsyncJobManager } from "../async";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
type AppKeybinding,
|
|
21
|
+
formatKeyHint,
|
|
22
|
+
formatKeyHints,
|
|
23
|
+
KeybindingsManager,
|
|
24
|
+
type KeyDisplayContext,
|
|
25
|
+
} from "../config/keybindings";
|
|
20
26
|
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
21
27
|
import { DEFAULT_GJC_DEFINITION_NAMES } from "../defaults/gjc-defaults";
|
|
22
28
|
import type {
|
|
@@ -83,6 +89,7 @@ import { TodoCommandController } from "./controllers/todo-command-controller";
|
|
|
83
89
|
import { IrcObservationLedger } from "./irc-observation-ledger";
|
|
84
90
|
import { JobsObserver } from "./jobs-observer";
|
|
85
91
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
92
|
+
import { PromptSuggestionController } from "./prompt-suggestion-controller";
|
|
86
93
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
87
94
|
import { interruptHint } from "./shared";
|
|
88
95
|
import { shouldShowExtensionCommand } from "./slash-command-visibility";
|
|
@@ -105,8 +112,15 @@ import {
|
|
|
105
112
|
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
106
113
|
import { addChatChild, prepareTranscriptRebuild, UiHelpers } from "./utils/ui-helpers";
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
export function getDefaultComposerPlaceholder(context: KeyDisplayContext = { platform: process.platform }): string {
|
|
116
|
+
const newlineKeys = context.platform === "win32" ? ["alt+enter", "ctrl+j"] : ["shift+enter", "ctrl+j"];
|
|
117
|
+
return `Type your message... ${formatKeyHints(newlineKeys, context)}: New line · ${formatKeyHint(
|
|
118
|
+
"ctrl+c",
|
|
119
|
+
context,
|
|
120
|
+
)}: Clear · ${formatKeyHint("ctrl+r", context)}: Search history · ${formatKeyHint("shift+tab", context)}: Reasoning`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const DEFAULT_COMPOSER_PLACEHOLDER = getDefaultComposerPlaceholder();
|
|
110
124
|
const WELCOME_RESERVED_CONTAINER_CHILD_LIMIT = 8;
|
|
111
125
|
|
|
112
126
|
const IRC_SIDEBAR_TOGGLE_SHADOWING_ACTIONS: readonly AppKeybinding[] = [
|
|
@@ -128,23 +142,6 @@ export function getWelcomeTranscriptReservedRows(chatContainer: Container, width
|
|
|
128
142
|
? 0
|
|
129
143
|
: chatContainer.render(width).length;
|
|
130
144
|
}
|
|
131
|
-
const FRIENDLY_KEY_PARTS: Record<string, string> = {
|
|
132
|
-
alt: "Alt",
|
|
133
|
-
cmd: "Cmd",
|
|
134
|
-
command: "Cmd",
|
|
135
|
-
ctrl: "Ctrl",
|
|
136
|
-
enter: "Enter",
|
|
137
|
-
meta: process.platform === "darwin" ? "Command" : "Meta",
|
|
138
|
-
option: "Option",
|
|
139
|
-
shift: "Shift",
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
function formatShortcutForPlaceholder(key: string): string {
|
|
143
|
-
return key
|
|
144
|
-
.split("+")
|
|
145
|
-
.map(part => FRIENDLY_KEY_PARTS[part.toLowerCase()] ?? (part.length === 1 ? part.toUpperCase() : part))
|
|
146
|
-
.join("+");
|
|
147
|
-
}
|
|
148
145
|
|
|
149
146
|
const HINT_SHIMMER_PALETTE: ShimmerPalette = {
|
|
150
147
|
low: "dim",
|
|
@@ -169,7 +166,7 @@ function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
|
169
166
|
editor.setClosedBorderBox(true);
|
|
170
167
|
editor.setPromptGutter(undefined);
|
|
171
168
|
editor.setInputPrefix(getDefaultInputPrefix());
|
|
172
|
-
editor.setPlaceholder(
|
|
169
|
+
editor.setPlaceholder(getDefaultComposerPlaceholder());
|
|
173
170
|
editor.setPaddingX(1);
|
|
174
171
|
editor.setRightGutterWidth(1);
|
|
175
172
|
editor.setTopBorder(undefined);
|
|
@@ -264,6 +261,10 @@ export interface InteractiveModeOptions {
|
|
|
264
261
|
initialMessages?: string[];
|
|
265
262
|
}
|
|
266
263
|
|
|
264
|
+
export function selectShutdownDraft(editorText: string, hasActiveBtw: boolean): string {
|
|
265
|
+
return hasActiveBtw ? "" : editorText;
|
|
266
|
+
}
|
|
267
|
+
|
|
267
268
|
export class InteractiveMode implements InteractiveModeContext {
|
|
268
269
|
session: AgentSession;
|
|
269
270
|
sessionManager: SessionManager;
|
|
@@ -336,6 +337,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
336
337
|
fileSlashCommands: Set<string> = new Set();
|
|
337
338
|
skillCommands: Map<string, Skill> = new Map();
|
|
338
339
|
oauthManualInput: OAuthManualInputManager = new OAuthManualInputManager();
|
|
340
|
+
promptSuggestion: PromptSuggestionController;
|
|
339
341
|
|
|
340
342
|
#baseSlashCommands: SlashCommand[] = [];
|
|
341
343
|
#resolvedSlashCommands: SlashCommand[] = [];
|
|
@@ -347,6 +349,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
347
349
|
#petUnavailableWarningDisposer?: () => void;
|
|
348
350
|
readonly #version: string;
|
|
349
351
|
readonly #changelogMarkdown: string | undefined;
|
|
352
|
+
readonly #keyDisplayContext: KeyDisplayContext;
|
|
350
353
|
|
|
351
354
|
readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
|
|
352
355
|
mcpManager?: import("../runtime-mcp").MCPManager;
|
|
@@ -394,6 +397,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
394
397
|
lspServers: LspStartupServerInfo[] | undefined = undefined,
|
|
395
398
|
mcpManager?: import("../runtime-mcp").MCPManager,
|
|
396
399
|
eventBus?: EventBus,
|
|
400
|
+
keyDisplayContext: KeyDisplayContext = { platform: process.platform },
|
|
397
401
|
) {
|
|
398
402
|
this.session = session;
|
|
399
403
|
this.sessionManager = session.sessionManager;
|
|
@@ -432,6 +436,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
432
436
|
this.lspServers = lspServers;
|
|
433
437
|
this.mcpManager = mcpManager;
|
|
434
438
|
this.#eventBus = eventBus;
|
|
439
|
+
this.#keyDisplayContext = keyDisplayContext;
|
|
440
|
+
this.keybindings.setDisplayContext(this.#keyDisplayContext);
|
|
435
441
|
const thisMode = this;
|
|
436
442
|
this.#goalModeController = new GoalModeController({
|
|
437
443
|
session: this.session,
|
|
@@ -513,6 +519,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
513
519
|
this.btwContainer = new Container();
|
|
514
520
|
this.editor = new CustomEditor(getEditorTheme());
|
|
515
521
|
configureDefaultComposerChrome(this.editor);
|
|
522
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
516
523
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
517
524
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
518
525
|
this.editor.onAutocompleteCancel = () => {
|
|
@@ -540,7 +547,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
540
547
|
this.hookWidgetContainerBelow = new Container();
|
|
541
548
|
this.editorContainer = new Container();
|
|
542
549
|
this.editorContainer.addChild(this.editor);
|
|
543
|
-
this.statusLine = new StatusLineComponent(session, {
|
|
550
|
+
this.statusLine = new StatusLineComponent(session, {
|
|
551
|
+
version: this.#version,
|
|
552
|
+
focusDomain: "composer",
|
|
553
|
+
keyDisplayContext: this.#keyDisplayContext,
|
|
554
|
+
});
|
|
544
555
|
this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
545
556
|
|
|
546
557
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
@@ -576,6 +587,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
576
587
|
this.#selectorController = new SelectorController(this);
|
|
577
588
|
this.#inputController = new InputController(this);
|
|
578
589
|
this.statusLine.setActionRegistry(this.#inputController.actionRegistry, () => this.keybindings);
|
|
590
|
+
this.promptSuggestion = new PromptSuggestionController(this);
|
|
579
591
|
this.#observerRegistry = new SessionObserverRegistry();
|
|
580
592
|
}
|
|
581
593
|
|
|
@@ -596,6 +608,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
596
608
|
if (this.isInitialized) return;
|
|
597
609
|
|
|
598
610
|
this.keybindings = logger.time("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
|
|
611
|
+
this.keybindings.setDisplayContext(this.#keyDisplayContext);
|
|
599
612
|
|
|
600
613
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
601
614
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
@@ -653,6 +666,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
653
666
|
getReservedBottomRows: getWelcomeReservedBottomRows,
|
|
654
667
|
changelogMarkdown: this.#changelogMarkdown,
|
|
655
668
|
collapseChangelog: settings.get("collapseChangelog"),
|
|
669
|
+
keyDisplayContext: this.#keyDisplayContext,
|
|
656
670
|
},
|
|
657
671
|
);
|
|
658
672
|
|
|
@@ -803,6 +817,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
803
817
|
onThemeChange(() => {
|
|
804
818
|
clearRenderCache();
|
|
805
819
|
configureDefaultComposerChrome(this.editor);
|
|
820
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
806
821
|
this.ui.invalidate();
|
|
807
822
|
this.updateEditorChrome();
|
|
808
823
|
this.ui.requestRender();
|
|
@@ -1034,12 +1049,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1034
1049
|
}
|
|
1035
1050
|
|
|
1036
1051
|
#getComposerPlaceholder(): string {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1052
|
+
const defaultPlaceholder = getDefaultComposerPlaceholder(this.#keyDisplayContext);
|
|
1053
|
+
if (!this.#isPromptDeliveryBusy()) return defaultPlaceholder;
|
|
1054
|
+
const submitAction = this.settings.get("busyPromptMode") === "steer" ? "Steer" : "Queue";
|
|
1055
|
+
const submitKey = this.keybindings.getDisplayString("tui.input.submit", this.#keyDisplayContext);
|
|
1056
|
+
const parts = submitKey ? [`${submitKey}: ${submitAction}`] : [];
|
|
1040
1057
|
const queueKey = this.#getMessageQueueShortcut();
|
|
1041
|
-
if (queueKey) parts.push(`${
|
|
1042
|
-
return `${
|
|
1058
|
+
if (queueKey) parts.push(`${formatKeyHint(queueKey, this.#keyDisplayContext)}: Queue`);
|
|
1059
|
+
return parts.length > 0 ? `${defaultPlaceholder} · ${parts.join(" · ")}` : defaultPlaceholder;
|
|
1043
1060
|
}
|
|
1044
1061
|
|
|
1045
1062
|
#getWelcomeReservedRows(width: number): number {
|
|
@@ -1322,19 +1339,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1322
1339
|
if (this.#isShuttingDown) return;
|
|
1323
1340
|
this.#isShuttingDown = true;
|
|
1324
1341
|
|
|
1325
|
-
//
|
|
1326
|
-
//
|
|
1327
|
-
|
|
1328
|
-
const draftText = this.editor.getText();
|
|
1342
|
+
// `/btw` owns the shared composer while its panel is open. Never persist a
|
|
1343
|
+
// side-chat draft or pending side-chat images into the main-session draft.
|
|
1344
|
+
const hadActiveBtw = this.#btwController.hasOpenPanel();
|
|
1345
|
+
const draftText = selectShutdownDraft(this.editor.getText(), hadActiveBtw);
|
|
1346
|
+
if (hadActiveBtw) this.pendingImages = [];
|
|
1347
|
+
this.#btwController.dispose();
|
|
1329
1348
|
|
|
1330
|
-
// Flush pending session writes before shutdown
|
|
1349
|
+
// Flush pending session writes before shutdown.
|
|
1331
1350
|
await this.sessionManager.flush();
|
|
1332
1351
|
try {
|
|
1333
1352
|
await this.sessionManager.saveDraft(draftText);
|
|
1334
1353
|
} catch (err) {
|
|
1335
1354
|
logger.warn("Failed to save session draft", { error: String(err) });
|
|
1336
1355
|
}
|
|
1337
|
-
this.#btwController.dispose();
|
|
1338
1356
|
|
|
1339
1357
|
// Emit shutdown event to hooks
|
|
1340
1358
|
this.session.setSdkPlanModeHandler(null);
|
|
@@ -1393,6 +1411,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1393
1411
|
: new CustomEditor(getEditorTheme());
|
|
1394
1412
|
|
|
1395
1413
|
configureDefaultComposerChrome(nextEditor);
|
|
1414
|
+
nextEditor.setPlaceholder(this.#getComposerPlaceholder());
|
|
1396
1415
|
nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
1397
1416
|
nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible"));
|
|
1398
1417
|
nextEditor.onAutocompleteCancel = () => {
|
|
@@ -2025,7 +2044,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2025
2044
|
}
|
|
2026
2045
|
|
|
2027
2046
|
hasActiveBtw(): boolean {
|
|
2028
|
-
return this.#btwController.
|
|
2047
|
+
return this.#btwController.hasOpenPanel();
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
handleBtwFollowUp(question: string): Promise<"accepted" | "busy" | "closed" | "rejected"> {
|
|
2051
|
+
return this.#btwController.submitFollowUp(question);
|
|
2029
2052
|
}
|
|
2030
2053
|
|
|
2031
2054
|
handleBtwEscape(): boolean {
|
|
@@ -3,12 +3,10 @@ import {
|
|
|
3
3
|
type AutocompleteProvider,
|
|
4
4
|
CombinedAutocompleteProvider,
|
|
5
5
|
extractSlashCommandTokenPrefix,
|
|
6
|
-
getKeybindings,
|
|
7
6
|
getSlashCommandMatchRank,
|
|
8
7
|
type SlashCommand,
|
|
9
8
|
} from "@gajae-code/tui";
|
|
10
9
|
import type { KeybindingsManager } from "../config/keybindings";
|
|
11
|
-
import { formatKeyHints } from "../config/keybindings";
|
|
12
10
|
import { isSettingsInitialized, settings } from "../config/settings";
|
|
13
11
|
import { applyEmojiCompletion, getEmojiSuggestions, isEmojiPrefix, tryEmojiInlineReplace } from "./emoji-autocomplete";
|
|
14
12
|
|
|
@@ -44,6 +42,11 @@ interface PromptActionAutocompleteOptions {
|
|
|
44
42
|
moveCursorToMessageStart: () => void;
|
|
45
43
|
moveCursorToLineStart: () => void;
|
|
46
44
|
moveCursorToLineEnd: () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Ghost-text next-prompt prediction shown in the empty composer as a dim
|
|
47
|
+
* inline hint (Tab accepts it via the editor's onTab handler).
|
|
48
|
+
*/
|
|
49
|
+
getPromptSuggestion?: () => string | null;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function fuzzyMatch(query: string, target: string): boolean {
|
|
@@ -186,11 +189,18 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
186
189
|
#baseProvider: CombinedAutocompleteProvider;
|
|
187
190
|
#actions: PromptActionDefinition[];
|
|
188
191
|
#commands: SlashCommand[];
|
|
189
|
-
|
|
190
|
-
|
|
192
|
+
#getPromptSuggestion: (() => string | null) | undefined;
|
|
193
|
+
|
|
194
|
+
constructor(
|
|
195
|
+
commands: SlashCommand[],
|
|
196
|
+
basePath: string,
|
|
197
|
+
actions: PromptActionDefinition[],
|
|
198
|
+
getPromptSuggestion?: () => string | null,
|
|
199
|
+
) {
|
|
191
200
|
this.#baseProvider = new CombinedAutocompleteProvider(commands, basePath);
|
|
192
201
|
this.#actions = actions;
|
|
193
202
|
this.#commands = commands;
|
|
203
|
+
this.#getPromptSuggestion = getPromptSuggestion;
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
async getSuggestions(
|
|
@@ -309,6 +319,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
309
319
|
}
|
|
310
320
|
|
|
311
321
|
getInlineHint(lines: string[], cursorLine: number, cursorCol: number): string | null {
|
|
322
|
+
// An empty composer renders the pending prompt suggestion as ghost text.
|
|
323
|
+
if (lines.every(line => line === "")) {
|
|
324
|
+
const suggestion = this.#getPromptSuggestion?.();
|
|
325
|
+
if (suggestion) return suggestion;
|
|
326
|
+
}
|
|
312
327
|
return this.#baseProvider.getInlineHint?.(lines, cursorLine, cursorCol) ?? null;
|
|
313
328
|
}
|
|
314
329
|
trySyncSlashCompletion(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null {
|
|
@@ -382,12 +397,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
|
|
|
382
397
|
export function createPromptActionAutocompleteProvider(
|
|
383
398
|
options: PromptActionAutocompleteOptions,
|
|
384
399
|
): PromptActionAutocompleteProvider {
|
|
385
|
-
const editorKeybindings = getKeybindings();
|
|
386
400
|
const actions: PromptActionDefinition[] = [
|
|
387
401
|
{
|
|
388
402
|
id: "new-session",
|
|
389
403
|
label: "Start new session",
|
|
390
|
-
description:
|
|
404
|
+
description: options.keybindings.getDisplayString("app.session.new"),
|
|
391
405
|
keywords: ["new", "session", "fresh", "clear", "start", "conversation"],
|
|
392
406
|
execute: options.newSession,
|
|
393
407
|
},
|
|
@@ -401,21 +415,21 @@ export function createPromptActionAutocompleteProvider(
|
|
|
401
415
|
{
|
|
402
416
|
id: "copy-line",
|
|
403
417
|
label: "Copy current line",
|
|
404
|
-
description:
|
|
418
|
+
description: options.keybindings.getDisplayString("app.clipboard.copyLine"),
|
|
405
419
|
keywords: ["copy", "line", "clipboard", "current"],
|
|
406
420
|
execute: options.copyCurrentLine,
|
|
407
421
|
},
|
|
408
422
|
{
|
|
409
423
|
id: "copy-prompt",
|
|
410
424
|
label: "Copy whole prompt",
|
|
411
|
-
description:
|
|
425
|
+
description: options.keybindings.getDisplayString("app.clipboard.copyPrompt"),
|
|
412
426
|
keywords: ["copy", "prompt", "clipboard", "message"],
|
|
413
427
|
execute: options.copyPrompt,
|
|
414
428
|
},
|
|
415
429
|
{
|
|
416
430
|
id: "paste-image",
|
|
417
431
|
label: "Paste image from clipboard",
|
|
418
|
-
description:
|
|
432
|
+
description: options.keybindings.getDisplayString("app.clipboard.pasteImage"),
|
|
419
433
|
keywords: ["paste", "image", "clipboard", "screenshot", "attach", "vision"],
|
|
420
434
|
execute: options.pasteImage,
|
|
421
435
|
},
|
|
@@ -429,7 +443,7 @@ export function createPromptActionAutocompleteProvider(
|
|
|
429
443
|
{
|
|
430
444
|
id: "undo",
|
|
431
445
|
label: "Undo",
|
|
432
|
-
description:
|
|
446
|
+
description: options.keybindings.getDisplayString("tui.editor.undo"),
|
|
433
447
|
keywords: ["undo", "revert", "edit", "history"],
|
|
434
448
|
execute: options.undo,
|
|
435
449
|
},
|
|
@@ -450,18 +464,23 @@ export function createPromptActionAutocompleteProvider(
|
|
|
450
464
|
{
|
|
451
465
|
id: "cursor-line-start",
|
|
452
466
|
label: "Move cursor to beginning of line",
|
|
453
|
-
description:
|
|
467
|
+
description: options.keybindings.getDisplayString("tui.editor.cursorLineStart"),
|
|
454
468
|
keywords: ["move", "cursor", "line", "start", "beginning", "home"],
|
|
455
469
|
execute: options.moveCursorToLineStart,
|
|
456
470
|
},
|
|
457
471
|
{
|
|
458
472
|
id: "cursor-line-end",
|
|
459
473
|
label: "Move cursor to end of line",
|
|
460
|
-
description:
|
|
474
|
+
description: options.keybindings.getDisplayString("tui.editor.cursorLineEnd"),
|
|
461
475
|
keywords: ["move", "cursor", "line", "end"],
|
|
462
476
|
execute: options.moveCursorToLineEnd,
|
|
463
477
|
},
|
|
464
478
|
];
|
|
465
479
|
|
|
466
|
-
return new PromptActionAutocompleteProvider(
|
|
480
|
+
return new PromptActionAutocompleteProvider(
|
|
481
|
+
options.commands,
|
|
482
|
+
options.basePath,
|
|
483
|
+
actions,
|
|
484
|
+
options.getPromptSuggestion,
|
|
485
|
+
);
|
|
467
486
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { logger } from "@gajae-code/utils";
|
|
2
|
+
import { generatePromptSuggestion } from "../utils/prompt-suggestion";
|
|
3
|
+
import type { InteractiveModeContext } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Owns the ghost-text next-prompt prediction lifecycle for interactive mode.
|
|
7
|
+
*
|
|
8
|
+
* After each agent turn ends (and only when `promptSuggestions` is enabled
|
|
9
|
+
* and the composer is empty), a smol-model prediction of the user's next
|
|
10
|
+
* prompt is generated in the background. The result renders as dim ghost
|
|
11
|
+
* text in the empty composer via the autocomplete provider's inline hint;
|
|
12
|
+
* Tab accepts it, typing dismisses it, and a new turn clears it.
|
|
13
|
+
*/
|
|
14
|
+
export class PromptSuggestionController {
|
|
15
|
+
#ctx: InteractiveModeContext;
|
|
16
|
+
#current: string | null = null;
|
|
17
|
+
/** Monotonic token; bumping it invalidates any in-flight generation. */
|
|
18
|
+
#generation = 0;
|
|
19
|
+
|
|
20
|
+
constructor(ctx: InteractiveModeContext) {
|
|
21
|
+
this.#ctx = ctx;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Current suggestion to render as ghost text, or null. */
|
|
25
|
+
get current(): string | null {
|
|
26
|
+
return this.#current;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Drop the current suggestion and invalidate any in-flight generation. */
|
|
30
|
+
clear(): void {
|
|
31
|
+
this.#generation++;
|
|
32
|
+
if (this.#current === null) return;
|
|
33
|
+
this.#current = null;
|
|
34
|
+
this.#ctx.ui.requestRender();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A new agent turn started; any pending suggestion is stale. */
|
|
38
|
+
onAgentStart(): void {
|
|
39
|
+
this.clear();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Editor content changed. A non-empty composer means the user is writing
|
|
44
|
+
* their own prompt: dismiss the suggestion and cancel pending generation.
|
|
45
|
+
*/
|
|
46
|
+
notifyEditorChanged(text: string): void {
|
|
47
|
+
if (text.length > 0) this.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Agent turn ended; kick off background generation when the feature is
|
|
52
|
+
* enabled and the composer is idle and empty.
|
|
53
|
+
*/
|
|
54
|
+
onAgentEnd(): void {
|
|
55
|
+
if (!this.#ctx.settings.get("promptSuggestions")) return;
|
|
56
|
+
if (this.#ctx.shutdownRequested) return;
|
|
57
|
+
if (this.#ctx.session.isStreaming) return;
|
|
58
|
+
if (this.#ctx.editor.getText().trim() !== "") return;
|
|
59
|
+
|
|
60
|
+
const generation = ++this.#generation;
|
|
61
|
+
void generatePromptSuggestion(
|
|
62
|
+
this.#ctx.session.messages,
|
|
63
|
+
this.#ctx.session.modelRegistry,
|
|
64
|
+
this.#ctx.settings,
|
|
65
|
+
this.#ctx.session.sessionId,
|
|
66
|
+
this.#ctx.session.model,
|
|
67
|
+
provider => this.#ctx.session.agent.metadataForProvider(provider),
|
|
68
|
+
)
|
|
69
|
+
.then(suggestion => {
|
|
70
|
+
if (generation !== this.#generation) return;
|
|
71
|
+
if (!suggestion) return;
|
|
72
|
+
// The world may have moved on while the model was thinking.
|
|
73
|
+
if (this.#ctx.session.isStreaming) return;
|
|
74
|
+
if (this.#ctx.editor.getText().trim() !== "") return;
|
|
75
|
+
this.#current = suggestion;
|
|
76
|
+
this.#ctx.ui.requestRender();
|
|
77
|
+
})
|
|
78
|
+
.catch(error => {
|
|
79
|
+
logger.debug("prompt-suggestion: generation failed", { error: String(error) });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Tab handler for the composer: accept the suggestion when the composer
|
|
85
|
+
* is empty. Returns true when Tab was consumed.
|
|
86
|
+
*/
|
|
87
|
+
tryAcceptOnTab(text: string): boolean {
|
|
88
|
+
const suggestion = this.#current;
|
|
89
|
+
if (!suggestion || text.trim() !== "") return false;
|
|
90
|
+
// setText fires onChange with non-empty text, which clears #current.
|
|
91
|
+
this.#ctx.editor.setText(suggestion);
|
|
92
|
+
this.clear();
|
|
93
|
+
this.#ctx.ui.requestRender();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -95,7 +95,7 @@ export interface AskSelectedAckRecoveryParticipant {
|
|
|
95
95
|
|
|
96
96
|
/** SDK-native surface for emitting a workflow gate and awaiting its answer. */
|
|
97
97
|
export interface WorkflowGateEmitter {
|
|
98
|
-
|
|
98
|
+
supportsRemoteGateAnswers(): boolean;
|
|
99
99
|
emitGate(input: OpenGateInput): Promise<unknown>;
|
|
100
100
|
onGateEmitted?(listener: (gate: WorkflowGate) => void): () => void;
|
|
101
101
|
resolveGate?(response: WorkflowGateResponse): Promise<WorkflowGateResolution>;
|
|
@@ -167,7 +167,7 @@ export class BrokerWorkflowGateEmitter implements WorkflowGateEmitter {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
supportsRemoteGateAnswers(): boolean {
|
|
171
171
|
return true;
|
|
172
172
|
}
|
|
173
173
|
setRuntimeTurnProvider(provider: (() => string | undefined) | null): void {
|
package/src/modes/types.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
|
32
32
|
import type { StatusLineComponent } from "./components/tool-status-header";
|
|
33
33
|
import type { IrcObservationLedger } from "./irc-observation-ledger";
|
|
34
34
|
import type { OAuthManualInputManager } from "./oauth-manual-input";
|
|
35
|
+
import type { PromptSuggestionController } from "./prompt-suggestion-controller";
|
|
35
36
|
import type { Theme } from "./theme/theme";
|
|
36
37
|
import type { ParsedIrcMessage } from "./utils/irc-message";
|
|
37
38
|
|
|
@@ -156,6 +157,8 @@ export interface InteractiveModeContext {
|
|
|
156
157
|
skillCommands: Map<string, Skill>;
|
|
157
158
|
oauthManualInput: OAuthManualInputManager;
|
|
158
159
|
todoPhases: TodoPhase[];
|
|
160
|
+
/** Ghost-text next-prompt prediction; absent in ACP/lightweight test contexts. */
|
|
161
|
+
promptSuggestion?: PromptSuggestionController;
|
|
159
162
|
|
|
160
163
|
// Lifecycle
|
|
161
164
|
init(): Promise<void>;
|
|
@@ -338,6 +341,7 @@ export interface InteractiveModeContext {
|
|
|
338
341
|
handleBackgroundCommand(): void;
|
|
339
342
|
handleImagePaste(): Promise<boolean>;
|
|
340
343
|
handleBtwCommand(question: string): Promise<void>;
|
|
344
|
+
handleBtwFollowUp(question: string): Promise<"accepted" | "busy" | "closed" | "rejected">;
|
|
341
345
|
hasActiveBtw(): boolean;
|
|
342
346
|
handleBtwEscape(): boolean;
|
|
343
347
|
cycleThinkingLevel(): void;
|
|
@@ -1,60 +1,88 @@
|
|
|
1
|
+
import type { Keybinding } from "@gajae-code/tui";
|
|
1
2
|
import type { AppKeybinding, KeybindingsManager } from "../../config/keybindings";
|
|
2
3
|
|
|
3
4
|
export interface HotkeysMarkdownBindings {
|
|
4
|
-
keybindings: Pick<KeybindingsManager, "
|
|
5
|
+
keybindings: Pick<KeybindingsManager, "formatAccessibleKeyHint" | "getAccessibleDisplayString">;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function key(bindings: HotkeysMarkdownBindings, action: Keybinding): string {
|
|
9
|
+
return bindings.keybindings.getAccessibleDisplayString(action) || "Disabled";
|
|
5
10
|
}
|
|
6
11
|
|
|
7
12
|
function appKey(bindings: HotkeysMarkdownBindings, action: AppKeybinding): string {
|
|
8
|
-
return bindings
|
|
13
|
+
return key(bindings, action);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatHotkeyMarkdownCode(label: string, escapeTablePipes = true): string {
|
|
17
|
+
const escaped = escapeTablePipes ? label.replace(/\|/g, "\\|") : label;
|
|
18
|
+
let longestBacktickRun = 0;
|
|
19
|
+
for (const match of escaped.matchAll(/`+/g)) {
|
|
20
|
+
longestBacktickRun = Math.max(longestBacktickRun, match[0].length);
|
|
21
|
+
}
|
|
22
|
+
const fence = "`".repeat(longestBacktickRun + 1);
|
|
23
|
+
const padding = escaped.startsWith("`") || escaped.endsWith("`") ? " " : "";
|
|
24
|
+
return `${fence}${padding}${escaped}${padding}${fence}`;
|
|
9
25
|
}
|
|
10
26
|
|
|
11
27
|
export function buildHotkeysMarkdown(bindings: HotkeysMarkdownBindings): string {
|
|
28
|
+
const displayKey = (action: Keybinding): string => formatHotkeyMarkdownCode(key(bindings, action));
|
|
29
|
+
const displayAppKey = (action: AppKeybinding): string => formatHotkeyMarkdownCode(appKey(bindings, action));
|
|
30
|
+
const displayKeys = (...labels: string[]): string => formatHotkeyMarkdownCode(labels.join("/"));
|
|
31
|
+
const formatFixedKey = (key: string): string => bindings.keybindings.formatAccessibleKeyHint(key);
|
|
32
|
+
const displayKeyWithFixed = (action: Keybinding, fixedKey: string): string => {
|
|
33
|
+
const configured = key(bindings, action);
|
|
34
|
+
const fixed = formatFixedKey(fixedKey);
|
|
35
|
+
const labels = configured.split("/");
|
|
36
|
+
if (!labels.includes(fixed)) labels.push(fixed);
|
|
37
|
+
return displayKeys(...labels);
|
|
38
|
+
};
|
|
39
|
+
|
|
12
40
|
return [
|
|
13
41
|
"**Navigation**",
|
|
14
42
|
"| Key | Action |",
|
|
15
43
|
"|-----|--------|",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
44
|
+
`| ${displayKeys(key(bindings, "tui.editor.cursorUp"), key(bindings, "tui.editor.cursorDown"))} | Move cursor / browse history (Up when empty) |`,
|
|
45
|
+
`| ${displayKeys(key(bindings, "tui.editor.cursorWordLeft"), key(bindings, "tui.editor.cursorWordRight"))} | Move by word |`,
|
|
46
|
+
`| ${displayKeyWithFixed("tui.editor.cursorLineStart", "ctrl+a")} | Start of line |`,
|
|
47
|
+
`| ${displayKeyWithFixed("tui.editor.cursorLineEnd", "ctrl+e")} | End of line |`,
|
|
20
48
|
"",
|
|
21
49
|
"**Editing**",
|
|
22
50
|
"| Key | Action |",
|
|
23
51
|
"|-----|--------|",
|
|
24
|
-
"
|
|
25
|
-
`|
|
|
26
|
-
`|
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
`|
|
|
32
|
-
`|
|
|
52
|
+
`| ${displayKey("tui.input.submit")} | Send / queue while busy |`,
|
|
53
|
+
`| ${displayAppKey("app.message.queue")} | Queue message for next turn |`,
|
|
54
|
+
`| ${displayAppKey("app.message.dequeue")} | Select queued message to edit |`,
|
|
55
|
+
`| ${displayKeys(key(bindings, "tui.input.newLine"), formatFixedKey("ctrl+j"))} | New line |`,
|
|
56
|
+
`| ${displayKeys(formatFixedKey("ctrl+w"), formatFixedKey("alt+backspace"))} | Delete word backwards |`,
|
|
57
|
+
`| ${displayKeys(formatFixedKey("ctrl+u"))} | Delete to start of line |`,
|
|
58
|
+
`| ${displayKeys(formatFixedKey("ctrl+k"))} | Delete to end of line |`,
|
|
59
|
+
`| ${displayAppKey("app.clipboard.copyLine")} | Copy current line |`,
|
|
60
|
+
`| ${displayAppKey("app.clipboard.copyPrompt")} | Copy whole prompt |`,
|
|
33
61
|
"",
|
|
34
62
|
"**Other**",
|
|
35
63
|
"| Key | Action |",
|
|
36
64
|
"|-----|--------|",
|
|
37
|
-
"
|
|
38
|
-
`|
|
|
39
|
-
`|
|
|
40
|
-
`|
|
|
41
|
-
`|
|
|
42
|
-
`|
|
|
43
|
-
`|
|
|
44
|
-
`|
|
|
45
|
-
`|
|
|
46
|
-
`|
|
|
47
|
-
`|
|
|
48
|
-
`|
|
|
49
|
-
`|
|
|
50
|
-
`|
|
|
51
|
-
`|
|
|
52
|
-
`|
|
|
53
|
-
`|
|
|
54
|
-
`|
|
|
55
|
-
`|
|
|
56
|
-
`|
|
|
57
|
-
`|
|
|
65
|
+
`| ${displayKey("tui.input.tab")} | Path completion / accept autocomplete |`,
|
|
66
|
+
`| ${displayAppKey("app.interrupt")} | Cancel autocomplete / interrupt active work |`,
|
|
67
|
+
`| ${displayAppKey("app.clear")} | Clear editor (first) / exit (second) |`,
|
|
68
|
+
`| ${displayAppKey("app.exit")} | Exit (when editor is empty) |`,
|
|
69
|
+
`| ${displayAppKey("app.suspend")} | Suspend to background |`,
|
|
70
|
+
`| ${displayAppKey("app.commandPalette.open")} | Open command palette |`,
|
|
71
|
+
`| ${displayAppKey("app.session.new")} | Start a fresh session |`,
|
|
72
|
+
`| ${displayAppKey("app.thinking.cycle")} | Cycle thinking level |`,
|
|
73
|
+
`| ${displayAppKey("app.model.cycleForward")} | Cycle configured model roles |`,
|
|
74
|
+
`| ${displayAppKey("app.model.cycleBackward")} | Cycle configured model roles temporarily |`,
|
|
75
|
+
`| ${displayAppKey("app.model.selectTemporary")} | Select model (temporary) |`,
|
|
76
|
+
`| ${displayAppKey("app.model.select")} | Select default model |`,
|
|
77
|
+
`| ${displayAppKey("app.plan.toggle")} | Toggle plan mode |`,
|
|
78
|
+
`| ${displayAppKey("app.history.search")} | Search prompt history |`,
|
|
79
|
+
`| ${displayAppKey("app.tools.expand")} | Toggle tool output expansion |`,
|
|
80
|
+
`| ${displayAppKey("app.tool.backgroundFold")} twice | Fold supported foreground bash into a background job |`,
|
|
81
|
+
`| ${displayAppKey("app.thinking.toggle")} | Toggle thinking block visibility |`,
|
|
82
|
+
`| ${displayAppKey("app.editor.external")} | Edit message in external editor |`,
|
|
83
|
+
`| ${displayAppKey("app.clipboard.pasteImage")} | Paste image from clipboard |`,
|
|
84
|
+
`| ${displayAppKey("app.stt.toggle")} | Toggle speech-to-text recording |`,
|
|
85
|
+
`| ${displayAppKey("app.irc.sidebar.toggle")} | Toggle IRC sidebar |`,
|
|
58
86
|
"| `?` | Show help / shortcuts |",
|
|
59
87
|
"| `#` | Prompt actions (command-palette style actions) |",
|
|
60
88
|
"| `/` | Slash commands (try `/help` or `/new`) |",
|
|
@@ -916,9 +916,11 @@ export class UiHelpers {
|
|
|
916
916
|
const queuedText = theme.fg("dim", `${entry.label}: ${entry.message}`);
|
|
917
917
|
this.ctx.pendingMessagesContainer.addChild(new TruncatedText(queuedText, 1, 0));
|
|
918
918
|
}
|
|
919
|
-
const dequeueKey = this.ctx.keybindings.getDisplayString("app.message.dequeue")
|
|
920
|
-
|
|
921
|
-
|
|
919
|
+
const dequeueKey = this.ctx.keybindings.getDisplayString("app.message.dequeue");
|
|
920
|
+
if (dequeueKey) {
|
|
921
|
+
const hintText = theme.fg("dim", `${theme.tree.hook} ${dequeueKey} to select/edit/reorder`);
|
|
922
|
+
this.ctx.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
923
|
+
}
|
|
922
924
|
}
|
|
923
925
|
}
|
|
924
926
|
|
|
@@ -33,7 +33,6 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
33
33
|
- Never approve CRITICAL or HIGH severity issues.
|
|
34
34
|
- Do not skip spec compliance to jump to style nitpicks.
|
|
35
35
|
- Be constructive: explain why an issue matters and how to fix it or strengthen the design.
|
|
36
|
-
- Practical sufficiency beats endless logical completeness: treat preemptive logic for edge cases with no observed or evidenced trigger as over-engineering, but never downgrade genuine CRITICAL/HIGH architecture, extensibility, or robustness risks that have observability-based evidence.
|
|
37
36
|
</constraints>
|
|
38
37
|
|
|
39
38
|
<review_stages>
|