@gajae-code/coding-agent 0.7.2 → 0.7.4
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 +86 -0
- package/bin/gjc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/extensibility/gjc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/gjc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/gjc-plugins/validation.d.ts +8 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +20 -1
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +72 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +181 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli.ts +27 -6
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +55 -7
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/gjc/skills/team/SKILL.md +5 -3
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/gjc-plugins/compiler.ts +351 -0
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/gjc-plugins/index.ts +9 -0
- package/src/extensibility/gjc-plugins/injection.ts +109 -0
- package/src/extensibility/gjc-plugins/installer.ts +434 -0
- package/src/extensibility/gjc-plugins/loader.ts +3 -1
- package/src/extensibility/gjc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/gjc-plugins/observability.ts +84 -0
- package/src/extensibility/gjc-plugins/paths.ts +1 -1
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/gjc-plugins/registry.ts +180 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/gjc-plugins/schema.ts +250 -20
- package/src/extensibility/gjc-plugins/session-validation.ts +147 -0
- package/src/extensibility/gjc-plugins/types.ts +199 -3
- package/src/extensibility/gjc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/gjc-runtime/launch-tmux.ts +61 -7
- package/src/gjc-runtime/psmux-detect.ts +239 -0
- package/src/gjc-runtime/team-runtime.ts +56 -23
- package/src/gjc-runtime/tmux-common.ts +30 -3
- package/src/gjc-runtime/tmux-sessions.ts +51 -1
- package/src/gjc-runtime/ultragoal-guard.ts +25 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +75 -15
- package/src/hooks/skill-state.ts +57 -0
- package/src/internal-urls/docs-index.generated.ts +12 -8
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +56 -11
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +53 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +228 -0
- package/src/notifications/lifecycle-control-runtime.ts +400 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +157 -10
- package/src/session/agent-session.ts +166 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +678 -7
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +3 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
package/src/main.ts
CHANGED
|
@@ -52,7 +52,7 @@ import { formatModelOnboardingGuidance } from "./setup/model-onboarding-guidance
|
|
|
52
52
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
53
53
|
import { resolvePromptInput } from "./system-prompt";
|
|
54
54
|
import type { LspStartupServerInfo } from "./tools";
|
|
55
|
-
import { getDisplayChangelogEntries, getNewEntries } from "./utils/changelog";
|
|
55
|
+
import { getDisplayChangelogEntries, getInstalledVersionChangelogEntry, getNewEntries } from "./utils/changelog";
|
|
56
56
|
import type { EventBus } from "./utils/event-bus";
|
|
57
57
|
|
|
58
58
|
async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
|
|
@@ -407,7 +407,7 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
|
|
|
407
407
|
if (entries.length > 0) {
|
|
408
408
|
settings.set("lastChangelogVersion", VERSION);
|
|
409
409
|
await flushChangelogVersion();
|
|
410
|
-
return entries
|
|
410
|
+
return getInstalledVersionChangelogEntry(entries, VERSION)?.content;
|
|
411
411
|
}
|
|
412
412
|
} else {
|
|
413
413
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
@@ -429,7 +429,7 @@ async function flushChangelogVersion(): Promise<void> {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
async function createSessionManager(
|
|
432
|
+
export async function createSessionManager(
|
|
433
433
|
parsed: Args,
|
|
434
434
|
cwd: string,
|
|
435
435
|
activeSettings: Settings = settings,
|
|
@@ -482,6 +482,17 @@ async function createSessionManager(
|
|
|
482
482
|
if (parsed.sessionDir) {
|
|
483
483
|
return SessionManager.create(cwd, parsed.sessionDir);
|
|
484
484
|
}
|
|
485
|
+
// A lifecycle `/session_create` child must start a FRESH session that adopts
|
|
486
|
+
// the pre-allocated id (GJC_SESSION_ID), never auto-resume existing history in
|
|
487
|
+
// the target cwd — otherwise the daemon/tmux id and the session header id
|
|
488
|
+
// diverge and close/resume-by-create-id break. Resume children are launched
|
|
489
|
+
// with `--resume <id>` (handled above) and carry no GJC_LIFECYCLE_REQUEST_ID.
|
|
490
|
+
if (
|
|
491
|
+
process.env.GJC_LIFECYCLE_REQUEST_ID &&
|
|
492
|
+
/^[A-Za-z0-9._-]{1,128}$/.test(process.env.GJC_SESSION_ID?.trim() ?? "")
|
|
493
|
+
) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
485
496
|
// Auto-resume: behave like --continue if the setting is enabled and a prior
|
|
486
497
|
// session exists. When a prior session is resumed, mark parsed.continue so
|
|
487
498
|
// buildSessionOptions restores the session's model/thinking instead of
|
|
@@ -153,6 +153,14 @@ function jsonResponse(status: number, body: unknown): Response {
|
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function isBridgeControllerOwner(options: BridgeFetchHandlerOptions, ownerToken: string): boolean {
|
|
157
|
+
if (!ownerToken) return false;
|
|
158
|
+
const ownerTokens = [options.permissionBroker?.ownerToken, options.uiBroker?.ownerToken].filter(
|
|
159
|
+
(token): token is string => typeof token === "string" && token.length > 0,
|
|
160
|
+
);
|
|
161
|
+
return ownerTokens.length > 0 && ownerTokens.every(token => token === ownerToken);
|
|
162
|
+
}
|
|
163
|
+
|
|
156
164
|
function parseBridgeScopes(value: string | undefined): readonly BridgeCommandScope[] {
|
|
157
165
|
if (!value?.trim()) return DEFAULT_BRIDGE_SCOPES;
|
|
158
166
|
const allowed = new Set(BRIDGE_COMMAND_SCOPES);
|
|
@@ -425,6 +433,9 @@ export function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (r
|
|
|
425
433
|
"answer" in payload &&
|
|
426
434
|
(correlationId === (payload as RpcWorkflowGateResponse).gate_id || correlationId.startsWith("wg_"))
|
|
427
435
|
) {
|
|
436
|
+
if (!isBridgeControllerOwner(options, ownerToken)) {
|
|
437
|
+
return jsonResponse(403, { status: "rejected", code: "not_controller" });
|
|
438
|
+
}
|
|
428
439
|
try {
|
|
429
440
|
const resolution = await options.unattendedControlPlane?.resolveGate({
|
|
430
441
|
gate_id: (payload as RpcWorkflowGateResponse).gate_id,
|
|
@@ -18,6 +18,7 @@ type ConfigurableEditorAction = Extract<
|
|
|
18
18
|
| "app.editor.external"
|
|
19
19
|
| "app.history.search"
|
|
20
20
|
| "app.message.dequeue"
|
|
21
|
+
| "app.message.followUp"
|
|
21
22
|
| "app.message.queue"
|
|
22
23
|
| "app.clipboard.pasteImage"
|
|
23
24
|
| "app.clipboard.copyPrompt"
|
|
@@ -40,6 +41,7 @@ const CONFIGURABLE_EDITOR_ACTIONS = [
|
|
|
40
41
|
"app.thinking.toggle",
|
|
41
42
|
"app.editor.external",
|
|
42
43
|
"app.history.search",
|
|
44
|
+
"app.message.followUp",
|
|
43
45
|
"app.message.queue",
|
|
44
46
|
"app.message.dequeue",
|
|
45
47
|
"app.clipboard.pasteImage",
|
|
@@ -8,6 +8,7 @@ import { shortenPath } from "../../tools/render-utils";
|
|
|
8
8
|
import * as git from "../../utils/git";
|
|
9
9
|
import { sanitizeStatusText } from "../shared";
|
|
10
10
|
import { getContextUsageLevel, getContextUsageThemeColor } from "./status-line/context-thresholds";
|
|
11
|
+
import { resolveCurrentBranch } from "./status-line/git-utils";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Footer component that shows pwd, token stats, and context usage
|
|
@@ -103,9 +104,7 @@ export class FooterComponent implements Component {
|
|
|
103
104
|
return this.#cachedBranch;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
this.#cachedBranch =
|
|
108
|
-
headState === null ? null : headState.kind === "ref" ? (headState.branchName ?? headState.ref) : "detached";
|
|
107
|
+
this.#cachedBranch = resolveCurrentBranch(getProjectDir()).branch;
|
|
109
108
|
return this.#cachedBranch;
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -66,7 +66,7 @@ export class HookEditorComponent extends Container {
|
|
|
66
66
|
|
|
67
67
|
// Hint
|
|
68
68
|
const hint = this.#promptStyle
|
|
69
|
-
? "enter submit esc cancel ctrl+g external editor"
|
|
69
|
+
? "enter submit shift+enter/ctrl+j newline esc cancel ctrl+g external editor"
|
|
70
70
|
: "ctrl+enter submit esc cancel ctrl+g external editor";
|
|
71
71
|
this.addChild(new Text(theme.fg("dim", hint), 1, 0));
|
|
72
72
|
|
|
@@ -28,22 +28,6 @@ import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
|
28
28
|
import { CountdownTimer } from "./countdown-timer";
|
|
29
29
|
import { DynamicBorder } from "./dynamic-border";
|
|
30
30
|
|
|
31
|
-
const SGR_MOUSE_PRESS_PATTERN = /^\x1b\[<(\d+);\d+;\d+M$/;
|
|
32
|
-
const MOUSE_WHEEL_TITLE_SCROLL_ROWS = 3;
|
|
33
|
-
|
|
34
|
-
function getMouseWheelTitleScrollRows(keyData: string): number {
|
|
35
|
-
const match = SGR_MOUSE_PRESS_PATTERN.exec(keyData);
|
|
36
|
-
if (!match) return 0;
|
|
37
|
-
|
|
38
|
-
const button = Number.parseInt(match[1] ?? "", 10);
|
|
39
|
-
if (!Number.isFinite(button) || (button & 64) === 0) return 0;
|
|
40
|
-
|
|
41
|
-
const wheelDirection = button & 3;
|
|
42
|
-
if (wheelDirection === 0) return -MOUSE_WHEEL_TITLE_SCROLL_ROWS;
|
|
43
|
-
if (wheelDirection === 1) return MOUSE_WHEEL_TITLE_SCROLL_ROWS;
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
31
|
export interface HookSelectorOptions {
|
|
48
32
|
tui?: TUI;
|
|
49
33
|
timeout?: number;
|
|
@@ -132,26 +116,58 @@ class ScrollableTitle extends Container {
|
|
|
132
116
|
|
|
133
117
|
render(width: number): string[] {
|
|
134
118
|
const lines = this.#markdown.render(width);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
119
|
+
if (lines.length <= this.#maxRows) {
|
|
120
|
+
this.#lastMaxScrollOffset = 0;
|
|
121
|
+
this.#scrollOffset = 0;
|
|
122
|
+
return lines;
|
|
123
|
+
}
|
|
138
124
|
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
if (this.#maxRows < 3) {
|
|
126
|
+
const maxScrollOffset = Math.max(0, lines.length - this.#maxRows);
|
|
127
|
+
this.#lastMaxScrollOffset = maxScrollOffset;
|
|
128
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#scrollOffset, maxScrollOffset));
|
|
129
|
+
|
|
130
|
+
const visibleLines = lines.slice(this.#scrollOffset, this.#scrollOffset + this.#maxRows);
|
|
131
|
+
const indicator =
|
|
132
|
+
this.#scrollOffset === 0
|
|
133
|
+
? theme.fg("dim", " PgDn↓")
|
|
134
|
+
: this.#scrollOffset >= maxScrollOffset
|
|
135
|
+
? theme.fg("dim", " PgUp↑")
|
|
136
|
+
: theme.fg("dim", " PgUp/PgDn↕");
|
|
137
|
+
const lastIndex = visibleLines.length - 1;
|
|
138
|
+
const availableWidth = Math.max(1, width - visibleWidth(indicator));
|
|
139
|
+
const fittedLine = truncateToWidth(visibleLines[lastIndex] ?? "", availableWidth);
|
|
140
|
+
visibleLines[lastIndex] = `${fittedLine}${indicator}`;
|
|
141
141
|
return visibleLines;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
let showTopIndicator = this.#scrollOffset > 0;
|
|
145
|
+
let showBottomIndicator = true;
|
|
146
|
+
let contentRows = 1;
|
|
147
|
+
let maxScrollOffset = 0;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < 4; i++) {
|
|
150
|
+
contentRows = Math.max(1, this.#maxRows - (showTopIndicator ? 1 : 0) - (showBottomIndicator ? 1 : 0));
|
|
151
|
+
maxScrollOffset = Math.max(0, lines.length - contentRows);
|
|
152
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#scrollOffset, maxScrollOffset));
|
|
153
|
+
|
|
154
|
+
const nextShowTopIndicator = this.#scrollOffset > 0;
|
|
155
|
+
const nextShowBottomIndicator = this.#scrollOffset + contentRows < lines.length;
|
|
156
|
+
if (nextShowTopIndicator === showTopIndicator && nextShowBottomIndicator === showBottomIndicator) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
showTopIndicator = nextShowTopIndicator;
|
|
160
|
+
showBottomIndicator = nextShowBottomIndicator;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.#lastMaxScrollOffset = maxScrollOffset;
|
|
164
|
+
|
|
165
|
+
const visibleLines = lines.slice(this.#scrollOffset, this.#scrollOffset + contentRows);
|
|
166
|
+
const result: string[] = [];
|
|
167
|
+
if (showTopIndicator) result.push(theme.fg("dim", truncateToWidth("▲ more", width)));
|
|
168
|
+
result.push(...visibleLines);
|
|
169
|
+
if (showBottomIndicator) result.push(theme.fg("dim", truncateToWidth("▼ more", width)));
|
|
170
|
+
return result.slice(0, this.#maxRows);
|
|
155
171
|
}
|
|
156
172
|
}
|
|
157
173
|
|
|
@@ -454,13 +470,6 @@ export class HookSelectorComponent extends Container {
|
|
|
454
470
|
// Reset countdown on any interaction
|
|
455
471
|
this.#countdown?.reset();
|
|
456
472
|
|
|
457
|
-
if (this.#scrollTitleRows !== undefined) {
|
|
458
|
-
const wheelRows = getMouseWheelTitleScrollRows(keyData);
|
|
459
|
-
if (wheelRows !== 0) {
|
|
460
|
-
this.#scrollableTitle?.scrollBy(wheelRows);
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
473
|
if (this.#scrollTitleRows !== undefined && matchesKey(keyData, "pageUp")) {
|
|
465
474
|
this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
|
|
466
475
|
return;
|
|
@@ -469,6 +478,14 @@ export class HookSelectorComponent extends Container {
|
|
|
469
478
|
this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
|
|
470
479
|
return;
|
|
471
480
|
}
|
|
481
|
+
if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+u")) {
|
|
482
|
+
this.#scrollableTitle?.scrollBy(-this.#scrollTitleRows);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (!this.#inlineEditor && this.#scrollTitleRows !== undefined && matchesKey(keyData, "ctrl+d")) {
|
|
486
|
+
this.#scrollableTitle?.scrollBy(this.#scrollTitleRows);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
472
489
|
if (this.#inlineEditor) {
|
|
473
490
|
this.#handleInputModeKey(keyData, this.#inlineEditor);
|
|
474
491
|
return;
|
|
@@ -537,16 +554,23 @@ export class HookSelectorComponent extends Container {
|
|
|
537
554
|
editor.setBorderVisible(false);
|
|
538
555
|
editor.setPromptGutter("> ");
|
|
539
556
|
editor.disableSubmit = true;
|
|
557
|
+
// Mark the inline editor focused only when mirroring the app's hardware-cursor
|
|
558
|
+
// mode, so it emits CURSOR_MARKER at the input caret for IME preedit anchoring
|
|
559
|
+
// without changing legacy non-hardware-cursor layout.
|
|
560
|
+
const useTerminalCursor = this.#tui?.getShowHardwareCursor() ?? false;
|
|
561
|
+
editor.focused = useTerminalCursor;
|
|
562
|
+
editor.setUseTerminalCursor(useTerminalCursor);
|
|
540
563
|
if (this.#autocompleteProvider) {
|
|
541
564
|
editor.setAutocompleteProvider(this.#autocompleteProvider);
|
|
542
565
|
}
|
|
543
566
|
this.#inlineEditor = editor;
|
|
544
567
|
this.#inputArea.addChild(new Spacer(1));
|
|
545
568
|
this.#inputArea.addChild(editor);
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
569
|
+
const helpText =
|
|
570
|
+
this.#scrollTitleRows === undefined
|
|
571
|
+
? "enter submit esc back to options ctrl+g external editor"
|
|
572
|
+
: "enter submit esc back to options PgUp/PgDn: question · Wheel: transcript";
|
|
573
|
+
this.#helpTextComponent.setText(theme.fg("dim", helpText));
|
|
550
574
|
this.invalidate();
|
|
551
575
|
}
|
|
552
576
|
|
|
@@ -221,8 +221,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
221
221
|
#scopedModels: ReadonlyArray<ScopedModelItem>;
|
|
222
222
|
#temporaryOnly: boolean;
|
|
223
223
|
#currentModel?: Model;
|
|
224
|
+
#currentThinkingLevel?: ThinkingLevel;
|
|
225
|
+
#activeModelProfile?: string;
|
|
224
226
|
#isFastForProvider: (provider?: string) => boolean = () => false;
|
|
225
227
|
#isFastForSubagentProvider: (provider?: string) => boolean = () => false;
|
|
228
|
+
#isCurrentModelFastModeActive: () => boolean = () => false;
|
|
226
229
|
#pendingActionItem?: ModelItem | CanonicalModelItem;
|
|
227
230
|
#selectedActionIndex: number = 0;
|
|
228
231
|
#pendingThinkingChoice?: PendingThinkingChoice;
|
|
@@ -258,6 +261,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
258
261
|
sessionId?: string;
|
|
259
262
|
isFastForProvider?: (provider?: string) => boolean;
|
|
260
263
|
isFastForSubagentProvider?: (provider?: string) => boolean;
|
|
264
|
+
isCurrentModelFastModeActive?: () => boolean;
|
|
265
|
+
currentThinkingLevel?: ThinkingLevel;
|
|
266
|
+
activeModelProfile?: string;
|
|
261
267
|
},
|
|
262
268
|
) {
|
|
263
269
|
super();
|
|
@@ -271,8 +277,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
271
277
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
272
278
|
this.#authSessionId = options?.sessionId;
|
|
273
279
|
this.#currentModel = _currentModel;
|
|
280
|
+
this.#currentThinkingLevel = options?.currentThinkingLevel;
|
|
281
|
+
this.#activeModelProfile = options?.activeModelProfile;
|
|
274
282
|
this.#isFastForProvider = options?.isFastForProvider ?? (() => false);
|
|
275
283
|
this.#isFastForSubagentProvider = options?.isFastForSubagentProvider ?? (() => false);
|
|
284
|
+
// Current-model EFFECTIVE fast state. Defaults to intent for the current
|
|
285
|
+
// model so existing callers/tests keep prior behavior; production wires the
|
|
286
|
+
// session's effective predicate so an auto-disabled provider shows no glyph.
|
|
287
|
+
this.#isCurrentModelFastModeActive =
|
|
288
|
+
options?.isCurrentModelFastModeActive ??
|
|
289
|
+
(() => (this.#currentModel ? this.#isFastForProvider(this.#currentModel.provider) : false));
|
|
276
290
|
const initialSearchInput = options?.initialSearchInput;
|
|
277
291
|
this.#viewMode = this.#temporaryOnly || initialSearchInput || scopedModels.length > 0 ? "models" : "presets";
|
|
278
292
|
|
|
@@ -367,6 +381,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
367
381
|
};
|
|
368
382
|
}
|
|
369
383
|
}
|
|
384
|
+
if (this.#activeModelProfile && this.#currentModel) {
|
|
385
|
+
this.#roles.default = {
|
|
386
|
+
model: this.#currentModel,
|
|
387
|
+
thinkingLevel: this.#currentThinkingLevel ?? ThinkingLevel.Inherit,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
refreshRoleAssignments(
|
|
393
|
+
options: { currentModel?: Model; currentThinkingLevel?: ThinkingLevel; activeModelProfile?: string } = {},
|
|
394
|
+
): void {
|
|
395
|
+
if ("currentModel" in options) this.#currentModel = options.currentModel;
|
|
396
|
+
if ("currentThinkingLevel" in options) this.#currentThinkingLevel = options.currentThinkingLevel;
|
|
397
|
+
if ("activeModelProfile" in options) this.#activeModelProfile = options.activeModelProfile;
|
|
398
|
+
this.#roles = {};
|
|
399
|
+
this.#loadRoleModels();
|
|
400
|
+
this.#applyTabFilter();
|
|
370
401
|
}
|
|
371
402
|
|
|
372
403
|
#sortModels(models: ModelItem[]): void {
|
|
@@ -961,32 +992,46 @@ export class ModelSelectorComponent extends Container {
|
|
|
961
992
|
|
|
962
993
|
// Build role badges (inverted: color as background, black text)
|
|
963
994
|
const roleBadgeTokens: string[] = [];
|
|
964
|
-
|
|
995
|
+
// Whether a non-subagent (modelRoles) badge on the CURRENT model row already
|
|
996
|
+
// rendered the current-model EFFECTIVE glyph. Only that case should suppress
|
|
997
|
+
// the standalone current glyph below — a subagent-only match must NOT, since
|
|
998
|
+
// subagent badges reflect the subagent tier, not the current model.
|
|
999
|
+
let currentModelEffectiveGlyphRendered = false;
|
|
965
1000
|
for (const role of GJC_MODEL_ASSIGNMENT_TARGET_IDS) {
|
|
966
1001
|
const roleInfo = GJC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
967
1002
|
const assigned = this.#roles[role];
|
|
968
1003
|
if (roleInfo.tag && assigned && modelsAreEqual(assigned.model, item.model)) {
|
|
969
|
-
roleMatched = true;
|
|
970
1004
|
const badge = makeInvertedBadge(roleInfo.tag, roleInfo.color ?? "muted");
|
|
971
1005
|
const thinkingLabel = getThinkingLevelMetadata(assigned.thinkingLevel).label;
|
|
972
|
-
|
|
973
|
-
//
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1006
|
+
|
|
1007
|
+
// Subagent roles (task.agentModelOverrides) run under task.serviceTier,
|
|
1008
|
+
// so their ⚡ uses the effective subagent tier. A non-subagent
|
|
1009
|
+
// (modelRoles) badge on the CURRENT model row uses the current-model
|
|
1010
|
+
// effective predicate so a provider auto-disable hides the glyph;
|
|
1011
|
+
// other modelRoles rows show pure intent.
|
|
1012
|
+
const isSubagentRole = roleInfo.settingsPath === "task.agentModelOverrides";
|
|
1013
|
+
const isCurrentRow = this.#currentModel !== undefined && modelsAreEqual(this.#currentModel, item.model);
|
|
1014
|
+
const roleFast = isSubagentRole
|
|
1015
|
+
? this.#isFastForSubagentProvider(assigned.model.provider)
|
|
1016
|
+
: isCurrentRow
|
|
1017
|
+
? this.#isCurrentModelFastModeActive()
|
|
977
1018
|
: this.#isFastForProvider(assigned.model.provider);
|
|
1019
|
+
if (roleFast && isCurrentRow && !isSubagentRole) {
|
|
1020
|
+
currentModelEffectiveGlyphRendered = true;
|
|
1021
|
+
}
|
|
978
1022
|
const fastSuffix = roleFast ? ` ${theme.icon.fast}` : "";
|
|
979
1023
|
roleBadgeTokens.push(`${badge} ${theme.fg("dim", `(${thinkingLabel})`)}${fastSuffix}`);
|
|
980
1024
|
}
|
|
981
1025
|
}
|
|
982
1026
|
// Active/current non-role row: show the fast glyph on the session's current
|
|
983
|
-
// model row
|
|
984
|
-
//
|
|
1027
|
+
// model row. Suppress only when a non-subagent current-row badge already
|
|
1028
|
+
// rendered the current-model effective glyph (duplicate-glyph guard) — a
|
|
1029
|
+
// subagent-only match must not hide the current model's own indicator.
|
|
985
1030
|
if (
|
|
986
|
-
!
|
|
1031
|
+
!currentModelEffectiveGlyphRendered &&
|
|
987
1032
|
this.#currentModel !== undefined &&
|
|
988
1033
|
modelsAreEqual(this.#currentModel, item.model) &&
|
|
989
|
-
this.#
|
|
1034
|
+
this.#isCurrentModelFastModeActive()
|
|
990
1035
|
) {
|
|
991
1036
|
roleBadgeTokens.push(theme.icon.fast);
|
|
992
1037
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { GitHeadState } from "../../../utils/git";
|
|
2
|
+
import * as git from "../../../utils/git";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Extract "owner/repo" from a GitHub remote URL.
|
|
3
6
|
* Handles HTTPS, SSH (scp-style), and git:// protocols.
|
|
@@ -40,3 +43,25 @@ export function canReuseCachedPr(
|
|
|
40
43
|
): boolean {
|
|
41
44
|
return cachedPr !== undefined && currentContext !== null && isSamePrCacheContext(cachedContext, currentContext);
|
|
42
45
|
}
|
|
46
|
+
|
|
47
|
+
export interface CurrentBranchState {
|
|
48
|
+
readonly branch: string | null;
|
|
49
|
+
readonly repoId: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveCurrentBranch(
|
|
53
|
+
cwd: string,
|
|
54
|
+
resolveHead: (cwd: string) => GitHeadState | null = git.head.resolveSync,
|
|
55
|
+
): CurrentBranchState {
|
|
56
|
+
try {
|
|
57
|
+
const head = resolveHead(cwd);
|
|
58
|
+
if (!head) return { branch: null, repoId: null };
|
|
59
|
+
return {
|
|
60
|
+
branch: head.kind === "ref" ? (head.branchName ?? head.ref) : "detached",
|
|
61
|
+
repoId: head.headPath,
|
|
62
|
+
};
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error instanceof Error) return { branch: null, repoId: null };
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
createPrCacheContext,
|
|
21
21
|
isSamePrCacheContext,
|
|
22
22
|
type PrCacheContext,
|
|
23
|
+
resolveCurrentBranch,
|
|
23
24
|
} from "./status-line/git-utils";
|
|
24
25
|
import { getPreset } from "./status-line/presets";
|
|
25
26
|
import { renderSegment, type SegmentContext } from "./status-line/segments";
|
|
@@ -303,19 +304,13 @@ export class StatusLineComponent implements Component {
|
|
|
303
304
|
this.#cachedPrContext = undefined;
|
|
304
305
|
}
|
|
305
306
|
#getCurrentBranch(): string | null {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === gitHeadPath) {
|
|
307
|
+
const current = resolveCurrentBranch(getProjectDir());
|
|
308
|
+
if (this.#cachedBranch !== undefined && this.#cachedBranchRepoId === current.repoId) {
|
|
309
309
|
return this.#cachedBranch;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
this.#cachedBranchRepoId =
|
|
313
|
-
|
|
314
|
-
this.#cachedBranch = null;
|
|
315
|
-
return null;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
this.#cachedBranch = head.kind === "ref" ? (head.branchName ?? head.ref) : "detached";
|
|
312
|
+
this.#cachedBranchRepoId = current.repoId;
|
|
313
|
+
this.#cachedBranch = current.branch;
|
|
319
314
|
|
|
320
315
|
return this.#cachedBranch ?? null;
|
|
321
316
|
}
|
|
@@ -680,7 +675,11 @@ export class StatusLineComponent implements Component {
|
|
|
680
675
|
const effectiveSettings = this.#resolveSettings();
|
|
681
676
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
682
677
|
|
|
683
|
-
|
|
678
|
+
// Use the subtle surface tone (the same elevated background as user-message
|
|
679
|
+
// bubbles) instead of the heavy `statusLineBg` block, so the rail layers
|
|
680
|
+
// just above the base background as a quiet zone rather than a solid bar.
|
|
681
|
+
// Resolving through a semantic slot keeps it correct across every theme.
|
|
682
|
+
const bgAnsi = theme.getBgAnsi("userMessageBg");
|
|
684
683
|
const fgAnsi = theme.getFgAnsi("text");
|
|
685
684
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
686
685
|
|
|
@@ -74,9 +74,8 @@ export class WelcomeComponent implements Component {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
render(termWidth: number): string[] {
|
|
77
|
-
// Box dimensions
|
|
78
|
-
const
|
|
79
|
-
const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
|
|
77
|
+
// Box dimensions track the live viewport so wide terminals feel intentionally full-screen.
|
|
78
|
+
const boxWidth = Math.max(0, termWidth - 2);
|
|
80
79
|
if (boxWidth < 4) {
|
|
81
80
|
return [];
|
|
82
81
|
}
|
|
@@ -25,8 +25,6 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
25
25
|
import { setSessionTerminalTitle, setTerminalTitle } from "../../utils/title-generator";
|
|
26
26
|
|
|
27
27
|
const MAX_WIDGET_LINES = 10;
|
|
28
|
-
const HOOK_SELECTOR_MOUSE_REPORTING_ENABLE = "\x1b[?1006h\x1b[?1000h";
|
|
29
|
-
const HOOK_SELECTOR_MOUSE_REPORTING_DISABLE = "\x1b[?1000l\x1b[?1006l";
|
|
30
28
|
const HOOK_SELECTOR_CHROME_ROWS = 7;
|
|
31
29
|
const HOOK_SELECTOR_OUTLINE_ROWS = 2;
|
|
32
30
|
const HOOK_SELECTOR_INLINE_INPUT_ROWS = 2;
|
|
@@ -35,7 +33,6 @@ export class ExtensionUiController {
|
|
|
35
33
|
#extensionTerminalInputUnsubscribers = new Set<() => void>();
|
|
36
34
|
#hookWidgetsAbove = new Map<string, ExtensionUiComponent>();
|
|
37
35
|
#hookWidgetsBelow = new Map<string, ExtensionUiComponent>();
|
|
38
|
-
#hookSelectorMouseReportingEnabled = false;
|
|
39
36
|
#activeHookCustomComponent?: Component & { dispose?(): void };
|
|
40
37
|
#activeHookCustomOverlay?: OverlayHandle;
|
|
41
38
|
|
|
@@ -624,9 +621,6 @@ export class ExtensionUiController {
|
|
|
624
621
|
this.ctx.ui.terminal.rows - scrollOptionRows - listChromeRows - inlineInputRows - HOOK_SELECTOR_CHROME_ROWS;
|
|
625
622
|
const scrollTitleRows =
|
|
626
623
|
requestedTitleRows === undefined ? undefined : Math.max(1, Math.min(requestedTitleRows, availableTitleRows));
|
|
627
|
-
if (scrollTitleRows !== undefined) {
|
|
628
|
-
this.#enableHookSelectorMouseReporting();
|
|
629
|
-
}
|
|
630
624
|
|
|
631
625
|
this.ctx.hookSelector = new HookSelectorComponent(
|
|
632
626
|
title,
|
|
@@ -691,31 +685,10 @@ export class ExtensionUiController {
|
|
|
691
685
|
return promise;
|
|
692
686
|
}
|
|
693
687
|
|
|
694
|
-
#enableHookSelectorMouseReporting(): void {
|
|
695
|
-
if (this.#hookSelectorMouseReportingEnabled) return;
|
|
696
|
-
this.#hookSelectorMouseReportingEnabled = true;
|
|
697
|
-
this.#writeTerminalControl(HOOK_SELECTOR_MOUSE_REPORTING_ENABLE);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
#disableHookSelectorMouseReporting(): void {
|
|
701
|
-
if (!this.#hookSelectorMouseReportingEnabled) return;
|
|
702
|
-
this.#hookSelectorMouseReportingEnabled = false;
|
|
703
|
-
this.#writeTerminalControl(HOOK_SELECTOR_MOUSE_REPORTING_DISABLE);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
#writeTerminalControl(sequence: string): void {
|
|
707
|
-
try {
|
|
708
|
-
this.ctx.ui.terminal.write(sequence);
|
|
709
|
-
} catch {
|
|
710
|
-
// Terminal teardown can race selector cleanup; normal shutdown restores modes.
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
688
|
/**
|
|
715
689
|
* Hide the hook selector.
|
|
716
690
|
*/
|
|
717
691
|
hideHookSelector(): void {
|
|
718
|
-
this.#disableHookSelectorMouseReporting();
|
|
719
692
|
this.ctx.hookSelector?.dispose();
|
|
720
693
|
this.ctx.editorContainer.clear();
|
|
721
694
|
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
@@ -4,8 +4,10 @@ import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
|
|
|
4
4
|
import type { Component, OverlayHandle } from "@gajae-code/tui";
|
|
5
5
|
import { Input, Loader, Spacer, Text } from "@gajae-code/tui";
|
|
6
6
|
import { getAgentDbPath, getProjectDir } from "@gajae-code/utils";
|
|
7
|
-
import { activateModelProfile } from "../../config/model-profile-activation";
|
|
7
|
+
import { activateModelProfile, materializeActiveModelProfileAssignment } from "../../config/model-profile-activation";
|
|
8
8
|
import { recommendModelProfileForProvider } from "../../config/model-profiles";
|
|
9
|
+
import { GJC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
|
|
10
|
+
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
9
11
|
import { settings } from "../../config/settings";
|
|
10
12
|
import { DebugSelectorComponent } from "../../debug";
|
|
11
13
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -663,7 +665,8 @@ export class SelectorController {
|
|
|
663
665
|
|
|
664
666
|
showModelSelector(options?: { temporaryOnly?: boolean }): void {
|
|
665
667
|
this.showSelector(done => {
|
|
666
|
-
|
|
668
|
+
let modelSelector: ModelSelectorComponent;
|
|
669
|
+
modelSelector = new ModelSelectorComponent(
|
|
667
670
|
this.ctx.ui,
|
|
668
671
|
this.ctx.session.model,
|
|
669
672
|
this.ctx.settings,
|
|
@@ -697,38 +700,73 @@ export class SelectorController {
|
|
|
697
700
|
this.ctx.ui.requestRender();
|
|
698
701
|
return;
|
|
699
702
|
}
|
|
700
|
-
const { model, role, thinkingLevel, selector } = selection;
|
|
703
|
+
const { model, role, thinkingLevel, selector: selectedSelector } = selection;
|
|
701
704
|
if (role === null) {
|
|
702
705
|
// Temporary: update agent state but don't persist to settings
|
|
703
706
|
await this.ctx.session.setModelTemporary(model, thinkingLevel);
|
|
704
707
|
this.ctx.statusLine.invalidate();
|
|
705
708
|
this.ctx.updateEditorBorderColor();
|
|
706
|
-
this.ctx.showStatus(`Temporary model: ${
|
|
709
|
+
this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
|
|
707
710
|
done();
|
|
708
711
|
this.ctx.ui.requestRender();
|
|
709
712
|
} else if (role === "default") {
|
|
710
713
|
// Default: update agent state and persist as the active default model.
|
|
711
714
|
await this.ctx.session.setModel(model, role, {
|
|
712
|
-
selector,
|
|
715
|
+
selector: selectedSelector,
|
|
713
716
|
thinkingLevel,
|
|
714
717
|
});
|
|
718
|
+
const value = formatModelSelectorValue(
|
|
719
|
+
selectedSelector ?? `${model.provider}/${model.id}`,
|
|
720
|
+
thinkingLevel,
|
|
721
|
+
);
|
|
722
|
+
materializeActiveModelProfileAssignment({
|
|
723
|
+
session: this.ctx.session,
|
|
724
|
+
settings: this.ctx.settings,
|
|
725
|
+
role,
|
|
726
|
+
selector: value,
|
|
727
|
+
});
|
|
715
728
|
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
716
729
|
this.ctx.session.setThinkingLevel(thinkingLevel);
|
|
717
730
|
}
|
|
731
|
+
modelSelector.refreshRoleAssignments({
|
|
732
|
+
currentModel: this.ctx.session.model,
|
|
733
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
734
|
+
activeModelProfile:
|
|
735
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
736
|
+
});
|
|
718
737
|
this.ctx.statusLine.invalidate();
|
|
719
738
|
this.ctx.updateEditorBorderColor();
|
|
720
|
-
this.ctx.showStatus(`Default model: ${
|
|
739
|
+
this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
|
|
721
740
|
done();
|
|
722
741
|
this.ctx.ui.requestRender();
|
|
723
742
|
} else {
|
|
724
|
-
// Role-agent assignments configure Task dispatch and must not switch the active chat model.
|
|
725
743
|
const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
|
|
726
744
|
if (!apiKey) {
|
|
727
745
|
throw new Error(`No API key for ${model.provider}/${model.id}`);
|
|
728
746
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
747
|
+
const value =
|
|
748
|
+
selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
|
|
749
|
+
const materializedProfile = materializeActiveModelProfileAssignment({
|
|
750
|
+
session: this.ctx.session,
|
|
751
|
+
settings: this.ctx.settings,
|
|
752
|
+
role,
|
|
753
|
+
selector: value,
|
|
754
|
+
});
|
|
755
|
+
if (!materializedProfile) {
|
|
756
|
+
const target = GJC_MODEL_ASSIGNMENT_TARGETS[role];
|
|
757
|
+
if (target.settingsPath === "modelRoles") {
|
|
758
|
+
this.ctx.settings.setModelRole(role, value);
|
|
759
|
+
} else {
|
|
760
|
+
const overrides = this.ctx.settings.get("task.agentModelOverrides");
|
|
761
|
+
this.ctx.settings.set("task.agentModelOverrides", { ...overrides, [role]: value });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
modelSelector.refreshRoleAssignments({
|
|
765
|
+
currentModel: this.ctx.session.model,
|
|
766
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
767
|
+
activeModelProfile:
|
|
768
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
769
|
+
});
|
|
732
770
|
this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
|
|
733
771
|
this.ctx.showStatus(`${role} agent model: ${value}`);
|
|
734
772
|
done();
|
|
@@ -745,11 +783,15 @@ export class SelectorController {
|
|
|
745
783
|
{
|
|
746
784
|
...options,
|
|
747
785
|
sessionId: this.ctx.session.sessionId,
|
|
786
|
+
currentThinkingLevel: this.ctx.session.thinkingLevel,
|
|
787
|
+
activeModelProfile:
|
|
788
|
+
this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
|
|
748
789
|
isFastForProvider: provider => this.ctx.session.isFastForProvider(provider),
|
|
749
790
|
isFastForSubagentProvider: provider => this.ctx.session.isFastForSubagentProvider(provider),
|
|
791
|
+
isCurrentModelFastModeActive: () => this.ctx.session.isFastModeActive(),
|
|
750
792
|
},
|
|
751
793
|
);
|
|
752
|
-
return { component:
|
|
794
|
+
return { component: modelSelector, focus: modelSelector };
|
|
753
795
|
});
|
|
754
796
|
}
|
|
755
797
|
|