@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.
- package/CHANGELOG.md +30 -1
- package/dist/types/commands/gjc-runtime-bridge.d.ts +24 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +23 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +40 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +15 -10
- package/dist/types/hooks/skill-state.d.ts +4 -1
- package/dist/types/modes/components/model-selector.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -4
- package/dist/types/session/agent-session.d.ts +3 -9
- package/dist/types/skill-state/active-state.d.ts +19 -0
- package/dist/types/skill-state/workflow-hud.d.ts +62 -0
- package/package.json +9 -9
- package/src/commands/deep-interview.ts +21 -2
- package/src/commands/gjc-runtime-bridge.ts +161 -15
- package/src/commands/ralplan.ts +21 -2
- package/src/commands/team.ts +54 -3
- package/src/commands/ultragoal.ts +21 -1
- package/src/config/model-registry.ts +4 -0
- package/src/config/model-resolver.ts +5 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +6 -6
- package/src/defaults/gjc/skills/ralplan/SKILL.md +5 -9
- package/src/defaults/gjc/skills/team/SKILL.md +5 -4
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -8
- package/src/gjc-runtime/launch-tmux.ts +73 -2
- package/src/gjc-runtime/team-runtime.ts +365 -35
- package/src/gjc-runtime/ultragoal-guard.ts +43 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +307 -187
- package/src/hooks/skill-state.ts +4 -1
- package/src/main.ts +1 -0
- package/src/modes/components/model-selector.ts +108 -8
- package/src/modes/components/skill-hud/render.ts +35 -8
- package/src/modes/interactive-mode.ts +34 -22
- package/src/prompts/system/system-prompt.md +5 -4
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +15 -3
- package/src/skill-state/active-state.ts +104 -4
- package/src/skill-state/workflow-hud.ts +160 -0
- 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 {
|
|
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
|
-
|
|
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.#
|
|
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(
|
|
125
|
-
editor.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
585
|
+
this.updateEditorChrome();
|
|
581
586
|
this.ui.requestRender();
|
|
582
587
|
});
|
|
583
588
|
|
|
584
589
|
// Initial top border update
|
|
585
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
947
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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?:
|
|
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?:
|
|
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:
|
|
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<
|
|
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) {
|