@gajae-code/coding-agent 0.8.0 → 0.8.2
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 +48 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +7 -0
- package/dist/types/cli.d.ts +124 -1
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +3 -0
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/file-lock.d.ts +7 -6
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +6 -0
- package/dist/types/config/settings.d.ts +8 -0
- package/dist/types/extensibility/extensions/runner.d.ts +2 -1
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/main.d.ts +4 -1
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/print-mode.d.ts +13 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -1
- package/dist/types/notifications/config.d.ts +8 -1
- package/dist/types/notifications/daemon-paths.d.ts +1 -0
- package/dist/types/notifications/index.d.ts +6 -3
- package/dist/types/notifications/lifecycle-commands.d.ts +11 -3
- package/dist/types/notifications/telegram-daemon.d.ts +18 -3
- package/dist/types/notifications/telegram-reference.d.ts +0 -5
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +10 -0
- package/dist/types/session/agent-storage.d.ts +1 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/tools/computer.d.ts +2 -2
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/workflow/workflow-intent-diff.d.ts +8 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/package.json +7 -7
- package/src/cli/completion-cli.ts +234 -0
- package/src/cli/config-cli.ts +37 -9
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/notify-cli.ts +91 -7
- package/src/cli.ts +3 -1
- package/src/commands/completion.ts +84 -0
- package/src/commands/config.ts +2 -0
- package/src/commands/deep-interview.ts +2 -1
- package/src/config/file-lock.ts +73 -34
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +65 -0
- package/src/config/model-registry.ts +14 -2
- package/src/config/settings.ts +22 -0
- package/src/coordinator-mcp/server.ts +67 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +38 -5
- package/src/defaults/gjc/skills/ralplan/SKILL.md +15 -11
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +2 -0
- package/src/extensibility/extensions/types.ts +9 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/gjc-runtime/deep-interview-ambiguity.ts +206 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +62 -8
- package/src/gjc-runtime/deep-interview-runtime.ts +210 -2
- package/src/gjc-runtime/deep-interview-state.ts +13 -0
- package/src/gjc-runtime/session-state-sidecar.ts +59 -11
- package/src/gjc-runtime/state-runtime.ts +12 -2
- package/src/gjc-runtime/team-runtime.ts +23 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +73 -19
- package/src/hooks/native-skill-hook.ts +29 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/main.ts +52 -41
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/model-selector.ts +54 -11
- package/src/modes/components/queued-message-selector.ts +92 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/components/welcome.ts +160 -44
- package/src/modes/controllers/command-controller.ts +4 -4
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +186 -25
- package/src/modes/controllers/selector-controller.ts +122 -27
- package/src/modes/interactive-mode.ts +43 -16
- package/src/modes/print-mode.ts +43 -4
- package/src/modes/prompt-action-autocomplete.ts +24 -13
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +2 -2
- package/src/notifications/config.ts +9 -1
- package/src/notifications/daemon-paths.ts +2 -0
- package/src/notifications/index.ts +39 -9
- package/src/notifications/lifecycle-commands.ts +37 -7
- package/src/notifications/lifecycle-orchestrator.ts +19 -6
- package/src/notifications/telegram-daemon-control.ts +3 -1
- package/src/notifications/telegram-daemon.ts +231 -43
- package/src/notifications/telegram-reference.ts +0 -16
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/computer.md +2 -2
- package/src/runtime-api-key.ts +16 -0
- package/src/sdk.ts +20 -6
- package/src/session/agent-session.ts +225 -31
- package/src/session/agent-storage.ts +4 -0
- package/src/session/session-manager.ts +44 -2
- package/src/slash-commands/builtin-registry.ts +153 -71
- package/src/task/executor.ts +7 -4
- package/src/task/index.ts +9 -3
- package/src/task/output-manager.ts +16 -6
- package/src/task/receipt.ts +26 -7
- package/src/tools/computer.ts +246 -43
- package/src/tools/skill.ts +1 -1
- package/src/tools/telegram-send.ts +6 -0
- package/src/workflow/workflow-intent-diff.ts +29 -6
- package/src/workflow/workflow-intent-report-consumer.ts +221 -0
- package/src/workflow/workflow-intent-report-schema.ts +129 -0
- package/src/workflow/workflow-intent-report.ts +135 -0
package/src/main.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
|
37
37
|
import type { InteractiveMode } from "./modes/interactive-mode";
|
|
38
38
|
import { initTheme, stopThemeWatcher } from "./modes/theme/theme";
|
|
39
39
|
import type { SubmittedUserInput } from "./modes/types";
|
|
40
|
+
import { applyCliRuntimeApiKeyOverride } from "./runtime-api-key";
|
|
40
41
|
import type { MCPManager } from "./runtime-mcp";
|
|
41
42
|
import {
|
|
42
43
|
type CreateAgentSessionOptions,
|
|
@@ -189,6 +190,11 @@ function applyExtensionFlagValues(session: AgentSession, rawArgs: string[]): Map
|
|
|
189
190
|
return extensionRunner.getFlagValues();
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
type CreateSessionForMain = (
|
|
194
|
+
options: CreateAgentSessionOptions,
|
|
195
|
+
context?: { skipPostCreateModelRefresh?: boolean },
|
|
196
|
+
) => Promise<CreateAgentSessionResult>;
|
|
197
|
+
|
|
192
198
|
type AcpSessionFactory = (cwd: string) => Promise<AgentSession>;
|
|
193
199
|
|
|
194
200
|
export interface AcpSessionFactoryOptions {
|
|
@@ -199,7 +205,7 @@ export interface AcpSessionFactoryOptions {
|
|
|
199
205
|
modelRegistry: ModelRegistry;
|
|
200
206
|
parsedArgs: Pick<Args, "apiKey" | "default" | "model" | "mpreset" | "thinking">;
|
|
201
207
|
rawArgs: string[];
|
|
202
|
-
createSession:
|
|
208
|
+
createSession: CreateSessionForMain;
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
export async function applyStartupModelProfiles(args: {
|
|
@@ -221,8 +227,11 @@ export async function applyStartupModelProfiles(args: {
|
|
|
221
227
|
// override it. startupModel covers the eager path; session.model covers the
|
|
222
228
|
// deferred `--model <pattern>` path resolved inside createAgentSession.
|
|
223
229
|
const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
|
|
224
|
-
|
|
225
230
|
const defaultProfile = args.settings.get("modelProfile.default");
|
|
231
|
+
if (defaultProfile || args.parsedArgs.mpreset) {
|
|
232
|
+
await args.modelRegistry.refresh("online-if-uncached");
|
|
233
|
+
}
|
|
234
|
+
|
|
226
235
|
if (defaultProfile) {
|
|
227
236
|
await applyProfile(defaultProfile, false);
|
|
228
237
|
}
|
|
@@ -263,19 +272,23 @@ export async function applyStartupModelProfilesOrExit(
|
|
|
263
272
|
export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSessionFactory {
|
|
264
273
|
return async cwd => {
|
|
265
274
|
const nextSettings = await args.settings.cloneForCwd(cwd);
|
|
275
|
+
const hasStartupProfile = Boolean(nextSettings.get("modelProfile.default") || args.parsedArgs.mpreset);
|
|
266
276
|
const nextSessionManager = SessionManager.create(cwd, args.sessionDir);
|
|
267
277
|
const agentId = `acp:${nextSessionManager.getSessionId()}`;
|
|
268
|
-
const { session: nextSession } = await args.createSession(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
const { session: nextSession } = await args.createSession(
|
|
279
|
+
{
|
|
280
|
+
...args.baseOptions,
|
|
281
|
+
cwd,
|
|
282
|
+
sessionManager: nextSessionManager,
|
|
283
|
+
settings: nextSettings,
|
|
284
|
+
authStorage: args.authStorage,
|
|
285
|
+
modelRegistry: args.modelRegistry,
|
|
286
|
+
agentId,
|
|
287
|
+
hasUI: false,
|
|
288
|
+
enableMCP: false,
|
|
289
|
+
},
|
|
290
|
+
{ skipPostCreateModelRefresh: hasStartupProfile },
|
|
291
|
+
);
|
|
279
292
|
await applyStartupModelProfilesOrExit({
|
|
280
293
|
session: nextSession,
|
|
281
294
|
settings: nextSettings,
|
|
@@ -284,9 +297,7 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
|
|
|
284
297
|
startupModel: args.baseOptions.model,
|
|
285
298
|
startupThinkingLevel: args.baseOptions.thinkingLevel,
|
|
286
299
|
});
|
|
287
|
-
|
|
288
|
-
args.authStorage.setRuntimeApiKey(nextSession.model.provider, args.parsedArgs.apiKey);
|
|
289
|
-
}
|
|
300
|
+
applyCliRuntimeApiKeyOverride(args.authStorage, args.parsedArgs.apiKey, nextSession.model);
|
|
290
301
|
applyExtensionFlagValues(nextSession, args.rawArgs);
|
|
291
302
|
return nextSession;
|
|
292
303
|
};
|
|
@@ -395,30 +406,28 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
|
|
|
395
406
|
return undefined;
|
|
396
407
|
}
|
|
397
408
|
|
|
398
|
-
const
|
|
399
|
-
if (
|
|
400
|
-
// Steady state: user already saw the current version's changelog. Skip the file read + parse.
|
|
409
|
+
const entries = getDisplayChangelogEntries();
|
|
410
|
+
if (entries.length === 0) {
|
|
401
411
|
return undefined;
|
|
402
412
|
}
|
|
403
413
|
|
|
404
|
-
const
|
|
405
|
-
|
|
414
|
+
const lastVersion = settings.get("lastChangelogVersion");
|
|
406
415
|
if (!lastVersion) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
416
|
+
settings.set("lastChangelogVersion", VERSION);
|
|
417
|
+
await flushChangelogVersion();
|
|
418
|
+
return getInstalledVersionChangelogEntry(entries, VERSION)?.content;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (lastVersion !== VERSION) {
|
|
413
422
|
const newEntries = getNewEntries(entries, lastVersion);
|
|
423
|
+
settings.set("lastChangelogVersion", VERSION);
|
|
424
|
+
await flushChangelogVersion();
|
|
414
425
|
if (newEntries.length > 0) {
|
|
415
|
-
settings.set("lastChangelogVersion", VERSION);
|
|
416
|
-
await flushChangelogVersion();
|
|
417
426
|
return newEntries.map(e => e.content).join("\n\n");
|
|
418
427
|
}
|
|
419
428
|
}
|
|
420
429
|
|
|
421
|
-
return
|
|
430
|
+
return getInstalledVersionChangelogEntry(entries, VERSION)?.content;
|
|
422
431
|
}
|
|
423
432
|
|
|
424
433
|
async function flushChangelogVersion(): Promise<void> {
|
|
@@ -946,6 +955,7 @@ export async function runRootCommand(
|
|
|
946
955
|
sessionOptions.modelRegistry = modelRegistry;
|
|
947
956
|
sessionOptions.hasUI = isInteractive || mode === "rpc-ui";
|
|
948
957
|
sessionOptions.settings = settingsInstance;
|
|
958
|
+
const hasRootStartupProfile = Boolean(settingsInstance.get("modelProfile.default") || parsedArgs.mpreset);
|
|
949
959
|
|
|
950
960
|
// Research-mode (RLM) preset: augment session options before session creation.
|
|
951
961
|
deps.rlmPreset?.applyOptions(sessionOptions, settingsInstance);
|
|
@@ -958,18 +968,19 @@ export async function runRootCommand(
|
|
|
958
968
|
);
|
|
959
969
|
process.exit(1);
|
|
960
970
|
}
|
|
961
|
-
|
|
962
|
-
authStorage.setRuntimeApiKey(sessionOptions.model.provider, parsedArgs.apiKey);
|
|
963
|
-
}
|
|
971
|
+
applyCliRuntimeApiKeyOverride(authStorage, parsedArgs.apiKey, sessionOptions.model);
|
|
964
972
|
}
|
|
965
973
|
|
|
966
974
|
const createAgentSessionImpl = deps.createAgentSession ?? createAgentSession;
|
|
967
|
-
const createSession = async (options
|
|
975
|
+
const createSession: CreateSessionForMain = async (options, context): Promise<CreateAgentSessionResult> => {
|
|
968
976
|
const result = await logger.time("createAgentSession", createAgentSessionImpl, options);
|
|
969
977
|
// Kick off background model discovery only after createAgentSession finishes its parallel
|
|
970
978
|
// discovery arms; running these concurrently contends for the event loop and stretches
|
|
971
|
-
// every parallel arm by ~30ms.
|
|
972
|
-
|
|
979
|
+
// every parallel arm by ~30ms. Startup model profiles do their own foreground refresh
|
|
980
|
+
// before activation so project-scoped defaults can resolve freshly discovered models.
|
|
981
|
+
if (!context?.skipPostCreateModelRefresh) {
|
|
982
|
+
modelRegistry.refreshInBackground();
|
|
983
|
+
}
|
|
973
984
|
return result;
|
|
974
985
|
};
|
|
975
986
|
|
|
@@ -986,11 +997,11 @@ export async function runRootCommand(
|
|
|
986
997
|
});
|
|
987
998
|
await (deps.runAcpMode ?? (await import("./modes/acp")).runAcpMode)(createAcpSession);
|
|
988
999
|
} else {
|
|
989
|
-
const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager, eventBus } =
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1000
|
+
const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager, eventBus } = await createSession(
|
|
1001
|
+
sessionOptions,
|
|
1002
|
+
{ skipPostCreateModelRefresh: hasRootStartupProfile },
|
|
1003
|
+
);
|
|
1004
|
+
applyCliRuntimeApiKeyOverride(authStorage, parsedArgs.apiKey, session.model);
|
|
994
1005
|
|
|
995
1006
|
// Research-mode (RLM) preset: hard tool-boundary assertion after the registry is assembled.
|
|
996
1007
|
if (deps.rlmPreset?.onSessionCreated) {
|
|
@@ -710,9 +710,6 @@ export class AcpAgent implements Agent {
|
|
|
710
710
|
text: string,
|
|
711
711
|
options: { directAliasMayCollide?: boolean } = {},
|
|
712
712
|
): Promise<boolean> {
|
|
713
|
-
if (!text.startsWith("/")) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
713
|
if (!record.session.skillsSettings?.enableSkillCommands) {
|
|
717
714
|
return false;
|
|
718
715
|
}
|
|
@@ -758,9 +755,13 @@ export class AcpAgent implements Agent {
|
|
|
758
755
|
}
|
|
759
756
|
return true;
|
|
760
757
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
758
|
+
const slashText = text.trimStart();
|
|
759
|
+
if (!slashText.startsWith("/")) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
const spaceIndex = slashText.indexOf(" ");
|
|
763
|
+
const commandName = spaceIndex === -1 ? slashText.slice(1) : slashText.slice(1, spaceIndex);
|
|
764
|
+
const args = spaceIndex === -1 ? "" : slashText.slice(spaceIndex + 1).trim();
|
|
764
765
|
if (
|
|
765
766
|
!isNamespacedSkillSlashCommandName(commandName) &&
|
|
766
767
|
options.directAliasMayCollide !== true &&
|
|
@@ -96,6 +96,7 @@ export type ModelSelectorSelection =
|
|
|
96
96
|
kind: "assignment";
|
|
97
97
|
model: Model;
|
|
98
98
|
role: GjcModelAssignmentTargetId | null;
|
|
99
|
+
roles?: readonly GjcModelAssignmentTargetId[];
|
|
99
100
|
thinkingLevel?: ThinkingLevel;
|
|
100
101
|
selector?: string;
|
|
101
102
|
}
|
|
@@ -1360,7 +1361,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1360
1361
|
for (let i = 0; i < actionCount; i++) {
|
|
1361
1362
|
const prefix = i === this.#selectedActionIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
|
|
1362
1363
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[i];
|
|
1363
|
-
const label =
|
|
1364
|
+
const label = role
|
|
1365
|
+
? `Set as ${GJC_MODEL_ASSIGNMENT_TARGETS[role].tag ?? role.toUpperCase()} (${GJC_MODEL_ASSIGNMENT_TARGETS[role].name})`
|
|
1366
|
+
: i === GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1367
|
+
? "Set for all role agents"
|
|
1368
|
+
: "Set for all targets";
|
|
1364
1369
|
this.#listContainer.addChild(
|
|
1365
1370
|
new Text(`${prefix}${i === this.#selectedActionIndex ? theme.fg("accent", label) : label}`, 0, 0),
|
|
1366
1371
|
);
|
|
@@ -1389,7 +1394,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
1389
1394
|
return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
|
|
1390
1395
|
}
|
|
1391
1396
|
#getActionCount(_model: Model): number {
|
|
1392
|
-
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length;
|
|
1397
|
+
return GJC_MODEL_ASSIGNMENT_TARGET_IDS.length + 2;
|
|
1393
1398
|
}
|
|
1394
1399
|
|
|
1395
1400
|
#getSelectedItem(): ModelItem | CanonicalModelItem | undefined {
|
|
@@ -1660,7 +1665,15 @@ export class ModelSelectorComponent extends Container {
|
|
|
1660
1665
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
1661
1666
|
this.#pendingActionItem = undefined;
|
|
1662
1667
|
const role = GJC_MODEL_ASSIGNMENT_TARGET_IDS[this.#selectedActionIndex];
|
|
1663
|
-
if (role)
|
|
1668
|
+
if (role) {
|
|
1669
|
+
this.#handleSelect(item, role);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
const roles =
|
|
1673
|
+
this.#selectedActionIndex === GJC_MODEL_ASSIGNMENT_TARGET_IDS.length
|
|
1674
|
+
? (["executor", "architect", "planner", "critic"] as const)
|
|
1675
|
+
: GJC_MODEL_ASSIGNMENT_TARGET_IDS;
|
|
1676
|
+
this.#handleSelect(item, "default", undefined, roles);
|
|
1664
1677
|
return;
|
|
1665
1678
|
}
|
|
1666
1679
|
if (getKeybindings().matches(keyData, "tui.select.cancel")) {
|
|
@@ -1699,17 +1712,34 @@ export class ModelSelectorComponent extends Container {
|
|
|
1699
1712
|
this.#updateList();
|
|
1700
1713
|
}
|
|
1701
1714
|
}
|
|
1715
|
+
#getInitialThinkingChoiceIndex(item: ModelItem | CanonicalModelItem, levels: ThinkingLevel[]): number {
|
|
1716
|
+
const preferred = this.#getPreferredThinkingLevel(item);
|
|
1717
|
+
if (preferred && preferred !== ThinkingLevel.Inherit) {
|
|
1718
|
+
const index = levels.indexOf(preferred);
|
|
1719
|
+
if (index !== -1) return index;
|
|
1720
|
+
}
|
|
1721
|
+
return 0;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
#getPreferredThinkingLevel(item: ModelItem | CanonicalModelItem): ThinkingLevel | undefined {
|
|
1725
|
+
if (item.thinkingLevel && item.thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1726
|
+
return item.thinkingLevel;
|
|
1727
|
+
}
|
|
1728
|
+
return undefined;
|
|
1729
|
+
}
|
|
1702
1730
|
|
|
1703
1731
|
#handleSelect(
|
|
1704
1732
|
item: ModelItem | CanonicalModelItem,
|
|
1705
1733
|
role: GjcModelAssignmentTargetId | null,
|
|
1706
1734
|
thinkingLevel?: ThinkingLevel,
|
|
1735
|
+
roles?: readonly GjcModelAssignmentTargetId[],
|
|
1707
1736
|
): void {
|
|
1708
1737
|
const itemThinkingLevel = thinkingLevel ?? item.thinkingLevel;
|
|
1709
1738
|
const hasExplicitThinkingChoice = thinkingLevel !== undefined || item.explicitThinkingLevel === true;
|
|
1710
|
-
if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model)) {
|
|
1711
|
-
|
|
1712
|
-
this.#
|
|
1739
|
+
if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model, role)) {
|
|
1740
|
+
const levels = getSelectableThinkingLevels(item.model);
|
|
1741
|
+
this.#pendingThinkingChoice = { item, role, levels };
|
|
1742
|
+
this.#selectedThinkingIndex = this.#getInitialThinkingChoiceIndex(item, levels);
|
|
1713
1743
|
this.#updateList();
|
|
1714
1744
|
return;
|
|
1715
1745
|
}
|
|
@@ -1727,17 +1757,23 @@ export class ModelSelectorComponent extends Container {
|
|
|
1727
1757
|
}
|
|
1728
1758
|
|
|
1729
1759
|
const selectedThinkingLevel = itemThinkingLevel ?? this.#getCurrentRoleThinkingLevel(role);
|
|
1730
|
-
const selectorValue =
|
|
1731
|
-
|
|
1760
|
+
const selectorValue = roles
|
|
1761
|
+
? formatModelSelectorValue(item.selector, selectedThinkingLevel)
|
|
1762
|
+
: role === "default"
|
|
1763
|
+
? item.selector
|
|
1764
|
+
: formatModelSelectorValue(item.selector, selectedThinkingLevel);
|
|
1732
1765
|
|
|
1733
1766
|
// Update local state for UI
|
|
1734
|
-
|
|
1767
|
+
for (const targetRole of roles ?? [role]) {
|
|
1768
|
+
this.#roles[targetRole] = { model: item.model, thinkingLevel: selectedThinkingLevel };
|
|
1769
|
+
}
|
|
1735
1770
|
|
|
1736
1771
|
// Notify caller (for updating agent state if needed)
|
|
1737
1772
|
this.#onSelectCallback({
|
|
1738
1773
|
kind: "assignment",
|
|
1739
1774
|
model: item.model,
|
|
1740
1775
|
role,
|
|
1776
|
+
roles,
|
|
1741
1777
|
thinkingLevel: selectedThinkingLevel,
|
|
1742
1778
|
selector: selectorValue,
|
|
1743
1779
|
});
|
|
@@ -1752,6 +1788,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
1752
1788
|
async __testSelectProfile(profileName: string, setDefault: boolean): Promise<void> {
|
|
1753
1789
|
await this.#onSelectCallback({ kind: "profile", profileName, setDefault });
|
|
1754
1790
|
}
|
|
1791
|
+
async __testSelectAssignment(
|
|
1792
|
+
selection: Omit<Extract<ModelSelectorSelection, { kind: "assignment" }>, "kind">,
|
|
1793
|
+
): Promise<void> {
|
|
1794
|
+
await this.#onSelectCallback({ kind: "assignment", ...selection });
|
|
1795
|
+
}
|
|
1755
1796
|
async __testSelectPresetAction(profileName: string, action: "rename" | "delete"): Promise<void> {
|
|
1756
1797
|
await this.#onSelectCallback({
|
|
1757
1798
|
kind: action === "rename" ? "renameProfile" : "deleteProfile",
|
|
@@ -1764,8 +1805,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
1764
1805
|
}
|
|
1765
1806
|
}
|
|
1766
1807
|
|
|
1767
|
-
function requiresExplicitThinkingChoice(model: Model): boolean {
|
|
1768
|
-
|
|
1808
|
+
function requiresExplicitThinkingChoice(model: Model, role: GjcModelAssignmentTargetId | null): boolean {
|
|
1809
|
+
if (model.reasoning !== true) return false;
|
|
1810
|
+
if (model.provider === "openai" || model.provider === "openai-codex") return true;
|
|
1811
|
+
return role !== null && GJC_MODEL_ASSIGNMENT_TARGETS[role].settingsPath === "task.agentModelOverrides";
|
|
1769
1812
|
}
|
|
1770
1813
|
|
|
1771
1814
|
function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Container, matchesKey, type SelectItem, SelectList, Spacer, Text } from "@gajae-code/tui";
|
|
2
|
+
import type { QueuedMessageEditEntry } from "../../session/agent-session";
|
|
3
|
+
import { getSelectListTheme, theme } from "../theme/theme";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
|
+
|
|
6
|
+
const MAX_VISIBLE_QUEUED_MESSAGES = 8;
|
|
7
|
+
const RAW_UP = "\x1b[A";
|
|
8
|
+
const RAW_DOWN = "\x1b[B";
|
|
9
|
+
type QueuedMessageMoveDirection = "up" | "down";
|
|
10
|
+
|
|
11
|
+
export type { QueuedMessageMoveDirection };
|
|
12
|
+
|
|
13
|
+
export class QueuedMessageSelectorComponent extends Container {
|
|
14
|
+
#selectList: SelectList;
|
|
15
|
+
#selectedEntry: QueuedMessageEditEntry | undefined;
|
|
16
|
+
#selectedIndex = 0;
|
|
17
|
+
#onDelete: (entry: QueuedMessageEditEntry, selectedIndex: number) => void;
|
|
18
|
+
#onMove: (entry: QueuedMessageEditEntry, selectedIndex: number, direction: QueuedMessageMoveDirection) => void;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
entries: QueuedMessageEditEntry[],
|
|
22
|
+
onSelect: (entry: QueuedMessageEditEntry) => void,
|
|
23
|
+
onDelete: (entry: QueuedMessageEditEntry, selectedIndex: number) => void,
|
|
24
|
+
onMove: (entry: QueuedMessageEditEntry, selectedIndex: number, direction: QueuedMessageMoveDirection) => void,
|
|
25
|
+
onCancel: () => void,
|
|
26
|
+
options?: { selectedIndex?: number },
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
this.#onDelete = onDelete;
|
|
31
|
+
this.#onMove = onMove;
|
|
32
|
+
const byId = new Map(entries.map(entry => [entry.id, entry]));
|
|
33
|
+
this.#selectedIndex = Math.max(0, Math.min(options?.selectedIndex ?? 0, entries.length - 1));
|
|
34
|
+
this.#selectedEntry = entries[this.#selectedIndex];
|
|
35
|
+
const items: SelectItem[] = entries.map((entry, index) => ({
|
|
36
|
+
value: entry.id,
|
|
37
|
+
label: `${entry.label} ${index + 1}`,
|
|
38
|
+
description: entry.text,
|
|
39
|
+
hint: "Enter edit · Del remove · Ctrl+↑/↓ move",
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
this.addChild(new Spacer(1));
|
|
43
|
+
this.addChild(new Text(theme.bold("Queued messages"), 1, 0));
|
|
44
|
+
this.addChild(new Text(theme.fg("muted", "Enter edit · Del remove · Ctrl+↑/↓ move · Esc cancel"), 1, 0));
|
|
45
|
+
this.addChild(new Spacer(1));
|
|
46
|
+
this.addChild(new DynamicBorder());
|
|
47
|
+
|
|
48
|
+
this.#selectList = new SelectList(items, MAX_VISIBLE_QUEUED_MESSAGES, getSelectListTheme());
|
|
49
|
+
this.#selectList.onSelect = item => {
|
|
50
|
+
const entry = byId.get(item.value);
|
|
51
|
+
if (entry) onSelect(entry);
|
|
52
|
+
};
|
|
53
|
+
this.#selectList.onSelectionChange = item => {
|
|
54
|
+
const index = entries.findIndex(entry => entry.id === item.value);
|
|
55
|
+
this.#selectedIndex = index === -1 ? this.#selectedIndex : index;
|
|
56
|
+
this.#selectedEntry = byId.get(item.value);
|
|
57
|
+
};
|
|
58
|
+
this.#selectList.setSelectedIndex(this.#selectedIndex);
|
|
59
|
+
this.#selectList.onCancel = onCancel;
|
|
60
|
+
|
|
61
|
+
this.addChild(this.#selectList);
|
|
62
|
+
this.addChild(new DynamicBorder());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
handleInput(keyData: string): void {
|
|
66
|
+
if (matchesKey(keyData, "alt+up")) {
|
|
67
|
+
this.#selectList.handleInput(RAW_UP);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (matchesKey(keyData, "alt+down")) {
|
|
71
|
+
this.#selectList.handleInput(RAW_DOWN);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (matchesKey(keyData, "ctrl+up") || matchesKey(keyData, "ctrl+shift+up")) {
|
|
75
|
+
if (this.#selectedEntry) this.#onMove(this.#selectedEntry, this.#selectedIndex, "up");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (matchesKey(keyData, "ctrl+down") || matchesKey(keyData, "ctrl+shift+down")) {
|
|
79
|
+
if (this.#selectedEntry) this.#onMove(this.#selectedEntry, this.#selectedIndex, "down");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (matchesKey(keyData, "delete")) {
|
|
83
|
+
if (this.#selectedEntry) this.#onDelete(this.#selectedEntry, this.#selectedIndex);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.#selectList.handleInput(keyData);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getSelectList(): SelectList {
|
|
90
|
+
return this.#selectList;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -657,6 +657,8 @@ export interface SettingsRuntimeContext {
|
|
|
657
657
|
thinkingLevel: ThinkingLevel | undefined;
|
|
658
658
|
/** Available themes */
|
|
659
659
|
availableThemes: string[];
|
|
660
|
+
/** Available model profile names (from the model registry) */
|
|
661
|
+
availableModelProfiles: string[];
|
|
660
662
|
/** Working directory for plugins tab */
|
|
661
663
|
cwd: string;
|
|
662
664
|
}
|
|
@@ -847,6 +849,8 @@ export class SettingsSelectorComponent extends Container {
|
|
|
847
849
|
});
|
|
848
850
|
} else if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
849
851
|
options = this.context.availableThemes.map(t => ({ value: t, label: t }));
|
|
852
|
+
} else if (def.path === "modelProfile.default") {
|
|
853
|
+
options = this.context.availableModelProfiles.map(p => ({ value: p, label: p }));
|
|
850
854
|
}
|
|
851
855
|
if (def.path === "statusLine.preset") {
|
|
852
856
|
options = options.filter(option => option.value !== "custom");
|