@gajae-code/coding-agent 0.11.7 → 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 +55 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/config/keybindings.d.ts +33 -3
- 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 +15 -2
- 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/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 +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
- package/dist/types/main.d.ts +8 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +3 -2
- package/dist/types/modes/components/welcome.d.ts +2 -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 +5 -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 +3 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- 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 +5 -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 +25 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +13 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -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/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +11 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +17 -2
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-storage.d.ts +18 -0
- 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/image-gen.d.ts +47 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -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 +2 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +18 -0
- package/src/commands/sdk.ts +2 -2
- package/src/config/keybindings.ts +157 -24
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +17 -2
- 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/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/prelude.py +2 -2
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- 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 +5 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/internal-urls/local-protocol.ts +33 -16
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +25 -10
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/components/model-selector.ts +42 -8
- 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 +20 -4
- package/src/modes/components/welcome.ts +22 -18
- 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 +7 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +46 -54
- package/src/modes/interactive-mode.ts +41 -27
- 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 +3 -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/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/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 +13 -6
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +18 -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 +240 -65
- package/src/sdk/bus/lifecycle-commands.ts +47 -3
- 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 +27 -2
- 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 +13 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +835 -110
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/cli/session-cli.ts +15 -2
- 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 +47 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +2 -0
- package/src/sdk/startup-capability.ts +9 -1
- package/src/session/agent-session.ts +52 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +854 -84
- package/src/session/internal/managed-session-storage.ts +251 -199
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +310 -327
- package/src/session/session-storage.ts +128 -11
- 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 +198 -88
- 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/image-gen.ts +178 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/url-guard.ts +3 -1
- package/src/web/scrapers/docs-rs.ts +30 -5
|
@@ -5,7 +5,7 @@ import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
|
|
|
5
5
|
import type { Component, OverlayHandle, SlashCommand } from "@gajae-code/tui";
|
|
6
6
|
import { Input, isPetMode, Loader, Spacer, Text } from "@gajae-code/tui";
|
|
7
7
|
import { getAgentDbPath, getProjectDir, logger, VERSION } from "@gajae-code/utils";
|
|
8
|
-
import {
|
|
8
|
+
import type { AppKeybinding } from "../../config/keybindings";
|
|
9
9
|
import {
|
|
10
10
|
activateModelProfile,
|
|
11
11
|
type MaterializeModelProfileForDeletionResult,
|
|
@@ -64,7 +64,7 @@ import {
|
|
|
64
64
|
import type { NotificationSessionStatus } from "../../sdk/bus/session-control";
|
|
65
65
|
import {
|
|
66
66
|
ensureTelegramDaemonRunningDetailed,
|
|
67
|
-
|
|
67
|
+
resolveTelegramSetupPreflight,
|
|
68
68
|
unregisterNotificationRoot,
|
|
69
69
|
} from "../../sdk/bus/telegram-daemon";
|
|
70
70
|
import { TelegramDaemonController } from "../../sdk/bus/telegram-daemon-control";
|
|
@@ -226,6 +226,7 @@ export interface NotificationsEditorOperationDependencies {
|
|
|
226
226
|
sanitizeDiagnostic: typeof sanitizeDiagnostic;
|
|
227
227
|
ensureTelegramDaemonRunningDetailed: typeof ensureTelegramDaemonRunningDetailed;
|
|
228
228
|
runTelegramSetup: typeof runTelegramSetup;
|
|
229
|
+
resolveTelegramSetupPreflight: typeof resolveTelegramSetupPreflight;
|
|
229
230
|
proposedTelegramIdentity: typeof proposedTelegramIdentity;
|
|
230
231
|
reconcileCommittedTelegramConfiguration: typeof reconcileCommittedTelegramConfiguration;
|
|
231
232
|
saveTelegramInactive: typeof saveTelegramInactive;
|
|
@@ -250,6 +251,7 @@ const notificationEditorOperationDependencies: NotificationsEditorOperationDepen
|
|
|
250
251
|
sanitizeDiagnostic,
|
|
251
252
|
ensureTelegramDaemonRunningDetailed,
|
|
252
253
|
runTelegramSetup,
|
|
254
|
+
resolveTelegramSetupPreflight,
|
|
253
255
|
proposedTelegramIdentity,
|
|
254
256
|
reconcileCommittedTelegramConfiguration,
|
|
255
257
|
saveTelegramInactive,
|
|
@@ -306,33 +308,8 @@ export function createNotificationsEditorOperations(
|
|
|
306
308
|
cwd: ctx.sessionManager.getCwd(),
|
|
307
309
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
308
310
|
});
|
|
309
|
-
const telegramSetupPreflight = async (): Promise<TelegramSetupPreflight> =>
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
const state = await readDaemonState(ctx.settings);
|
|
313
|
-
const validPid = Number.isSafeInteger(state?.pid) && (state?.pid ?? 0) > 0;
|
|
314
|
-
if (!state || !validPid) return { storedChatId };
|
|
315
|
-
let live = false;
|
|
316
|
-
try {
|
|
317
|
-
process.kill(state.pid, 0);
|
|
318
|
-
live = true;
|
|
319
|
-
} catch (error) {
|
|
320
|
-
live = (error as NodeJS.ErrnoException).code === "EPERM";
|
|
321
|
-
}
|
|
322
|
-
return live
|
|
323
|
-
? {
|
|
324
|
-
storedChatId,
|
|
325
|
-
daemon: {
|
|
326
|
-
live,
|
|
327
|
-
tokenFingerprint: typeof state.tokenFingerprint === "string" ? state.tokenFingerprint : undefined,
|
|
328
|
-
chatId: typeof state.chatId === "string" && state.chatId.trim() ? state.chatId.trim() : undefined,
|
|
329
|
-
},
|
|
330
|
-
}
|
|
331
|
-
: { storedChatId };
|
|
332
|
-
} catch {
|
|
333
|
-
return { storedChatId };
|
|
334
|
-
}
|
|
335
|
-
};
|
|
311
|
+
const telegramSetupPreflight = async (): Promise<TelegramSetupPreflight> =>
|
|
312
|
+
await services.resolveTelegramSetupPreflight(ctx.settings);
|
|
336
313
|
|
|
337
314
|
return {
|
|
338
315
|
loadState: async () => {
|
|
@@ -838,7 +815,7 @@ export class SelectorController {
|
|
|
838
815
|
id: `action:${action.id}`,
|
|
839
816
|
label: action.label,
|
|
840
817
|
description: action.id,
|
|
841
|
-
keybinding:
|
|
818
|
+
keybinding: this.ctx.keybindings.getDisplayString(action.id as AppKeybinding) || undefined,
|
|
842
819
|
searchText: action.id,
|
|
843
820
|
handler: action.handler,
|
|
844
821
|
})),
|
|
@@ -1108,12 +1085,12 @@ export class SelectorController {
|
|
|
1108
1085
|
|
|
1109
1086
|
async #handleImageGenerationConfig(): Promise<void> {
|
|
1110
1087
|
const provider = await this.ctx.showHookInput(
|
|
1111
|
-
"Image Generation provider (auto, openai, gemini, openrouter, antigravity, custom)",
|
|
1088
|
+
"Image Generation provider (auto, openai, gemini, openrouter, antigravity, alibaba, custom)",
|
|
1112
1089
|
"auto",
|
|
1113
1090
|
);
|
|
1114
1091
|
if (provider === undefined) return;
|
|
1115
1092
|
const normalized = provider.trim().toLowerCase();
|
|
1116
|
-
const validProviders = ["auto", "openai", "gemini", "openrouter", "antigravity", "custom"];
|
|
1093
|
+
const validProviders = ["auto", "openai", "gemini", "openrouter", "antigravity", "alibaba", "custom"];
|
|
1117
1094
|
if (!validProviders.includes(normalized)) {
|
|
1118
1095
|
this.ctx.showStatus(`Invalid image provider: ${normalized}. Valid: ${validProviders.join(", ")}`);
|
|
1119
1096
|
return;
|
|
@@ -1145,7 +1122,14 @@ export class SelectorController {
|
|
|
1145
1122
|
if (scope === undefined) return;
|
|
1146
1123
|
const persistDefault = scope.trim().toLowerCase() === "default";
|
|
1147
1124
|
|
|
1148
|
-
const imageProvider = normalized as
|
|
1125
|
+
const imageProvider = normalized as
|
|
1126
|
+
| "auto"
|
|
1127
|
+
| "openai"
|
|
1128
|
+
| "gemini"
|
|
1129
|
+
| "openrouter"
|
|
1130
|
+
| "antigravity"
|
|
1131
|
+
| "alibaba"
|
|
1132
|
+
| "custom";
|
|
1149
1133
|
setPreferredImageProvider(imageProvider === "custom" ? "auto" : imageProvider);
|
|
1150
1134
|
setConfiguredImageModel({
|
|
1151
1135
|
provider: imageProvider,
|
|
@@ -1644,6 +1628,7 @@ export class SelectorController {
|
|
|
1644
1628
|
imgProvider === "gemini" ||
|
|
1645
1629
|
imgProvider === "openrouter" ||
|
|
1646
1630
|
imgProvider === "antigravity" ||
|
|
1631
|
+
imgProvider === "alibaba" ||
|
|
1647
1632
|
imgProvider === "custom"
|
|
1648
1633
|
) {
|
|
1649
1634
|
setPreferredImageProvider(imgProvider === "custom" ? "auto" : imgProvider);
|
|
@@ -1694,6 +1679,14 @@ export class SelectorController {
|
|
|
1694
1679
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
1695
1680
|
this.showSelector(done => {
|
|
1696
1681
|
let modelSelector: ModelSelectorComponent;
|
|
1682
|
+
const refreshRoleAssignments = () => {
|
|
1683
|
+
modelSelector.refreshRoleAssignments({
|
|
1684
|
+
currentModel: this.ctx.session.model,
|
|
1685
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
1686
|
+
activeModelProfile:
|
|
1687
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
1688
|
+
});
|
|
1689
|
+
};
|
|
1697
1690
|
modelSelector = new ModelSelectorComponent(
|
|
1698
1691
|
this.ctx.ui,
|
|
1699
1692
|
this.ctx.session.model,
|
|
@@ -1701,6 +1694,8 @@ export class SelectorController {
|
|
|
1701
1694
|
this.ctx.session.modelRegistry,
|
|
1702
1695
|
this.ctx.session.scopedModels,
|
|
1703
1696
|
async selection => {
|
|
1697
|
+
const isTrackedSingleAssignment =
|
|
1698
|
+
selection.kind === "assignment" && selection.role !== null && selection.roles === undefined;
|
|
1704
1699
|
try {
|
|
1705
1700
|
if (selection.kind === "createProfile") {
|
|
1706
1701
|
done();
|
|
@@ -1741,7 +1736,7 @@ export class SelectorController {
|
|
|
1741
1736
|
this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
|
|
1742
1737
|
done();
|
|
1743
1738
|
this.ctx.ui.requestRender();
|
|
1744
|
-
} else if (selection.roles) {
|
|
1739
|
+
} else if (selection.roles !== undefined) {
|
|
1745
1740
|
const targetRoles: readonly GjcModelAssignmentTargetId[] = selection.roles;
|
|
1746
1741
|
const includesDefault = targetRoles.includes("default");
|
|
1747
1742
|
const includesRoleAgent = targetRoles.some(targetRole => targetRole !== "default");
|
|
@@ -1819,25 +1814,23 @@ export class SelectorController {
|
|
|
1819
1814
|
selectedSelector ?? `${model.provider}/${model.id}`,
|
|
1820
1815
|
thinkingLevel,
|
|
1821
1816
|
);
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1817
|
+
if (
|
|
1818
|
+
!materializeActiveModelProfileAssignment({
|
|
1819
|
+
session: this.ctx.session,
|
|
1820
|
+
settings: this.ctx.settings,
|
|
1821
|
+
role,
|
|
1822
|
+
selector: value,
|
|
1823
|
+
})
|
|
1824
|
+
) {
|
|
1825
|
+
this.ctx.settings.setModelRole(role, value);
|
|
1826
|
+
}
|
|
1828
1827
|
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1829
1828
|
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
1830
1829
|
}
|
|
1831
|
-
|
|
1832
|
-
currentModel: this.ctx.session.model,
|
|
1833
|
-
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
1834
|
-
activeModelProfile:
|
|
1835
|
-
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
1836
|
-
});
|
|
1830
|
+
refreshRoleAssignments();
|
|
1837
1831
|
this.ctx.statusLine.invalidate();
|
|
1838
1832
|
this.ctx.updateEditorBorderColor();
|
|
1839
1833
|
this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
|
|
1840
|
-
done();
|
|
1841
1834
|
this.ctx.ui.requestRender();
|
|
1842
1835
|
} else {
|
|
1843
1836
|
const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
|
|
@@ -1860,22 +1853,21 @@ export class SelectorController {
|
|
|
1860
1853
|
this.ctx.settings.setAgentModelOverride(role, value);
|
|
1861
1854
|
}
|
|
1862
1855
|
}
|
|
1863
|
-
|
|
1864
|
-
currentModel: this.ctx.session.model,
|
|
1865
|
-
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
1866
|
-
activeModelProfile:
|
|
1867
|
-
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
1868
|
-
});
|
|
1856
|
+
refreshRoleAssignments();
|
|
1869
1857
|
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
1870
1858
|
this.ctx.statusLine.invalidate();
|
|
1871
1859
|
this.ctx.updateEditorBorderColor();
|
|
1872
1860
|
await this.ctx.notifyConfigChanged?.();
|
|
1873
1861
|
this.ctx.showStatus(`${role} agent model: ${value}`);
|
|
1874
|
-
done();
|
|
1875
1862
|
this.ctx.ui.requestRender();
|
|
1876
1863
|
}
|
|
1877
1864
|
} catch (error) {
|
|
1878
1865
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
1866
|
+
if (isTrackedSingleAssignment) {
|
|
1867
|
+
refreshRoleAssignments();
|
|
1868
|
+
this.ctx.ui.requestRender();
|
|
1869
|
+
throw error;
|
|
1870
|
+
}
|
|
1879
1871
|
}
|
|
1880
1872
|
},
|
|
1881
1873
|
() => {
|
|
@@ -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);
|
|
@@ -340,6 +337,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
340
337
|
fileSlashCommands: Set<string> = new Set();
|
|
341
338
|
skillCommands: Map<string, Skill> = new Map();
|
|
342
339
|
oauthManualInput: OAuthManualInputManager = new OAuthManualInputManager();
|
|
340
|
+
promptSuggestion: PromptSuggestionController;
|
|
343
341
|
|
|
344
342
|
#baseSlashCommands: SlashCommand[] = [];
|
|
345
343
|
#resolvedSlashCommands: SlashCommand[] = [];
|
|
@@ -351,6 +349,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
351
349
|
#petUnavailableWarningDisposer?: () => void;
|
|
352
350
|
readonly #version: string;
|
|
353
351
|
readonly #changelogMarkdown: string | undefined;
|
|
352
|
+
readonly #keyDisplayContext: KeyDisplayContext;
|
|
354
353
|
|
|
355
354
|
readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
|
|
356
355
|
mcpManager?: import("../runtime-mcp").MCPManager;
|
|
@@ -398,6 +397,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
398
397
|
lspServers: LspStartupServerInfo[] | undefined = undefined,
|
|
399
398
|
mcpManager?: import("../runtime-mcp").MCPManager,
|
|
400
399
|
eventBus?: EventBus,
|
|
400
|
+
keyDisplayContext: KeyDisplayContext = { platform: process.platform },
|
|
401
401
|
) {
|
|
402
402
|
this.session = session;
|
|
403
403
|
this.sessionManager = session.sessionManager;
|
|
@@ -436,6 +436,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
436
436
|
this.lspServers = lspServers;
|
|
437
437
|
this.mcpManager = mcpManager;
|
|
438
438
|
this.#eventBus = eventBus;
|
|
439
|
+
this.#keyDisplayContext = keyDisplayContext;
|
|
440
|
+
this.keybindings.setDisplayContext(this.#keyDisplayContext);
|
|
439
441
|
const thisMode = this;
|
|
440
442
|
this.#goalModeController = new GoalModeController({
|
|
441
443
|
session: this.session,
|
|
@@ -517,6 +519,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
517
519
|
this.btwContainer = new Container();
|
|
518
520
|
this.editor = new CustomEditor(getEditorTheme());
|
|
519
521
|
configureDefaultComposerChrome(this.editor);
|
|
522
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
520
523
|
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
521
524
|
this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible"));
|
|
522
525
|
this.editor.onAutocompleteCancel = () => {
|
|
@@ -544,7 +547,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
544
547
|
this.hookWidgetContainerBelow = new Container();
|
|
545
548
|
this.editorContainer = new Container();
|
|
546
549
|
this.editorContainer.addChild(this.editor);
|
|
547
|
-
this.statusLine = new StatusLineComponent(session, {
|
|
550
|
+
this.statusLine = new StatusLineComponent(session, {
|
|
551
|
+
version: this.#version,
|
|
552
|
+
focusDomain: "composer",
|
|
553
|
+
keyDisplayContext: this.#keyDisplayContext,
|
|
554
|
+
});
|
|
548
555
|
this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
549
556
|
|
|
550
557
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
@@ -580,6 +587,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
580
587
|
this.#selectorController = new SelectorController(this);
|
|
581
588
|
this.#inputController = new InputController(this);
|
|
582
589
|
this.statusLine.setActionRegistry(this.#inputController.actionRegistry, () => this.keybindings);
|
|
590
|
+
this.promptSuggestion = new PromptSuggestionController(this);
|
|
583
591
|
this.#observerRegistry = new SessionObserverRegistry();
|
|
584
592
|
}
|
|
585
593
|
|
|
@@ -600,6 +608,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
600
608
|
if (this.isInitialized) return;
|
|
601
609
|
|
|
602
610
|
this.keybindings = logger.time("InteractiveMode.init:keybindings", () => KeybindingsManager.create());
|
|
611
|
+
this.keybindings.setDisplayContext(this.#keyDisplayContext);
|
|
603
612
|
|
|
604
613
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
605
614
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
@@ -657,6 +666,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
657
666
|
getReservedBottomRows: getWelcomeReservedBottomRows,
|
|
658
667
|
changelogMarkdown: this.#changelogMarkdown,
|
|
659
668
|
collapseChangelog: settings.get("collapseChangelog"),
|
|
669
|
+
keyDisplayContext: this.#keyDisplayContext,
|
|
660
670
|
},
|
|
661
671
|
);
|
|
662
672
|
|
|
@@ -807,6 +817,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
807
817
|
onThemeChange(() => {
|
|
808
818
|
clearRenderCache();
|
|
809
819
|
configureDefaultComposerChrome(this.editor);
|
|
820
|
+
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
810
821
|
this.ui.invalidate();
|
|
811
822
|
this.updateEditorChrome();
|
|
812
823
|
this.ui.requestRender();
|
|
@@ -1038,12 +1049,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1038
1049
|
}
|
|
1039
1050
|
|
|
1040
1051
|
#getComposerPlaceholder(): string {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
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}`] : [];
|
|
1044
1057
|
const queueKey = this.#getMessageQueueShortcut();
|
|
1045
|
-
if (queueKey) parts.push(`${
|
|
1046
|
-
return `${
|
|
1058
|
+
if (queueKey) parts.push(`${formatKeyHint(queueKey, this.#keyDisplayContext)}: Queue`);
|
|
1059
|
+
return parts.length > 0 ? `${defaultPlaceholder} · ${parts.join(" · ")}` : defaultPlaceholder;
|
|
1047
1060
|
}
|
|
1048
1061
|
|
|
1049
1062
|
#getWelcomeReservedRows(width: number): number {
|
|
@@ -1398,6 +1411,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1398
1411
|
: new CustomEditor(getEditorTheme());
|
|
1399
1412
|
|
|
1400
1413
|
configureDefaultComposerChrome(nextEditor);
|
|
1414
|
+
nextEditor.setPlaceholder(this.#getComposerPlaceholder());
|
|
1401
1415
|
nextEditor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
1402
1416
|
nextEditor.setAutocompleteMaxVisible(this.settings.get("autocompleteMaxVisible"));
|
|
1403
1417
|
nextEditor.onAutocompleteCancel = () => {
|
|
@@ -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>;
|