@gajae-code/coding-agent 0.1.1 → 0.1.3

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/types/config/model-registry.d.ts +8 -0
  3. package/dist/types/config/model-resolver.d.ts +4 -1
  4. package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
  5. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +26 -0
  6. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +44 -0
  7. package/dist/types/goals/tools/goal-tool.d.ts +4 -4
  8. package/dist/types/hooks/skill-state.d.ts +3 -0
  9. package/dist/types/modes/components/model-selector.d.ts +5 -7
  10. package/dist/types/modes/interactive-mode.d.ts +1 -0
  11. package/dist/types/sdk.d.ts +2 -4
  12. package/dist/types/session/agent-session.d.ts +3 -9
  13. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +28 -0
  14. package/package.json +13 -9
  15. package/src/config/model-registry.ts +45 -0
  16. package/src/config/model-resolver.ts +5 -1
  17. package/src/defaults/gjc/skills/deep-interview/SKILL.md +30 -30
  18. package/src/defaults/gjc/skills/team/SKILL.md +1 -0
  19. package/src/defaults/gjc/skills/ultragoal/SKILL.md +51 -21
  20. package/src/gjc-runtime/team-runtime.ts +80 -1
  21. package/src/gjc-runtime/ultragoal-guard.ts +239 -0
  22. package/src/gjc-runtime/ultragoal-runtime.ts +318 -4
  23. package/src/goals/tools/goal-tool.ts +10 -4
  24. package/src/hooks/native-skill-hook.ts +26 -0
  25. package/src/hooks/skill-state.ts +59 -0
  26. package/src/main.ts +2 -17
  27. package/src/modes/components/model-selector.ts +225 -33
  28. package/src/modes/controllers/selector-controller.ts +16 -3
  29. package/src/modes/interactive-mode.ts +34 -22
  30. package/src/modes/prompt-action-autocomplete.ts +40 -15
  31. package/src/sdk.ts +3 -1
  32. package/src/session/agent-session.ts +40 -4
  33. package/src/setup/model-onboarding-guidance.ts +5 -3
  34. package/src/skill-state/deep-interview-mutation-guard.ts +303 -0
  35. package/src/slash-commands/builtin-registry.ts +130 -11
  36. package/src/tools/ask.ts +55 -17
  37. package/src/tools/ast-edit.ts +7 -0
  38. package/src/tools/bash.ts +2 -1
  39. package/src/tools/gh.ts +37 -9
  40. package/src/tools/image-gen.ts +19 -10
  41. package/src/tools/path-utils.ts +1 -0
@@ -121,9 +121,14 @@ const HINT_SHIMMER_PALETTE: ShimmerPalette = {
121
121
  };
122
122
 
123
123
  function configureDefaultComposerChrome(editor: CustomEditor): void {
124
- editor.setBorderVisible(false);
125
- editor.setPromptGutter(`${theme.fg("accent", "›")} `);
124
+ editor.setBorderVisible(true);
125
+ editor.setBorderStyle("sharp");
126
+ editor.setClosedBorderBox(true);
127
+ editor.setPromptGutter(undefined);
128
+ editor.setInputPrefix(`${theme.fg("accent", ">")} `);
129
+ editor.setPlaceholder("Type your message...");
126
130
  editor.setPaddingX(1);
131
+ editor.setTopBorder(undefined);
127
132
  }
128
133
 
129
134
  interface WorkingMessageAccent {
@@ -376,7 +381,7 @@ export class InteractiveMode implements InteractiveModeContext {
376
381
  this.#syncEditorMaxHeight();
377
382
  this.#resizeHandler = () => {
378
383
  this.#syncEditorMaxHeight();
379
- this.updateEditorTopBorder();
384
+ this.updateEditorChrome();
380
385
  };
381
386
  process.stdout.on("resize", this.#resizeHandler);
382
387
  try {
@@ -500,7 +505,7 @@ export class InteractiveMode implements InteractiveModeContext {
500
505
  this.ui.addChild(this.statusContainer);
501
506
  this.ui.addChild(this.todoContainer);
502
507
  this.ui.addChild(this.btwContainer);
503
- this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer stays borderless.
508
+ this.ui.addChild(this.statusLine); // Main status rail + hook statuses; composer chrome is rendered by the editor.
504
509
  this.ui.addChild(this.hookWidgetContainerAbove);
505
510
  this.ui.addChild(this.editorContainer);
506
511
  this.ui.addChild(this.hookWidgetContainerBelow);
@@ -526,7 +531,7 @@ export class InteractiveMode implements InteractiveModeContext {
526
531
  this.ui.start();
527
532
  pushTerminalTitle();
528
533
  setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
529
- this.updateEditorBorderColor();
534
+ this.updateEditorChrome();
530
535
  this.#syncEditorMaxHeight();
531
536
  this.isInitialized = true;
532
537
  this.ui.requestRender(true);
@@ -544,7 +549,7 @@ export class InteractiveMode implements InteractiveModeContext {
544
549
  const draft = await this.sessionManager.consumeDraft();
545
550
  if (draft && !this.editor.getText()) {
546
551
  this.editor.setText(draft);
547
- this.updateEditorBorderColor();
552
+ this.updateEditorChrome();
548
553
  this.ui.requestRender();
549
554
  }
550
555
  } catch (err) {
@@ -564,7 +569,7 @@ export class InteractiveMode implements InteractiveModeContext {
564
569
  clearRenderCache();
565
570
  configureDefaultComposerChrome(this.editor);
566
571
  this.ui.invalidate();
567
- this.updateEditorBorderColor();
572
+ this.updateEditorChrome();
568
573
  this.ui.requestRender();
569
574
  });
570
575
 
@@ -577,12 +582,12 @@ export class InteractiveMode implements InteractiveModeContext {
577
582
 
578
583
  // Set up git branch watcher
579
584
  this.statusLine.watchBranch(() => {
580
- this.updateEditorTopBorder();
585
+ this.updateEditorChrome();
581
586
  this.ui.requestRender();
582
587
  });
583
588
 
584
589
  // Initial top border update
585
- this.updateEditorTopBorder();
590
+ this.updateEditorChrome();
586
591
  }
587
592
 
588
593
  /** Reload slash commands and autocomplete for the provided working directory. */
@@ -749,7 +754,7 @@ export class InteractiveMode implements InteractiveModeContext {
749
754
  this.loopLimit = undefined;
750
755
  this.#cancelLoopAutoSubmit();
751
756
  this.statusLine.setLoopModeStatus(undefined);
752
- this.updateEditorTopBorder();
757
+ this.updateEditorChrome();
753
758
  this.ui.requestRender();
754
759
  if (wasEnabled) {
755
760
  this.showStatus(message);
@@ -780,7 +785,7 @@ export class InteractiveMode implements InteractiveModeContext {
780
785
  this.loopPrompt = undefined;
781
786
  this.loopLimit = createLoopLimitRuntime(parsedLimit);
782
787
  this.statusLine.setLoopModeStatus({ enabled: true });
783
- this.updateEditorTopBorder();
788
+ this.updateEditorChrome();
784
789
  this.ui.requestRender();
785
790
  const limitSuffix = parsedLimit ? ` Limited to ${describeLoopLimit(parsedLimit)}.` : "";
786
791
  const remainingSuffix = this.loopLimit ? ` ${describeLoopLimitRuntime(this.loopLimit)}.` : "";
@@ -874,7 +879,7 @@ export class InteractiveMode implements InteractiveModeContext {
874
879
  this.rebuildChatFromMessages();
875
880
  this.editor.setText(submission.text);
876
881
  }
877
- this.updateEditorBorderColor();
882
+ this.updateEditorChrome();
878
883
  this.ui.requestRender();
879
884
  return true;
880
885
  }
@@ -921,7 +926,7 @@ export class InteractiveMode implements InteractiveModeContext {
921
926
  this.editor.setMaxHeight(this.#computeEditorMaxHeight());
922
927
  }
923
928
 
924
- updateEditorBorderColor(): void {
929
+ updateEditorChrome(): void {
925
930
  if (this.isBashMode) {
926
931
  this.editor.borderColor = theme.getBashModeBorderColor();
927
932
  } else if (this.isPythonMode) {
@@ -938,13 +943,21 @@ export class InteractiveMode implements InteractiveModeContext {
938
943
  this.editor.borderColor = theme.getThinkingBorderColor(level);
939
944
  }
940
945
  }
941
- this.updateEditorTopBorder();
946
+ this.#setComposerTopBorder();
942
947
  this.ui.requestRender();
943
948
  }
944
949
 
950
+ updateEditorBorderColor(): void {
951
+ this.updateEditorChrome();
952
+ }
953
+
945
954
  updateEditorTopBorder(): void {
946
- // The opencode-style composer is intentionally borderless. Keep status-line
947
- // rendering out of the input area so the prompt remains a simple gutter + body.
955
+ this.#setComposerTopBorder();
956
+ }
957
+
958
+ #setComposerTopBorder(): void {
959
+ // Keep the composer as a plain closed input rectangle; status-line
960
+ // rendering stays outside the input area.
948
961
  this.editor.setTopBorder(undefined);
949
962
  }
950
963
 
@@ -1048,7 +1061,7 @@ export class InteractiveMode implements InteractiveModeContext {
1048
1061
  }
1049
1062
  : undefined;
1050
1063
  this.statusLine.setPlanModeStatus(status);
1051
- this.updateEditorTopBorder();
1064
+ this.updateEditorChrome();
1052
1065
  this.ui.requestRender();
1053
1066
  }
1054
1067
 
@@ -1058,7 +1071,7 @@ export class InteractiveMode implements InteractiveModeContext {
1058
1071
  ? { enabled: this.goalModeEnabled, paused: this.goalModePaused }
1059
1072
  : undefined;
1060
1073
  this.statusLine.setGoalModeStatus(status);
1061
- this.updateEditorTopBorder();
1074
+ this.updateEditorChrome();
1062
1075
  this.ui.requestRender();
1063
1076
  }
1064
1077
 
@@ -1650,7 +1663,7 @@ export class InteractiveMode implements InteractiveModeContext {
1650
1663
  const applied = await this.sessionManager.setSessionName(seededName, "auto");
1651
1664
  if (applied) {
1652
1665
  setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd());
1653
- this.updateEditorBorderColor();
1666
+ this.updateEditorChrome();
1654
1667
  }
1655
1668
  }
1656
1669
 
@@ -2121,8 +2134,7 @@ export class InteractiveMode implements InteractiveModeContext {
2121
2134
  logger.warn("Failed to refresh slash command state for custom editor", { error: String(error) });
2122
2135
  });
2123
2136
 
2124
- this.updateEditorBorderColor();
2125
- this.updateEditorTopBorder();
2137
+ this.updateEditorChrome();
2126
2138
  this.ui.requestRender();
2127
2139
  }
2128
2140
 
@@ -2417,7 +2429,7 @@ export class InteractiveMode implements InteractiveModeContext {
2417
2429
  } else {
2418
2430
  this.#cleanupMicAnimation();
2419
2431
  }
2420
- this.updateEditorTopBorder();
2432
+ this.updateEditorChrome();
2421
2433
  this.ui.requestRender();
2422
2434
  },
2423
2435
  });
@@ -84,6 +84,34 @@ function isSkillCommandAutocompleteItem(item: AutocompleteItem): item is SkillCo
84
84
  return "normalizedSkillCommand" in item && item.normalizedSkillCommand === true;
85
85
  }
86
86
 
87
+ function mergeAutocompleteSuggestions(
88
+ primary: { items: AutocompleteItem[]; prefix: string } | null,
89
+ secondary: { items: AutocompleteItem[]; prefix: string } | null,
90
+ ): { items: AutocompleteItem[]; prefix: string } | null {
91
+ if (!primary) return secondary;
92
+ if (!secondary) return primary;
93
+ if (primary.prefix !== secondary.prefix) return primary;
94
+
95
+ const seen = new Set<string>();
96
+ const items: AutocompleteItem[] = [];
97
+ for (const item of [...primary.items, ...secondary.items]) {
98
+ const key = `${item.value}\0${item.label}`;
99
+ if (seen.has(key)) continue;
100
+ seen.add(key);
101
+ items.push(item);
102
+ }
103
+
104
+ return { items, prefix: primary.prefix };
105
+ }
106
+
107
+ function withoutSkillCommandSuggestions(
108
+ suggestions: { items: AutocompleteItem[]; prefix: string } | null,
109
+ ): { items: AutocompleteItem[]; prefix: string } | null {
110
+ if (!suggestions) return null;
111
+ const items = suggestions.items.filter(item => !item.value.startsWith("skill:"));
112
+ return items.length > 0 ? { ...suggestions, items } : null;
113
+ }
114
+
87
115
  function getPromptActionPrefix(textBeforeCursor: string): string | null {
88
116
  const hashIndex = textBeforeCursor.lastIndexOf("#");
89
117
  if (hashIndex === -1) return null;
@@ -148,8 +176,14 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
148
176
  }
149
177
  }
150
178
 
151
- const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
152
- if (skillCommandSuggestions) return skillCommandSuggestions;
179
+ const slashPrefix = getSlashTokenPrefix(textBeforeCursor);
180
+ if (slashPrefix) {
181
+ const baseSuggestions = withoutSkillCommandSuggestions(
182
+ await this.#baseProvider.getSuggestions(lines, cursorLine, cursorCol),
183
+ );
184
+ const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
185
+ return mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions);
186
+ }
153
187
 
154
188
  if (!isSettingsInitialized() || settings.get("emojiAutocomplete")) {
155
189
  const emojiSuggestions = getEmojiSuggestions(textBeforeCursor);
@@ -215,9 +249,11 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
215
249
  return this.#baseProvider.getInlineHint?.(lines, cursorLine, cursorCol) ?? null;
216
250
  }
217
251
  trySyncSlashCompletion(textBeforeCursor: string): { items: AutocompleteItem[]; prefix: string } | null {
252
+ const baseSuggestions = withoutSkillCommandSuggestions(
253
+ this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null,
254
+ );
218
255
  const skillCommandSuggestions = this.#getSkillCommandSuggestions(textBeforeCursor);
219
- if (skillCommandSuggestions) return skillCommandSuggestions;
220
- return this.#baseProvider.trySyncSlashCompletion?.(textBeforeCursor) ?? null;
256
+ return mergeAutocompleteSuggestions(baseSuggestions, skillCommandSuggestions);
221
257
  }
222
258
  trySyncInlineReplace(textBeforeCursor: string): { replaceLen: number; insert: string } | null {
223
259
  if (isSettingsInitialized() && !settings.get("emojiAutocomplete")) return null;
@@ -233,17 +269,6 @@ export class PromptActionAutocompleteProvider implements AutocompleteProvider {
233
269
  const exactNonSkillCommand = this.#commands.some(
234
270
  command => command.name === query && !command.name.startsWith("skill:"),
235
271
  );
236
- if (exactNonSkillCommand) {
237
- const command = this.#commands.find(
238
- candidate => candidate.name === query && !candidate.name.startsWith("skill:"),
239
- );
240
- if (command) {
241
- return {
242
- items: [{ value: command.name, label: command.name, description: command.description }],
243
- prefix,
244
- };
245
- }
246
- }
247
272
  const items = this.#commands
248
273
  .filter(command => command.name.startsWith("skill:"))
249
274
  .map(command => {
package/src/sdk.ts CHANGED
@@ -40,6 +40,7 @@ import {
40
40
  parseModelString,
41
41
  resolveAllowedModels,
42
42
  resolveModelRoleValue,
43
+ type ScopedModelSelection,
43
44
  } from "./config/model-resolver";
44
45
  import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
45
46
  import { Settings, type SkillsSettings } from "./config/settings";
@@ -230,7 +231,7 @@ export interface CreateAgentSessionOptions {
230
231
  /** Thinking selector. Default: from settings, else unset */
231
232
  thinkingLevel?: ThinkingLevel;
232
233
  /** Models available for cycling (Ctrl+P in interactive mode) */
233
- scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
234
+ scopedModels?: ScopedModelSelection[];
234
235
 
235
236
  /** System prompt blocks. Array replaces default, function receives default blocks and returns final blocks. */
236
237
  systemPrompt?: string[] | ((defaultPrompt: string[]) => string[]);
@@ -1760,6 +1761,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1760
1761
  }
1761
1762
  return key;
1762
1763
  },
1764
+ getAuthCredentialType: provider => modelRegistry.getSessionCredentialType(provider, agent.sessionId),
1763
1765
  streamFn: (streamModel, context, streamOptions) =>
1764
1766
  streamSimple(streamModel, context, {
1765
1767
  ...streamOptions,
@@ -95,6 +95,7 @@ import {
95
95
  parseModelString,
96
96
  type ResolvedModelRoleValue,
97
97
  resolveModelRoleValue,
98
+ type ScopedModelSelection,
98
99
  } from "../config/model-resolver";
99
100
  import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
100
101
  import type { Settings, SkillsSettings } from "../config/settings";
@@ -167,6 +168,7 @@ import {
167
168
  import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
168
169
  import { formatNoCredentialOnboardingError, formatNoModelOnboardingError } from "../setup/model-onboarding-guidance";
169
170
  import { isCanonicalGjcWorkflowSkill, syncSkillActiveState } from "../skill-state/active-state";
171
+ import { assertDeepInterviewMutationAllowed } from "../skill-state/deep-interview-mutation-guard";
170
172
  import { invalidateHostMetadata } from "../ssh/connection-manager";
171
173
  import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
172
174
  import {
@@ -257,7 +259,7 @@ export interface AgentSessionConfig {
257
259
  sessionManager: SessionManager;
258
260
  settings: Settings;
259
261
  /** Models to cycle through with Ctrl+P (from --models flag) */
260
- scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
262
+ scopedModels?: ScopedModelSelection[];
261
263
  /** Initial session thinking selector. */
262
264
  thinkingLevel?: ThinkingLevel;
263
265
  /** Prompt templates for expansion */
@@ -747,7 +749,7 @@ export class AgentSession {
747
749
 
748
750
  readonly configWarnings: string[] = [];
749
751
 
750
- #scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
752
+ #scopedModels: ScopedModelSelection[];
751
753
  #thinkingLevel: ThinkingLevel | undefined;
752
754
  #promptTemplates: PromptTemplate[];
753
755
  #slashCommands: FileSlashCommand[];
@@ -3273,6 +3275,35 @@ export class AgentSession {
3273
3275
  }) as T;
3274
3276
  }
3275
3277
 
3278
+ /**
3279
+ * Wrap a tool with the deep-interview mutation guard. This guard is intentionally
3280
+ * outermost so active interviews reject product-code mutation before ACP permission
3281
+ * prompts or tool execution can run.
3282
+ */
3283
+ #wrapToolForDeepInterviewMutationGuard<T extends AgentTool>(tool: T): T {
3284
+ if (!["edit", "write", "ast_edit"].includes(tool.name)) return tool;
3285
+ return new Proxy(tool, {
3286
+ get: (target, prop) => {
3287
+ if (prop !== "execute") return Reflect.get(target, prop, target);
3288
+ return async (
3289
+ toolCallId: string,
3290
+ args: unknown,
3291
+ signal: AbortSignal | undefined,
3292
+ onUpdate: never,
3293
+ ctx: never,
3294
+ ) => {
3295
+ await assertDeepInterviewMutationAllowed({
3296
+ cwd: this.sessionManager.getCwd(),
3297
+ sessionId: this.sessionManager.getSessionId(),
3298
+ tool: target,
3299
+ args,
3300
+ });
3301
+ return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
3302
+ };
3303
+ },
3304
+ }) as T;
3305
+ }
3306
+
3276
3307
  async #applyActiveToolsByName(
3277
3308
  toolNames: string[],
3278
3309
  options?: { persistMCPSelection?: boolean; previousSelectedMCPToolNames?: string[] },
@@ -3284,7 +3315,7 @@ export class AgentSession {
3284
3315
  for (const name of toolNames) {
3285
3316
  const tool = this.#toolRegistry.get(name);
3286
3317
  if (tool) {
3287
- tools.push(this.#wrapToolForAcpPermission(tool));
3318
+ tools.push(this.#wrapToolForDeepInterviewMutationGuard(this.#wrapToolForAcpPermission(tool)));
3288
3319
  validToolNames.push(name);
3289
3320
  }
3290
3321
  }
@@ -3719,7 +3750,7 @@ export class AgentSession {
3719
3750
  }
3720
3751
 
3721
3752
  /** Scoped models for cycling (from --models flag) */
3722
- get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel?: ThinkingLevel }> {
3753
+ get scopedModels(): ReadonlyArray<ScopedModelSelection> {
3723
3754
  return this.#scopedModels;
3724
3755
  }
3725
3756
 
@@ -6415,6 +6446,7 @@ export class AgentSession {
6415
6446
  metadata: this.agent.metadataForProvider(candidate.provider),
6416
6447
  convertToLlm,
6417
6448
  telemetry,
6449
+ authCredentialType: this.#modelRegistry.getSessionCredentialType(candidate.provider, this.sessionId),
6418
6450
  });
6419
6451
  } catch (error) {
6420
6452
  if (!this.#isCompactionAuthFailure(error)) {
@@ -6670,6 +6702,10 @@ export class AgentSession {
6670
6702
  initiatorOverride: "agent",
6671
6703
  convertToLlm,
6672
6704
  telemetry,
6705
+ authCredentialType: this.#modelRegistry.getSessionCredentialType(
6706
+ candidate.provider,
6707
+ this.sessionId,
6708
+ ),
6673
6709
  });
6674
6710
  break;
6675
6711
  } catch (error) {
@@ -7,14 +7,16 @@ export const MODEL_ONBOARDING_OAUTH_COMMAND = "/provider login [provider-id] or
7
7
  export function formatModelOnboardingGuidance(): string {
8
8
  return [
9
9
  "Model selection only shows configured providers.",
10
+ "Assignment targets are DEFAULT plus the GJC role agents: EXECUTOR, ARCHITECT, PLANNER, and CRITIC.",
11
+ "Legacy model-role aliases are compatibility-only and are not shown as assignment targets.",
10
12
  `API-compatible providers: ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}).`,
11
13
  `OAuth/subscription providers: ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
12
- "Then run /model to select a configured model.",
14
+ "Then run /model to select a configured model or assign it to a target.",
13
15
  ].join("\n");
14
16
  }
15
17
 
16
18
  export function formatModelOnboardingInlineHint(): string {
17
- return `Add API-compatible providers with ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}); OAuth/subscription with ${MODEL_ONBOARDING_OAUTH_COMMAND}; then run /model.`;
19
+ return `Add API-compatible providers with ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}); OAuth/subscription with ${MODEL_ONBOARDING_OAUTH_COMMAND}; then run /model for DEFAULT, EXECUTOR, ARCHITECT, PLANNER, and CRITIC.`;
18
20
  }
19
21
 
20
22
  export function formatNoModelOnboardingError(): string {
@@ -27,7 +29,7 @@ export function formatNoCredentialOnboardingError(providerId: string): string {
27
29
  "",
28
30
  `For API-compatible providers, configure credentials with ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}).`,
29
31
  `For OAuth/subscription providers, use ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
30
- "Then run /model to select a configured model.",
32
+ "Then run /model to select a configured model or assign it to DEFAULT, EXECUTOR, ARCHITECT, PLANNER, or CRITIC.",
31
33
  ].join("\n");
32
34
  }
33
35