@gajae-code/coding-agent 0.1.2 → 0.2.0

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 +30 -1
  2. package/dist/types/commands/gjc-runtime-bridge.d.ts +24 -0
  3. package/dist/types/config/model-registry.d.ts +1 -0
  4. package/dist/types/config/model-resolver.d.ts +4 -1
  5. package/dist/types/gjc-runtime/launch-tmux.d.ts +23 -0
  6. package/dist/types/gjc-runtime/team-runtime.d.ts +40 -1
  7. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +15 -10
  8. package/dist/types/hooks/skill-state.d.ts +4 -1
  9. package/dist/types/modes/components/model-selector.d.ts +2 -4
  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/active-state.d.ts +19 -0
  14. package/dist/types/skill-state/workflow-hud.d.ts +62 -0
  15. package/package.json +9 -9
  16. package/src/commands/deep-interview.ts +21 -2
  17. package/src/commands/gjc-runtime-bridge.ts +161 -15
  18. package/src/commands/ralplan.ts +21 -2
  19. package/src/commands/team.ts +54 -3
  20. package/src/commands/ultragoal.ts +21 -1
  21. package/src/config/model-registry.ts +4 -0
  22. package/src/config/model-resolver.ts +5 -1
  23. package/src/defaults/gjc/skills/deep-interview/SKILL.md +6 -6
  24. package/src/defaults/gjc/skills/ralplan/SKILL.md +5 -9
  25. package/src/defaults/gjc/skills/team/SKILL.md +5 -4
  26. package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -8
  27. package/src/gjc-runtime/launch-tmux.ts +73 -2
  28. package/src/gjc-runtime/team-runtime.ts +365 -35
  29. package/src/gjc-runtime/ultragoal-guard.ts +43 -1
  30. package/src/gjc-runtime/ultragoal-runtime.ts +307 -187
  31. package/src/hooks/skill-state.ts +4 -1
  32. package/src/main.ts +1 -0
  33. package/src/modes/components/model-selector.ts +108 -8
  34. package/src/modes/components/skill-hud/render.ts +35 -8
  35. package/src/modes/interactive-mode.ts +34 -22
  36. package/src/prompts/system/system-prompt.md +5 -4
  37. package/src/sdk.ts +3 -1
  38. package/src/session/agent-session.ts +15 -3
  39. package/src/skill-state/active-state.ts +104 -4
  40. package/src/skill-state/workflow-hud.ts +160 -0
  41. package/src/tools/image-gen.ts +19 -10
package/src/main.ts CHANGED
@@ -601,6 +601,7 @@ async function buildSessionOptions(
601
601
  thinkingLevel: scopedModel.explicitThinkingLevel
602
602
  ? (scopedModel.thinkingLevel ?? defaultThinkingLevel)
603
603
  : defaultThinkingLevel,
604
+ explicitThinkingLevel: scopedModel.explicitThinkingLevel,
604
605
  }));
605
606
  }
606
607
 
@@ -1,5 +1,5 @@
1
1
  import { ThinkingLevel } from "@gajae-code/agent-core";
2
- import { type Model, modelsAreEqual } from "@gajae-code/ai";
2
+ import { getSupportedEfforts, type Model, modelsAreEqual } from "@gajae-code/ai";
3
3
  import {
4
4
  Container,
5
5
  fuzzyFilter,
@@ -14,11 +14,15 @@ import {
14
14
  } from "@gajae-code/tui";
15
15
  import type { GjcModelAssignmentTargetId, ModelRegistry } from "../../config/model-registry";
16
16
  import { GJC_MODEL_ASSIGNMENT_TARGET_IDS, GJC_MODEL_ASSIGNMENT_TARGETS } from "../../config/model-registry";
17
- import { formatModelSelectorValue, resolveModelRoleValue } from "../../config/model-resolver";
17
+ import {
18
+ formatModelSelectorValue,
19
+ resolveModelRoleValue,
20
+ type ScopedModelSelection,
21
+ } from "../../config/model-resolver";
18
22
  import type { Settings } from "../../config/settings";
19
23
  import { type ThemeColor, theme } from "../../modes/theme/theme";
20
24
  import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
21
- import { getThinkingLevelMetadata } from "../../thinking";
25
+ import { getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
22
26
  import { getTabBarTheme } from "../shared";
23
27
  import { DynamicBorder } from "./dynamic-border";
24
28
 
@@ -54,6 +58,7 @@ interface ModelItem {
54
58
  model: Model;
55
59
  selector: string;
56
60
  thinkingLevel?: ThinkingLevel;
61
+ explicitThinkingLevel?: boolean;
57
62
  }
58
63
 
59
64
  interface CanonicalModelItem {
@@ -66,18 +71,22 @@ interface CanonicalModelItem {
66
71
  normalizedSearchText: string;
67
72
  compactSearchText: string;
68
73
  thinkingLevel?: ThinkingLevel;
74
+ explicitThinkingLevel?: boolean;
69
75
  }
70
76
 
71
- interface ScopedModelItem {
72
- model: Model;
73
- thinkingLevel?: ThinkingLevel;
74
- }
77
+ type ScopedModelItem = ScopedModelSelection;
75
78
 
76
79
  interface RoleAssignment {
77
80
  model: Model;
78
81
  thinkingLevel: ThinkingLevel;
79
82
  }
80
83
 
84
+ interface PendingThinkingChoice {
85
+ item: ModelItem | CanonicalModelItem;
86
+ role: GjcModelAssignmentTargetId | null;
87
+ levels: ThinkingLevel[];
88
+ }
89
+
81
90
  type RoleSelectCallback = (
82
91
  model: Model,
83
92
  role: GjcModelAssignmentTargetId | null,
@@ -134,6 +143,8 @@ export class ModelSelectorComponent extends Container {
134
143
  #temporaryOnly: boolean;
135
144
  #pendingActionItem?: ModelItem | CanonicalModelItem;
136
145
  #selectedActionIndex: number = 0;
146
+ #pendingThinkingChoice?: PendingThinkingChoice;
147
+ #selectedThinkingIndex: number = 0;
137
148
 
138
149
  // Tab state
139
150
  #providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
@@ -349,6 +360,7 @@ export class ModelSelectorComponent extends Container {
349
360
  model: scoped.model,
350
361
  selector: `${scoped.model.provider}/${scoped.model.id}`,
351
362
  thinkingLevel: scoped.thinkingLevel,
363
+ explicitThinkingLevel: scoped.explicitThinkingLevel,
352
364
  }));
353
365
  } else {
354
366
  // Reload config and cached discovery state without blocking on live provider refresh
@@ -418,6 +430,10 @@ export class ModelSelectorComponent extends Container {
418
430
  if (scopedThinkingLevel !== undefined) {
419
431
  item.thinkingLevel = scopedThinkingLevel;
420
432
  }
433
+ const scopedModel = models.find(model => `${model.model.provider}/${model.model.id}` === selectedSelector);
434
+ if (scopedModel?.explicitThinkingLevel !== undefined) {
435
+ item.explicitThinkingLevel = scopedModel.explicitThinkingLevel;
436
+ }
421
437
  return item;
422
438
  })
423
439
  .filter((item): item is CanonicalModelItem => item !== undefined);
@@ -722,11 +738,14 @@ export class ModelSelectorComponent extends Container {
722
738
  this.#listContainer.addChild(
723
739
  new Text(theme.fg("muted", ` Model Name: ${selected.model.name}${suffix}`), 0, 0),
724
740
  );
725
- if (this.#pendingActionItem) {
741
+ if (this.#pendingThinkingChoice) {
742
+ this.#renderThinkingMenu(this.#pendingThinkingChoice);
743
+ } else if (this.#pendingActionItem) {
726
744
  this.#renderActionMenu(this.#pendingActionItem);
727
745
  }
728
746
  }
729
747
  }
748
+
730
749
  #renderActionMenu(item: ModelItem | CanonicalModelItem): void {
731
750
  this.#listContainer.addChild(new Spacer(1));
732
751
  this.#listContainer.addChild(new Text(theme.fg("muted", ` Action for: ${item.model.id}`), 0, 0));
@@ -742,6 +761,24 @@ export class ModelSelectorComponent extends Container {
742
761
  }
743
762
  }
744
763
 
764
+ #renderThinkingMenu(choice: PendingThinkingChoice): void {
765
+ const targetLabel = choice.role === null ? "temporary model" : GJC_MODEL_ASSIGNMENT_TARGETS[choice.role].name;
766
+ this.#listContainer.addChild(new Spacer(1));
767
+ this.#listContainer.addChild(
768
+ new Text(theme.fg("muted", ` Reasoning for ${targetLabel}: ${choice.item.model.id}`), 0, 0),
769
+ );
770
+ this.#listContainer.addChild(new Spacer(1));
771
+ for (let i = 0; i < choice.levels.length; i++) {
772
+ const level = choice.levels[i];
773
+ const metadata = getThinkingLevelMetadata(level);
774
+ const prefix = i === this.#selectedThinkingIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
775
+ const label = `${metadata.label} — ${metadata.description}`;
776
+ this.#listContainer.addChild(
777
+ new Text(`${prefix}${i === this.#selectedThinkingIndex ? theme.fg("accent", label) : label}`, 0, 0),
778
+ );
779
+ }
780
+ }
781
+
745
782
  #getCurrentRoleThinkingLevel(role: string): ThinkingLevel {
746
783
  return this.#roles[role]?.thinkingLevel ?? ThinkingLevel.Inherit;
747
784
  }
@@ -753,6 +790,10 @@ export class ModelSelectorComponent extends Container {
753
790
  }
754
791
 
755
792
  handleInput(keyData: string): void {
793
+ if (this.#pendingThinkingChoice) {
794
+ this.#handleThinkingMenuInput(keyData);
795
+ return;
796
+ }
756
797
  if (this.#pendingActionItem) {
757
798
  this.#handleActionMenuInput(keyData);
758
799
  return;
@@ -839,12 +880,50 @@ export class ModelSelectorComponent extends Container {
839
880
  }
840
881
  }
841
882
 
883
+ #handleThinkingMenuInput(keyData: string): void {
884
+ const choice = this.#pendingThinkingChoice;
885
+ if (!choice) return;
886
+ if (matchesKey(keyData, "up")) {
887
+ this.#selectedThinkingIndex =
888
+ this.#selectedThinkingIndex === 0 ? choice.levels.length - 1 : this.#selectedThinkingIndex - 1;
889
+ this.#updateList();
890
+ return;
891
+ }
892
+ if (matchesKey(keyData, "down")) {
893
+ this.#selectedThinkingIndex = (this.#selectedThinkingIndex + 1) % choice.levels.length;
894
+ this.#updateList();
895
+ return;
896
+ }
897
+ if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
898
+ const level = choice.levels[this.#selectedThinkingIndex];
899
+ if (!level) return;
900
+ this.#pendingThinkingChoice = undefined;
901
+ this.#handleSelect(choice.item, choice.role, level);
902
+ return;
903
+ }
904
+ if (getKeybindings().matches(keyData, "tui.select.cancel")) {
905
+ this.#pendingThinkingChoice = undefined;
906
+ if (choice.role !== null) {
907
+ this.#pendingActionItem = choice.item;
908
+ this.#selectedActionIndex = Math.max(0, GJC_MODEL_ASSIGNMENT_TARGET_IDS.indexOf(choice.role));
909
+ }
910
+ this.#updateList();
911
+ }
912
+ }
913
+
842
914
  #handleSelect(
843
915
  item: ModelItem | CanonicalModelItem,
844
916
  role: GjcModelAssignmentTargetId | null,
845
917
  thinkingLevel?: ThinkingLevel,
846
918
  ): void {
847
919
  const itemThinkingLevel = thinkingLevel ?? item.thinkingLevel;
920
+ const hasExplicitThinkingChoice = thinkingLevel !== undefined || item.explicitThinkingLevel === true;
921
+ if (!hasExplicitThinkingChoice && requiresExplicitThinkingChoice(item.model)) {
922
+ this.#pendingThinkingChoice = { item, role, levels: getSelectableThinkingLevels(item.model) };
923
+ this.#selectedThinkingIndex = 0;
924
+ this.#updateList();
925
+ return;
926
+ }
848
927
 
849
928
  // For temporary role, don't save to settings - just notify caller
850
929
  if (role === null) {
@@ -871,6 +950,27 @@ export class ModelSelectorComponent extends Container {
871
950
  }
872
951
  }
873
952
 
953
+ function requiresExplicitThinkingChoice(model: Model): boolean {
954
+ return model.reasoning === true && (model.provider === "openai" || model.provider === "openai-codex");
955
+ }
956
+
957
+ function getSelectableThinkingLevels(model: Model): ThinkingLevel[] {
958
+ const levels: ThinkingLevel[] = [ThinkingLevel.Off];
959
+ let efforts: readonly string[];
960
+ try {
961
+ efforts = getSupportedEfforts(model);
962
+ } catch {
963
+ return levels;
964
+ }
965
+ for (const effort of efforts) {
966
+ const level = parseThinkingLevel(effort);
967
+ if (level && !levels.includes(level)) {
968
+ levels.push(level);
969
+ }
970
+ }
971
+ return levels;
972
+ }
973
+
874
974
  /** Extract the first version number from a model ID (e.g. "gemini-2.5-pro" → 2.5, "Anthropic model-sonnet-4-6" → 4.6). */
875
975
  function extractVersionNumber(id: string): number {
876
976
  // Dot-separated version: "gemini-2.5-pro" → 2.5
@@ -1,4 +1,4 @@
1
- import type { SkillActiveEntry } from "../../../skill-state/active-state";
1
+ import type { SkillActiveEntry, WorkflowHudChip } from "../../../skill-state/active-state";
2
2
 
3
3
  const ANSI_RESET_FG = "\x1b[39m";
4
4
  const ANSI_RESET_BOLD = "\x1b[22m";
@@ -31,16 +31,43 @@ function compareEntries(a: SkillActiveEntry, b: SkillActiveEntry): number {
31
31
  return a.skill.localeCompare(b.skill) || (a.phase ?? "").localeCompare(b.phase ?? "");
32
32
  }
33
33
 
34
+ function compareChips(a: WorkflowHudChip, b: WorkflowHudChip): number {
35
+ return (a.priority ?? 50) - (b.priority ?? 50) || a.label.localeCompare(b.label);
36
+ }
37
+
38
+ function chipPrefix(chip: WorkflowHudChip): string {
39
+ if (chip.severity === "error") return "!";
40
+ if (chip.severity === "blocked") return "block";
41
+ if (chip.severity === "warning") return "warn";
42
+ return "";
43
+ }
44
+
45
+ function formatChip(chip: WorkflowHudChip): string | null {
46
+ const label = sanitizeHudPart(chip.label);
47
+ const value = sanitizeHudPart(chip.value);
48
+ if (!label) return null;
49
+ const body = value ? `${label}=${value}` : label;
50
+ const prefix = chipPrefix(chip);
51
+ return prefix ? `${prefix}:${body}` : body;
52
+ }
53
+
54
+ function formatEntry(entry: SkillActiveEntry): string {
55
+ const skill = sanitizeHudPart(entry.skill);
56
+ const phase = sanitizeHudPart(entry.phase);
57
+ const base = phase ? `${skill}:${phase}` : skill;
58
+ const chips = [...(entry.hud?.chips ?? [])]
59
+ .sort(compareChips)
60
+ .map(formatChip)
61
+ .filter((chip): chip is string => Boolean(chip));
62
+ if (entry.stale === true) chips.unshift("warn:stale");
63
+ const summary = sanitizeHudPart(entry.hud?.summary);
64
+ return [base, summary, ...chips].filter(Boolean).join(" ");
65
+ }
66
+
34
67
  export function renderSkillHudBar(entries: readonly SkillActiveEntry[], width: number): string | null {
35
68
  const active = entries.filter(entry => entry.active !== false && sanitizeHudPart(entry.skill)).sort(compareEntries);
36
69
  if (active.length === 0 || width <= 0) return null;
37
- const body = active
38
- .map(entry => {
39
- const skill = sanitizeHudPart(entry.skill);
40
- const phase = sanitizeHudPart(entry.phase);
41
- return phase ? `${skill}:${phase}` : skill;
42
- })
43
- .join(" + ");
70
+ const body = active.map(formatEntry).join(" + ");
44
71
  const prefix = `${ANSI_BORDER}◆${ANSI_RESET_FG} ${ANSI_BOLD}${ANSI_ACCENT}hud${ANSI_RESET_FG}${ANSI_RESET_BOLD} `;
45
72
  const budget = Math.max(1, width - visibleWidth(prefix));
46
73
  return truncateToWidth(`${prefix}${ANSI_DIM}${truncateToWidth(body, budget)}${ANSI_RESET_BOLD}`, width);
@@ -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
  });
@@ -20,22 +20,23 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
20
20
  <public-workflow-surface>
21
21
  GJC exposes exactly four default workflow skills. Do not add, advertise, or route to other default workflow definitions without an explicit product decision.
22
22
 
23
- <skill name="deep-interview" user-entrypoint="/skill:deep-interview" cli-runtime="gjc deep-interview">
23
+ <skill name="deep-interview" user-entrypoint="/skill:deep-interview" cli-runtime="private-bridge-only: gjc deep-interview">
24
24
  Use for vague ideas that need Socratic requirements gathering, mathematical ambiguity scoring, topology confirmation, and a spec under `.gjc/specs/`. It is a requirements workflow; it must not mutate product code. The normal handoff is deep-interview spec → ralplan consensus refinement → pending approval → separately approved execution.
25
25
  </skill>
26
26
 
27
- <skill name="ralplan" user-entrypoint="/skill:ralplan" cli-runtime="gjc ralplan">
27
+ <skill name="ralplan" user-entrypoint="/skill:ralplan" cli-runtime="private-bridge-only: gjc ralplan">
28
28
  Use for consensus planning when requirements are clear enough to plan but architecture, sequencing, or verification needs Planner/Architect/Critic agreement. Plans belong under `.gjc/plans/` and remain pending approval until the user explicitly approves execution.
29
29
  </skill>
30
30
 
31
- <skill name="ultragoal" user-entrypoint="/skill:ultragoal" cli-runtime="gjc ultragoal">
31
+ <skill name="ultragoal" user-entrypoint="/skill:ultragoal" cli-runtime="native: gjc ultragoal">
32
32
  Use for durable multi-goal execution ledgers under `.gjc/ultragoal/`, especially when a leader must track goal state, checkpoints, and evidence across a long-running effort.
33
33
  </skill>
34
34
 
35
- <skill name="team" user-entrypoint="/skill:team" cli-runtime="gjc team">
35
+ <skill name="team" user-entrypoint="/skill:team" cli-runtime="native: gjc team">
36
36
  Use for tmux-backed coordinated execution with workers, shared state under `.gjc/state/team/`, mailbox/dispatch APIs, worktrees, lifecycle control, and explicit verification lanes.
37
37
  </skill>
38
38
  </public-workflow-surface>
39
+ Agent sessions MUST activate bundled workflow skills via the `/skill:<name>` user-entrypoint unless a skill explicitly requires its native CLI runtime. `gjc deep-interview` and `gjc ralplan` are compatibility bridges for private runtime deployments only; `gjc ultragoal` and `gjc team` are native runtime commands.
39
40
 
40
41
  <role-agent-surface>
41
42
  GJC also bundles four source-defined role agents for the task/sub-agent tool. These are not workflow skills and are not repo-visible `.gjc` defaults. They are implementation and review lanes loaded from source prompts.
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";
@@ -137,6 +138,7 @@ import type { HookCommandContext } from "../extensibility/hooks/types";
137
138
  import type { Skill, SkillWarning } from "../extensibility/skills";
138
139
  import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
139
140
  import { buildGjcRuntimeSessionEnv, consumePendingGoalModeRequest } from "../gjc-runtime/goal-mode-request";
141
+ import { requestGjcWorkerIntegrationAttempt } from "../gjc-runtime/team-runtime";
140
142
  import { GoalRuntime } from "../goals/runtime";
141
143
  import type { Goal, GoalModeState } from "../goals/state";
142
144
  import type { HindsightSessionState } from "../hindsight/state";
@@ -258,7 +260,7 @@ export interface AgentSessionConfig {
258
260
  sessionManager: SessionManager;
259
261
  settings: Settings;
260
262
  /** Models to cycle through with Ctrl+P (from --models flag) */
261
- scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
263
+ scopedModels?: ScopedModelSelection[];
262
264
  /** Initial session thinking selector. */
263
265
  thinkingLevel?: ThinkingLevel;
264
266
  /** Prompt templates for expansion */
@@ -748,7 +750,7 @@ export class AgentSession {
748
750
 
749
751
  readonly configWarnings: string[] = [];
750
752
 
751
- #scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
753
+ #scopedModels: ScopedModelSelection[];
752
754
  #thinkingLevel: ThinkingLevel | undefined;
753
755
  #promptTemplates: PromptTemplate[];
754
756
  #slashCommands: FileSlashCommand[];
@@ -2577,6 +2579,11 @@ export class AgentSession {
2577
2579
 
2578
2580
  /** Emit extension events based on session events */
2579
2581
  async #emitExtensionEvent(event: AgentSessionEvent): Promise<void> {
2582
+ if (event.type === "turn_end") {
2583
+ await requestGjcWorkerIntegrationAttempt(this.sessionManager.getCwd(), process.env).catch(error => {
2584
+ logger.warn("GJC team worker integration request failed", { error: String(error) });
2585
+ });
2586
+ }
2580
2587
  if (!this.#extensionRunner) return;
2581
2588
  if (event.type === "agent_start") {
2582
2589
  this.#turnIndex = 0;
@@ -3749,7 +3756,7 @@ export class AgentSession {
3749
3756
  }
3750
3757
 
3751
3758
  /** Scoped models for cycling (from --models flag) */
3752
- get scopedModels(): ReadonlyArray<{ model: Model; thinkingLevel?: ThinkingLevel }> {
3759
+ get scopedModels(): ReadonlyArray<ScopedModelSelection> {
3753
3760
  return this.#scopedModels;
3754
3761
  }
3755
3762
 
@@ -6445,6 +6452,7 @@ export class AgentSession {
6445
6452
  metadata: this.agent.metadataForProvider(candidate.provider),
6446
6453
  convertToLlm,
6447
6454
  telemetry,
6455
+ authCredentialType: this.#modelRegistry.getSessionCredentialType(candidate.provider, this.sessionId),
6448
6456
  });
6449
6457
  } catch (error) {
6450
6458
  if (!this.#isCompactionAuthFailure(error)) {
@@ -6700,6 +6708,10 @@ export class AgentSession {
6700
6708
  initiatorOverride: "agent",
6701
6709
  convertToLlm,
6702
6710
  telemetry,
6711
+ authCredentialType: this.#modelRegistry.getSessionCredentialType(
6712
+ candidate.provider,
6713
+ this.sessionId,
6714
+ ),
6703
6715
  });
6704
6716
  break;
6705
6717
  } catch (error) {