@gajae-code/coding-agent 0.11.7 → 0.11.9
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 +73 -1
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli.d.ts +1 -0
- 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/model-profiles.d.ts +2 -3
- 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/deep-interview-ambiguity.d.ts +12 -5
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +44 -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/gjc-runtime/workflow-manifest.d.ts +5 -3
- 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 +4 -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/runtime-mcp/redaction.d.ts +2 -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 +7 -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 +15 -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 +22 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +5 -1
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/skill-state/active-state.d.ts +4 -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/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +50 -0
- package/src/commands/deep-interview.ts +9 -4
- package/src/commands/sdk.ts +2 -2
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.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-profiles.ts +2 -3
- 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 +80 -165
- 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/deep-interview-ambiguity.ts +68 -47
- package/src/gjc-runtime/deep-interview-draft.ts +842 -0
- package/src/gjc-runtime/deep-interview-payload.ts +262 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
- package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
- package/src/gjc-runtime/deep-interview-state.ts +916 -4
- 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 +78 -4
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/state-writer.ts +166 -2
- 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/gjc-runtime/workflow-manifest.generated.json +386 -1
- package/src/gjc-runtime/workflow-manifest.ts +191 -5
- package/src/internal-urls/docs-index.generated.ts +8 -6
- 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 +32 -20
- 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 +70 -36
- 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 +7 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/runtime-mcp/json-rpc.ts +7 -4
- 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/redaction.ts +30 -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/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-control.ts +20 -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 +15 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +853 -112
- 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 +138 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +998 -133
- package/src/session/internal/managed-session-storage.ts +268 -203
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +406 -360
- package/src/session/session-storage.ts +151 -11
- package/src/skill-state/active-state.ts +10 -0
- package/src/task/executor.ts +13 -5
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -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,6 +5,7 @@ import {
|
|
|
5
5
|
type KeybindingDefinitions,
|
|
6
6
|
type KeybindingsConfig,
|
|
7
7
|
type KeyId,
|
|
8
|
+
parseKeyId,
|
|
8
9
|
setKeybindings,
|
|
9
10
|
TUI_KEYBINDINGS,
|
|
10
11
|
KeybindingsManager as TuiKeybindingsManager,
|
|
@@ -352,10 +353,29 @@ function toKeybindingsConfig(value: unknown): KeybindingsConfig {
|
|
|
352
353
|
logger.info("Ignoring unknown keybinding entry", { key });
|
|
353
354
|
continue;
|
|
354
355
|
}
|
|
355
|
-
if (val === undefined)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
if (val === undefined) {
|
|
357
|
+
config[key] = undefined;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const keys = typeof val === "string" ? [val] : Array.isArray(val) ? val : undefined;
|
|
362
|
+
if (!keys?.every(keyValue => typeof keyValue === "string")) {
|
|
363
|
+
logger.info("Ignoring malformed keybinding entry", { key });
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const normalizedKeys: KeyId[] = [];
|
|
368
|
+
for (const keyValue of keys) {
|
|
369
|
+
const parsedKey = parseKeyId(keyValue);
|
|
370
|
+
if (!parsedKey) {
|
|
371
|
+
logger.info("Ignoring invalid keybinding entry", { key, category: "invalid-keybinding" });
|
|
372
|
+
normalizedKeys.length = 0;
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
normalizedKeys.push(parsedKey.keyId);
|
|
376
|
+
}
|
|
377
|
+
if (normalizedKeys.length !== keys.length) continue;
|
|
378
|
+
config[key] = typeof val === "string" ? normalizedKeys[0] : normalizedKeys;
|
|
359
379
|
}
|
|
360
380
|
return config;
|
|
361
381
|
}
|
|
@@ -461,11 +481,18 @@ function migrateKeybindingsConfigFile(agentDir: string): void {
|
|
|
461
481
|
*/
|
|
462
482
|
export class KeybindingsManager extends TuiKeybindingsManager {
|
|
463
483
|
#configPath: string | undefined;
|
|
484
|
+
#displayContext: KeyDisplayContext = runtimeKeyDisplayContext;
|
|
464
485
|
|
|
465
486
|
constructor(userBindings: KeybindingsConfig = {}, configPath?: string) {
|
|
466
|
-
super(KEYBINDINGS, userBindings);
|
|
487
|
+
super(KEYBINDINGS, toKeybindingsConfig(userBindings));
|
|
467
488
|
this.#configPath = configPath;
|
|
468
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Replace user bindings only after canonical grammar validation.
|
|
492
|
+
*/
|
|
493
|
+
override setUserBindings(userBindings: KeybindingsConfig): void {
|
|
494
|
+
super.setUserBindings(toKeybindingsConfig(userBindings));
|
|
495
|
+
}
|
|
469
496
|
|
|
470
497
|
/**
|
|
471
498
|
* Create from config file at agentDir/keybindings.json.
|
|
@@ -485,6 +512,13 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
485
512
|
static inMemory(userBindings: KeybindingsConfig = {}): KeybindingsManager {
|
|
486
513
|
return new KeybindingsManager(userBindings);
|
|
487
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Set the default display context used by composed surfaces that do not
|
|
517
|
+
* supply an explicit context.
|
|
518
|
+
*/
|
|
519
|
+
setDisplayContext(context: KeyDisplayContext): void {
|
|
520
|
+
this.#displayContext = context;
|
|
521
|
+
}
|
|
488
522
|
|
|
489
523
|
/**
|
|
490
524
|
* Reload keybindings from the config file.
|
|
@@ -504,9 +538,29 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
504
538
|
/**
|
|
505
539
|
* Get display string for a keybinding (e.g., "ctrl+c/escape").
|
|
506
540
|
*/
|
|
507
|
-
getDisplayString(keybinding: Keybinding): string {
|
|
541
|
+
getDisplayString(keybinding: Keybinding, context: KeyDisplayContext = this.#displayContext): string {
|
|
542
|
+
const keys = this.getKeys(keybinding);
|
|
543
|
+
return formatKeyHints(keys, context);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get an accessibility-oriented display string for help surfaces.
|
|
547
|
+
* Darwin chords include both the concise glyphs and expanded key names.
|
|
548
|
+
*/
|
|
549
|
+
getAccessibleDisplayString(keybinding: Keybinding, context: KeyDisplayContext = this.#displayContext): string {
|
|
508
550
|
const keys = this.getKeys(keybinding);
|
|
509
|
-
return
|
|
551
|
+
return formatAccessibleKeyHints(keys, context);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Format a fixed key chord using this manager's display context.
|
|
555
|
+
*/
|
|
556
|
+
formatKeyHint(key: string): string {
|
|
557
|
+
return formatKeyHint(key, this.#displayContext);
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Format a fixed key chord for accessibility-oriented help surfaces.
|
|
561
|
+
*/
|
|
562
|
+
formatAccessibleKeyHint(key: string): string {
|
|
563
|
+
return formatAccessibleKeyHint(key, this.#displayContext);
|
|
510
564
|
}
|
|
511
565
|
|
|
512
566
|
/**
|
|
@@ -520,13 +574,20 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
520
574
|
/**
|
|
521
575
|
* Key hint formatting utilities for UI labels.
|
|
522
576
|
*/
|
|
523
|
-
|
|
577
|
+
export interface KeyDisplayContext {
|
|
578
|
+
platform: NodeJS.Platform;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const runtimeKeyDisplayContext: KeyDisplayContext = { platform: process.platform };
|
|
582
|
+
|
|
583
|
+
const TEXTUAL_MODIFIER_LABELS: Record<string, string> = {
|
|
524
584
|
ctrl: "Ctrl",
|
|
525
|
-
shift: "Shift",
|
|
526
585
|
alt: "Alt",
|
|
586
|
+
shift: "Shift",
|
|
587
|
+
super: "Super",
|
|
527
588
|
};
|
|
528
589
|
|
|
529
|
-
const
|
|
590
|
+
const TEXTUAL_KEY_LABELS: Record<string, string> = {
|
|
530
591
|
esc: "Esc",
|
|
531
592
|
escape: "Esc",
|
|
532
593
|
enter: "Enter",
|
|
@@ -535,33 +596,105 @@ const KEY_LABELS: Record<string, string> = {
|
|
|
535
596
|
tab: "Tab",
|
|
536
597
|
backspace: "Backspace",
|
|
537
598
|
delete: "Delete",
|
|
599
|
+
insert: "Insert",
|
|
600
|
+
clear: "Clear",
|
|
538
601
|
home: "Home",
|
|
539
602
|
end: "End",
|
|
540
|
-
|
|
541
|
-
|
|
603
|
+
pageUp: "PgUp",
|
|
604
|
+
pageDown: "PgDn",
|
|
542
605
|
up: "Up",
|
|
543
606
|
down: "Down",
|
|
544
607
|
left: "Left",
|
|
545
608
|
right: "Right",
|
|
546
609
|
};
|
|
547
610
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
611
|
+
const DARWIN_MODIFIER_LABELS: Record<string, string> = {
|
|
612
|
+
ctrl: "⌃",
|
|
613
|
+
alt: "⌥",
|
|
614
|
+
shift: "⇧",
|
|
615
|
+
super: "⌘",
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const DARWIN_KEY_LABELS: Record<string, string> = {
|
|
619
|
+
...TEXTUAL_KEY_LABELS,
|
|
620
|
+
esc: "⎋",
|
|
621
|
+
escape: "⎋",
|
|
622
|
+
enter: "↩",
|
|
623
|
+
return: "↩",
|
|
624
|
+
tab: "⇥",
|
|
625
|
+
backspace: "⌫",
|
|
626
|
+
delete: "⌦",
|
|
627
|
+
up: "↑",
|
|
628
|
+
down: "↓",
|
|
629
|
+
left: "←",
|
|
630
|
+
right: "→",
|
|
631
|
+
};
|
|
632
|
+
const DARWIN_ACCESSIBLE_MODIFIER_LABELS: Record<string, string> = {
|
|
633
|
+
ctrl: "Control",
|
|
634
|
+
alt: "Option",
|
|
635
|
+
shift: "Shift",
|
|
636
|
+
super: "Command",
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
const DARWIN_ACCESSIBLE_KEY_LABELS: Record<string, string> = {
|
|
640
|
+
...TEXTUAL_KEY_LABELS,
|
|
641
|
+
esc: "Escape",
|
|
642
|
+
escape: "Escape",
|
|
643
|
+
pageUp: "Page Up",
|
|
644
|
+
pageDown: "Page Down",
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const DISPLAY_MODIFIER_ORDER = ["ctrl", "alt", "shift", "super"] as const;
|
|
648
|
+
const INVALID_KEYBINDING_DISPLAY = "Invalid keybinding";
|
|
649
|
+
|
|
650
|
+
function formatBaseKey(baseKey: string, labels: Record<string, string>): string {
|
|
651
|
+
const label = labels[baseKey];
|
|
553
652
|
if (label) return label;
|
|
554
|
-
if (
|
|
555
|
-
return `${
|
|
653
|
+
if (baseKey.length === 1) return baseKey.toUpperCase();
|
|
654
|
+
return `${baseKey.charAt(0).toUpperCase()}${baseKey.slice(1)}`;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function formatKeyHint(key: string, context: KeyDisplayContext = runtimeKeyDisplayContext): string {
|
|
658
|
+
const parsed = parseKeyId(key);
|
|
659
|
+
if (!parsed) return INVALID_KEYBINDING_DISPLAY;
|
|
660
|
+
|
|
661
|
+
const darwin = context.platform === "darwin";
|
|
662
|
+
const modifierLabels = darwin ? DARWIN_MODIFIER_LABELS : TEXTUAL_MODIFIER_LABELS;
|
|
663
|
+
const baseLabels = darwin ? DARWIN_KEY_LABELS : TEXTUAL_KEY_LABELS;
|
|
664
|
+
const modifiers = DISPLAY_MODIFIER_ORDER.filter(modifier => parsed.modifiers.includes(modifier)).map(
|
|
665
|
+
modifier => modifierLabels[modifier],
|
|
666
|
+
);
|
|
667
|
+
const parts = [...modifiers, formatBaseKey(parsed.baseKey, baseLabels)];
|
|
668
|
+
return parts.join(darwin ? "" : "+");
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
export function formatAccessibleKeyHint(key: string, context: KeyDisplayContext = runtimeKeyDisplayContext): string {
|
|
672
|
+
const concise = formatKeyHint(key, context);
|
|
673
|
+
if (context.platform !== "darwin" || concise === INVALID_KEYBINDING_DISPLAY) return concise;
|
|
674
|
+
|
|
675
|
+
const parsed = parseKeyId(key);
|
|
676
|
+
if (!parsed) return INVALID_KEYBINDING_DISPLAY;
|
|
677
|
+
const modifiers = DISPLAY_MODIFIER_ORDER.filter(modifier => parsed.modifiers.includes(modifier)).map(
|
|
678
|
+
modifier => DARWIN_ACCESSIBLE_MODIFIER_LABELS[modifier],
|
|
679
|
+
);
|
|
680
|
+
const expanded = [...modifiers, formatBaseKey(parsed.baseKey, DARWIN_ACCESSIBLE_KEY_LABELS)].join("+");
|
|
681
|
+
return concise === expanded ? concise : `${concise} (${expanded})`;
|
|
556
682
|
}
|
|
557
683
|
|
|
558
|
-
export function
|
|
559
|
-
|
|
684
|
+
export function formatKeyHints(
|
|
685
|
+
keys: string | readonly string[],
|
|
686
|
+
context: KeyDisplayContext = runtimeKeyDisplayContext,
|
|
687
|
+
): string {
|
|
688
|
+
const list: readonly string[] = typeof keys === "string" ? [keys] : keys;
|
|
689
|
+
return list.map(key => formatKeyHint(key, context)).join("/");
|
|
560
690
|
}
|
|
561
691
|
|
|
562
|
-
export function
|
|
563
|
-
|
|
564
|
-
|
|
692
|
+
export function formatAccessibleKeyHints(
|
|
693
|
+
keys: string | readonly string[],
|
|
694
|
+
context: KeyDisplayContext = runtimeKeyDisplayContext,
|
|
695
|
+
): string {
|
|
696
|
+
const list: readonly string[] = typeof keys === "string" ? [keys] : keys;
|
|
697
|
+
return list.map(key => formatAccessibleKeyHint(key, context)).join("/");
|
|
565
698
|
}
|
|
566
699
|
|
|
567
700
|
export type { Keybinding, KeybindingsConfig, KeyId };
|
|
@@ -2,12 +2,15 @@ import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
2
2
|
import type { Api, Model } from "@gajae-code/ai";
|
|
3
3
|
import type { AgentSession } from "../session/agent-session";
|
|
4
4
|
import { formatClampedModelSelector } from "../thinking";
|
|
5
|
+
import { validateModelProfileName } from "./model-profile-contract";
|
|
5
6
|
import {
|
|
6
7
|
aggregateModelProfileRequiredProviders,
|
|
7
|
-
formatAvailableProfileNames,
|
|
8
8
|
formatModelProfileDisplayLabel,
|
|
9
9
|
resolveProfileBindings,
|
|
10
10
|
} from "./model-profiles";
|
|
11
|
+
|
|
12
|
+
export { resolveModelProfileName } from "./model-profile-contract";
|
|
13
|
+
|
|
11
14
|
import {
|
|
12
15
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
13
16
|
type GjcModelAssignmentTargetId,
|
|
@@ -24,8 +27,6 @@ import {
|
|
|
24
27
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
|
|
25
28
|
import type { Settings } from "./settings";
|
|
26
29
|
|
|
27
|
-
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
|
|
28
|
-
|
|
29
30
|
type ModelProfileActivationSession = Pick<
|
|
30
31
|
AgentSession,
|
|
31
32
|
"model" | "thinkingLevel" | "sessionId" | "getConfiguredModelChain" | "setConfiguredModelChain"
|
|
@@ -50,7 +51,7 @@ export interface PrepareModelProfileActivationOptions {
|
|
|
50
51
|
| "resolveCanonicalModel"
|
|
51
52
|
| "getCanonicalVariants"
|
|
52
53
|
| "getCanonicalId"
|
|
53
|
-
|
|
54
|
+
> & { getError?: ModelRegistry["getError"] };
|
|
54
55
|
settings: Pick<Settings, "get">;
|
|
55
56
|
profileName: string;
|
|
56
57
|
}
|
|
@@ -236,14 +237,6 @@ export function formatModelProfileCredentialError(profileLabel: string, provider
|
|
|
236
237
|
return `Model profile "${profileLabel}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
export function resolveModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
240
|
-
// A retired-name alias is fallback-only: never shadow a profile that actually
|
|
241
|
-
// exists under the requested name (e.g. a user-defined `codex-standard`).
|
|
242
|
-
if (profiles.has(profileName)) return profileName;
|
|
243
|
-
const replacement = LEGACY_MODEL_PROFILE_ALIASES.get(profileName);
|
|
244
|
-
return replacement && profiles.has(replacement) ? replacement : profileName;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
240
|
/**
|
|
248
241
|
* Rewrite a selector only within the selector provider's own alternative group.
|
|
249
242
|
* Strict providers are never rewritten, and authenticated alternative providers
|
|
@@ -348,12 +341,8 @@ export async function prepareModelProfileActivation(
|
|
|
348
341
|
options: PrepareModelProfileActivationOptions,
|
|
349
342
|
): Promise<PreparedModelProfileActivation> {
|
|
350
343
|
const profiles = options.modelRegistry.getModelProfiles();
|
|
351
|
-
const profileName =
|
|
352
|
-
const profile = profiles.get(profileName) ?? options.modelRegistry.getModelProfile(profileName)
|
|
353
|
-
if (!profile) {
|
|
354
|
-
const available = formatAvailableProfileNames(profiles);
|
|
355
|
-
throw new Error(`Unknown model profile "${options.profileName}". Available profiles: ${available}`);
|
|
356
|
-
}
|
|
344
|
+
const profileName = validateModelProfileName(options.profileName, profiles, options.modelRegistry.getError?.());
|
|
345
|
+
const profile = profiles.get(profileName) ?? options.modelRegistry.getModelProfile(profileName)!;
|
|
357
346
|
const profileLabel = formatModelProfileDisplayLabel(profile);
|
|
358
347
|
|
|
359
348
|
const requiredProviders = aggregateModelProfileRequiredProviders(profile.requiredProviders, profile);
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { formatModelProfileDisplayLabel, type ModelProfileDefinition } from "./model-profiles";
|
|
2
|
+
|
|
3
|
+
export const MODEL_PROFILE_DISCOVERY_QUERY = "models.profiles.list";
|
|
4
|
+
export const MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES = 2048;
|
|
5
|
+
const REQUESTED_PROFILE_MAX_BYTES = 256;
|
|
6
|
+
|
|
7
|
+
const LEGACY_MODEL_PROFILE_ALIASES: ReadonlyMap<string, string> = new Map([["codex-standard", "codex-medium"]]);
|
|
8
|
+
|
|
9
|
+
export interface ModelProfileCatalogItem {
|
|
10
|
+
id: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
source: "builtin" | "configured";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UnknownModelProfileDetails {
|
|
16
|
+
requestedProfile: string;
|
|
17
|
+
availableProfiles: string[];
|
|
18
|
+
discoveryQuery: typeof MODEL_PROFILE_DISCOVERY_QUERY;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ModelProfileRegistryErrorDetails {
|
|
22
|
+
requestedProfile?: string;
|
|
23
|
+
availableProfiles: [];
|
|
24
|
+
discoveryQuery: typeof MODEL_PROFILE_DISCOVERY_QUERY;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ModelProfileErrorDetails = UnknownModelProfileDetails | ModelProfileRegistryErrorDetails;
|
|
28
|
+
export type ModelProfileErrorCode = "unknown_model_profile" | "model_profile_registry_error";
|
|
29
|
+
|
|
30
|
+
function truncateUtf8(value: string, maxBytes: number): string {
|
|
31
|
+
if (Buffer.byteLength(value) <= maxBytes) return value;
|
|
32
|
+
let end = value.length;
|
|
33
|
+
while (end > 0 && Buffer.byteLength(value.slice(0, end)) > maxBytes) end--;
|
|
34
|
+
return value.slice(0, end);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function diagnosticProfileEcho(value: string): string {
|
|
38
|
+
return truncateUtf8(value.normalize("NFKC").replace(/[\p{Cc}\p{Cf}]+/gu, " "), REQUESTED_PROFILE_MAX_BYTES);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function boundedAvailableProfiles(requestedProfile: string, profiles: ReadonlyMap<string, unknown>): string[] {
|
|
42
|
+
const availableProfiles: string[] = [];
|
|
43
|
+
for (const id of [...new Set(profiles.keys())].sort((left, right) => left.localeCompare(right))) {
|
|
44
|
+
const candidate: UnknownModelProfileDetails = {
|
|
45
|
+
requestedProfile,
|
|
46
|
+
availableProfiles: [...availableProfiles, id],
|
|
47
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
48
|
+
};
|
|
49
|
+
if (Buffer.byteLength(JSON.stringify(candidate)) > MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES) continue;
|
|
50
|
+
availableProfiles.push(id);
|
|
51
|
+
}
|
|
52
|
+
return availableProfiles;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class UnknownModelProfileError extends Error {
|
|
56
|
+
readonly code = "unknown_model_profile" as const;
|
|
57
|
+
readonly details: UnknownModelProfileDetails;
|
|
58
|
+
|
|
59
|
+
constructor(requestedProfile: string, profiles: ReadonlyMap<string, unknown>) {
|
|
60
|
+
const echoed = diagnosticProfileEcho(requestedProfile);
|
|
61
|
+
const availableProfiles = boundedAvailableProfiles(echoed, profiles);
|
|
62
|
+
const available = availableProfiles.length > 0 ? availableProfiles.join(", ") : "none";
|
|
63
|
+
super(
|
|
64
|
+
truncateUtf8(
|
|
65
|
+
`Unknown model profile ${JSON.stringify(echoed)}. Available profiles: ${available}. Query ${MODEL_PROFILE_DISCOVERY_QUERY} for the complete catalog.`,
|
|
66
|
+
512,
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
this.name = "UnknownModelProfileError";
|
|
70
|
+
this.details = {
|
|
71
|
+
requestedProfile: echoed,
|
|
72
|
+
availableProfiles,
|
|
73
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class ModelProfileRegistryError extends Error {
|
|
79
|
+
readonly code = "model_profile_registry_error" as const;
|
|
80
|
+
readonly details: ModelProfileRegistryErrorDetails;
|
|
81
|
+
|
|
82
|
+
constructor(requestedProfile?: string) {
|
|
83
|
+
super(
|
|
84
|
+
`The model profile registry is unavailable. Query ${MODEL_PROFILE_DISCOVERY_QUERY} after fixing models.yml.`,
|
|
85
|
+
);
|
|
86
|
+
this.name = "ModelProfileRegistryError";
|
|
87
|
+
const echoed = requestedProfile === undefined ? undefined : diagnosticProfileEcho(requestedProfile);
|
|
88
|
+
this.details = {
|
|
89
|
+
...(echoed ? { requestedProfile: echoed } : {}),
|
|
90
|
+
availableProfiles: [],
|
|
91
|
+
discoveryQuery: MODEL_PROFILE_DISCOVERY_QUERY,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resolveModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
97
|
+
if (profiles.has(profileName)) return profileName;
|
|
98
|
+
const replacement = LEGACY_MODEL_PROFILE_ALIASES.get(profileName);
|
|
99
|
+
return replacement && profiles.has(replacement) ? replacement : profileName;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function validateModelProfileName(
|
|
103
|
+
profileName: string,
|
|
104
|
+
profiles: ReadonlyMap<string, unknown>,
|
|
105
|
+
registryError?: unknown,
|
|
106
|
+
): string {
|
|
107
|
+
if (registryError !== undefined) throw new ModelProfileRegistryError(profileName);
|
|
108
|
+
const resolved = resolveModelProfileName(profileName, profiles);
|
|
109
|
+
if (!profiles.has(resolved)) throw new UnknownModelProfileError(profileName, profiles);
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function projectModelProfileCatalog(
|
|
114
|
+
profiles: ReadonlyMap<string, ModelProfileDefinition>,
|
|
115
|
+
registryError?: unknown,
|
|
116
|
+
): ModelProfileCatalogItem[] {
|
|
117
|
+
if (registryError !== undefined) throw new ModelProfileRegistryError();
|
|
118
|
+
return [...profiles.entries()]
|
|
119
|
+
.map(([id, definition]) => ({
|
|
120
|
+
id,
|
|
121
|
+
displayName: formatModelProfileDisplayLabel(definition),
|
|
122
|
+
source: definition.source === "user" ? ("configured" as const) : ("builtin" as const),
|
|
123
|
+
}))
|
|
124
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isModelProfileError(value: unknown): value is {
|
|
128
|
+
code: ModelProfileErrorCode;
|
|
129
|
+
message: string;
|
|
130
|
+
details: ModelProfileErrorDetails;
|
|
131
|
+
} {
|
|
132
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
133
|
+
const error = value as Record<string, unknown>;
|
|
134
|
+
if (
|
|
135
|
+
(error.code !== "unknown_model_profile" && error.code !== "model_profile_registry_error") ||
|
|
136
|
+
typeof error.message !== "string" ||
|
|
137
|
+
!error.details ||
|
|
138
|
+
typeof error.details !== "object" ||
|
|
139
|
+
Array.isArray(error.details)
|
|
140
|
+
)
|
|
141
|
+
return false;
|
|
142
|
+
const details = error.details as Record<string, unknown>;
|
|
143
|
+
const detailKeys = Object.keys(details);
|
|
144
|
+
if (
|
|
145
|
+
!detailKeys.every(key => key === "requestedProfile" || key === "availableProfiles" || key === "discoveryQuery") ||
|
|
146
|
+
details.discoveryQuery !== MODEL_PROFILE_DISCOVERY_QUERY ||
|
|
147
|
+
!Array.isArray(details.availableProfiles) ||
|
|
148
|
+
!details.availableProfiles.every(id => typeof id === "string") ||
|
|
149
|
+
Buffer.byteLength(JSON.stringify(details)) > MODEL_PROFILE_ERROR_DETAIL_MAX_BYTES ||
|
|
150
|
+
(typeof details.requestedProfile === "string" &&
|
|
151
|
+
Buffer.byteLength(details.requestedProfile) > REQUESTED_PROFILE_MAX_BYTES)
|
|
152
|
+
)
|
|
153
|
+
return false;
|
|
154
|
+
if (error.code === "unknown_model_profile")
|
|
155
|
+
return detailKeys.length === 3 && typeof details.requestedProfile === "string";
|
|
156
|
+
return (
|
|
157
|
+
detailKeys.length === (details.requestedProfile === undefined ? 2 : 3) &&
|
|
158
|
+
details.availableProfiles.length === 0 &&
|
|
159
|
+
(details.requestedProfile === undefined || typeof details.requestedProfile === "string")
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { sanitizeText } from "@gajae-code/utils";
|
|
2
|
-
import type { GjcModelAssignmentTargetId } from "./model-registry";
|
|
3
2
|
import { type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
|
|
4
|
-
import type { ModelsConfig } from "./models-config-schema";
|
|
3
|
+
import type { GJC_MODEL_ASSIGNMENT_TARGET_IDS, ModelsConfig } from "./models-config-schema";
|
|
5
4
|
|
|
6
|
-
export type ModelProfileRole =
|
|
5
|
+
export type ModelProfileRole = (typeof GJC_MODEL_ASSIGNMENT_TARGET_IDS)[number];
|
|
7
6
|
|
|
8
7
|
export interface ModelProfileDefinition {
|
|
9
8
|
name: string;
|
|
@@ -2049,7 +2049,10 @@ export class ModelRegistry {
|
|
|
2049
2049
|
const modelsUrl = `${baseUrl}/models`;
|
|
2050
2050
|
|
|
2051
2051
|
const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
|
|
2052
|
-
|
|
2052
|
+
// Resolve with the same baseUrl context completion requests use so an
|
|
2053
|
+
// endpoint-scoped (or config-pinned) credential wins here exactly as it
|
|
2054
|
+
// does for chat completions.
|
|
2055
|
+
const apiKey = await this.authStorage.getApiKey(providerConfig.provider, undefined, { baseUrl });
|
|
2053
2056
|
if (apiKey && apiKey !== DEFAULT_LOCAL_TOKEN && apiKey !== kNoAuth) {
|
|
2054
2057
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
2055
2058
|
}
|
|
@@ -2059,6 +2062,13 @@ export class ModelRegistry {
|
|
|
2059
2062
|
signal: AbortSignal.timeout(250),
|
|
2060
2063
|
});
|
|
2061
2064
|
if (!response.ok) {
|
|
2065
|
+
if (response.status === 401 || response.status === 403) {
|
|
2066
|
+
// Redacted by construction: name the provider, endpoint, and the
|
|
2067
|
+
// config surface to fix — never the resolved key.
|
|
2068
|
+
throw new Error(
|
|
2069
|
+
`HTTP ${response.status} from ${modelsUrl}: provider "${providerConfig.provider}" credential was rejected for OpenAI models-list discovery; check providers.${providerConfig.provider}.apiKey/apiKeyEnv.`,
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2062
2072
|
throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
2063
2073
|
}
|
|
2064
2074
|
const payload = (await response.json()) as { data?: Array<{ id: string }> };
|
|
@@ -1343,6 +1343,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
1343
1343
|
description: "Suggest emojis from `:name:` shortcodes and expand text emoticons like `:D` or `:-)`",
|
|
1344
1344
|
},
|
|
1345
1345
|
},
|
|
1346
|
+
promptSuggestions: {
|
|
1347
|
+
type: "boolean",
|
|
1348
|
+
default: false,
|
|
1349
|
+
ui: {
|
|
1350
|
+
tab: "interaction",
|
|
1351
|
+
label: "Prompt Suggestions",
|
|
1352
|
+
description:
|
|
1353
|
+
"Predict your likely next prompt after each turn (smol-model call) and show it as ghost text; Tab accepts",
|
|
1354
|
+
},
|
|
1355
|
+
},
|
|
1346
1356
|
|
|
1347
1357
|
"startup.quiet": {
|
|
1348
1358
|
type: "boolean",
|
|
@@ -3299,7 +3309,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
3299
3309
|
},
|
|
3300
3310
|
"providers.image": {
|
|
3301
3311
|
type: "enum",
|
|
3302
|
-
values: ["auto", "openai", "gemini", "openrouter", "antigravity", "custom"] as const,
|
|
3312
|
+
values: ["auto", "openai", "gemini", "openrouter", "antigravity", "alibaba", "custom"] as const,
|
|
3303
3313
|
default: "auto",
|
|
3304
3314
|
ui: {
|
|
3305
3315
|
tab: "providers",
|
|
@@ -3309,12 +3319,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
3309
3319
|
{
|
|
3310
3320
|
value: "auto",
|
|
3311
3321
|
label: "Auto",
|
|
3312
|
-
description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini",
|
|
3322
|
+
description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini > Alibaba",
|
|
3313
3323
|
},
|
|
3314
3324
|
{ value: "openai", label: "OpenAI", description: "Uses gpt-image-2 via OpenAI Responses/Codex" },
|
|
3315
3325
|
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
3316
3326
|
{ value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
|
|
3317
3327
|
{ value: "antigravity", label: "Antigravity", description: "Requires login with google-antigravity" },
|
|
3328
|
+
{
|
|
3329
|
+
value: "alibaba",
|
|
3330
|
+
label: "Alibaba Bailian",
|
|
3331
|
+
description: "Requires ALIBABA_TOKEN_PLAN_API_KEY (wan2.7-image via Token Plan)",
|
|
3332
|
+
},
|
|
3318
3333
|
{
|
|
3319
3334
|
value: "custom",
|
|
3320
3335
|
label: "Custom",
|