@gajae-code/coding-agent 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
- package/dist/types/modes/components/pet-capability.d.ts +37 -0
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/types.d.ts +11 -1
- package/package.json +7 -7
- package/src/config/model-profiles.ts +19 -19
- package/src/extensibility/slash-commands.ts +3 -3
- package/src/gjc-runtime/tmux-owner-isolation.ts +4 -1
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/gajae-pet-widget.ts +169 -20
- package/src/modes/components/pet-capability.ts +115 -0
- package/src/modes/components/pet-selector.ts +4 -6
- package/src/modes/components/settings-selector.ts +28 -2
- package/src/modes/controllers/extension-ui-controller.ts +9 -8
- package/src/modes/controllers/input-controller.ts +43 -13
- package/src/modes/controllers/selector-controller.ts +7 -18
- package/src/modes/interactive-mode.ts +49 -4
- package/src/modes/types.ts +11 -1
- package/src/session/agent-session.ts +52 -7
- package/src/session/session-manager.ts +9 -2
- package/src/slash-commands/builtin-registry.ts +31 -29
|
@@ -79,6 +79,7 @@ import { HistorySearchComponent } from "../components/history-search";
|
|
|
79
79
|
import { JobsOverlayComponent } from "../components/jobs-overlay";
|
|
80
80
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
81
81
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
82
|
+
import { isPetAvailable } from "../components/pet-capability";
|
|
82
83
|
import { PetSelectorComponent } from "../components/pet-selector";
|
|
83
84
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
84
85
|
import {
|
|
@@ -492,6 +493,7 @@ export class SelectorController {
|
|
|
492
493
|
availableThemes,
|
|
493
494
|
availableModelProfiles: [...this.ctx.session.modelRegistry.getModelProfiles().keys()],
|
|
494
495
|
cwd: getProjectDir(),
|
|
496
|
+
petAvailable: isPetAvailable(),
|
|
495
497
|
},
|
|
496
498
|
{
|
|
497
499
|
onChange: (id, value) => this.handleSettingChange(id, value),
|
|
@@ -514,6 +516,7 @@ export class SelectorController {
|
|
|
514
516
|
onPetPreview: mode => {
|
|
515
517
|
this.ctx.previewPetMode(mode as PetMode);
|
|
516
518
|
},
|
|
519
|
+
onPetCommit: mode => this.ctx.commitPetPreviewMode(mode as PetMode),
|
|
517
520
|
onStatusLinePreview: previewSettings => {
|
|
518
521
|
// Update status line with preview settings
|
|
519
522
|
this.ctx.statusLine.updateSettings({
|
|
@@ -591,6 +594,7 @@ export class SelectorController {
|
|
|
591
594
|
showPetSelector(): void {
|
|
592
595
|
const stored = settings.get("pet.mode");
|
|
593
596
|
const initial: PetMode = isPetMode(stored) ? stored : "off";
|
|
597
|
+
const available = isPetAvailable();
|
|
594
598
|
this.showSelector(done => {
|
|
595
599
|
// Live-preview via previewMode (no editor re-mount, so the overlay stays);
|
|
596
600
|
// Enter commits + persists, Esc restores the initial skin.
|
|
@@ -607,6 +611,7 @@ export class SelectorController {
|
|
|
607
611
|
mode => {
|
|
608
612
|
this.ctx.previewPetMode(mode);
|
|
609
613
|
},
|
|
614
|
+
available,
|
|
610
615
|
);
|
|
611
616
|
return { component: selector, focus: selector.getSelectList() };
|
|
612
617
|
});
|
|
@@ -762,12 +767,6 @@ export class SelectorController {
|
|
|
762
767
|
});
|
|
763
768
|
break;
|
|
764
769
|
}
|
|
765
|
-
case "pet.mode":
|
|
766
|
-
// The settings submenu already persisted the value; apply it to the live
|
|
767
|
-
// widget via previewMode (the settings overlay is still open, so a full
|
|
768
|
-
// re-mount would tear it down — restoreComposer re-mounts on close).
|
|
769
|
-
this.ctx.previewPetMode(value as PetMode);
|
|
770
|
-
break;
|
|
771
770
|
case "symbolPreset": {
|
|
772
771
|
setSymbolPreset(value as "unicode" | "nerd" | "ascii").then(() => {
|
|
773
772
|
this.ctx.statusLine.invalidate();
|
|
@@ -985,21 +984,14 @@ export class SelectorController {
|
|
|
985
984
|
assignments,
|
|
986
985
|
});
|
|
987
986
|
if (!materializedProfile) {
|
|
988
|
-
const overrides = this.ctx.settings.get("task.agentModelOverrides");
|
|
989
|
-
const nextOverrides = { ...overrides };
|
|
990
|
-
let writesOverrides = false;
|
|
991
987
|
for (const targetRole of targetRoles) {
|
|
992
988
|
const target = GJC_MODEL_ASSIGNMENT_TARGETS[targetRole];
|
|
993
989
|
if (target.settingsPath === "modelRoles") {
|
|
994
990
|
this.ctx.settings.setModelRole(targetRole, value);
|
|
995
991
|
} else {
|
|
996
|
-
|
|
997
|
-
writesOverrides = true;
|
|
992
|
+
this.ctx.settings.setAgentModelOverride(targetRole, value);
|
|
998
993
|
}
|
|
999
994
|
}
|
|
1000
|
-
if (writesOverrides) {
|
|
1001
|
-
this.ctx.settings.set("task.agentModelOverrides", nextOverrides);
|
|
1002
|
-
}
|
|
1003
995
|
}
|
|
1004
996
|
modelSelector.refreshRoleAssignments({
|
|
1005
997
|
currentModel: this.ctx.session.model,
|
|
@@ -1069,10 +1061,7 @@ export class SelectorController {
|
|
|
1069
1061
|
if (target.settingsPath === "modelRoles") {
|
|
1070
1062
|
this.ctx.settings.setModelRole(role, value);
|
|
1071
1063
|
} else {
|
|
1072
|
-
this.ctx.settings.
|
|
1073
|
-
...this.ctx.settings.get("task.agentModelOverrides"),
|
|
1074
|
-
[role]: value,
|
|
1075
|
-
});
|
|
1064
|
+
this.ctx.settings.setAgentModelOverride(role, value);
|
|
1076
1065
|
}
|
|
1077
1066
|
}
|
|
1078
1067
|
modelSelector.refreshRoleAssignments({
|
|
@@ -87,6 +87,12 @@ import type { HookEditorComponent } from "./components/hook-editor";
|
|
|
87
87
|
import type { HookInputComponent } from "./components/hook-input";
|
|
88
88
|
import type { HookSelectorComponent } from "./components/hook-selector";
|
|
89
89
|
import { IrcSplitViewComponent } from "./components/irc-sidebar";
|
|
90
|
+
import {
|
|
91
|
+
getPetUnavailableWarning,
|
|
92
|
+
isPetAvailable,
|
|
93
|
+
isPetCapabilityProbePending,
|
|
94
|
+
warnWhenPetCapabilitySettled,
|
|
95
|
+
} from "./components/pet-capability";
|
|
90
96
|
import { StatusLineComponent } from "./components/status-line";
|
|
91
97
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
92
98
|
import {
|
|
@@ -389,6 +395,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
389
395
|
#cleanupUnsubscribe?: () => void;
|
|
390
396
|
#subprocessTeardownUnsubscribe?: () => void;
|
|
391
397
|
#petProtocolUnsubscribe?: () => void;
|
|
398
|
+
/** Cancels a startup pet-unavailable warning still awaiting probe settlement. */
|
|
399
|
+
#petUnavailableWarningDisposer?: () => void;
|
|
392
400
|
readonly #version: string;
|
|
393
401
|
readonly #changelogMarkdown: string | undefined;
|
|
394
402
|
#planModePreviousTools: string[] | undefined;
|
|
@@ -617,8 +625,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
617
625
|
this.ui.addChild(this.hookWidgetContainerBelow);
|
|
618
626
|
this.ui.setBottomPinnedComponent(this.statusLine);
|
|
619
627
|
this.ui.setFocus(this.editor);
|
|
628
|
+
this.petWidget?.dispose();
|
|
620
629
|
this.petWidget = this.#createPetWidget(this.editor);
|
|
621
|
-
|
|
630
|
+
const configuredPetMode = settings.get("pet.mode");
|
|
631
|
+
this.petWidget.setMode(configuredPetMode);
|
|
622
632
|
// The async sixel capability probe can enable graphics after the saved
|
|
623
633
|
// pet mode was applied and dropped (no protocol yet at startup).
|
|
624
634
|
// Re-apply the configured mode when capability arrives so the pet
|
|
@@ -631,6 +641,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
631
641
|
this.petWidget.setMode(saved);
|
|
632
642
|
}
|
|
633
643
|
});
|
|
644
|
+
if (configuredPetMode !== "off" && !isPetAvailable()) {
|
|
645
|
+
// The async Sixel capability probe (started by TUI.start()) may still
|
|
646
|
+
// enable graphics; warn only once the capability question is settled
|
|
647
|
+
// so a supported terminal is never told it is incompatible.
|
|
648
|
+
this.#petUnavailableWarningDisposer?.();
|
|
649
|
+
this.#petUnavailableWarningDisposer = warnWhenPetCapabilitySettled({
|
|
650
|
+
probePending: isPetCapabilityProbePending(),
|
|
651
|
+
onUnavailable: () => {
|
|
652
|
+
this.showStatus(theme.fg("warning", getPetUnavailableWarning()), { dim: false });
|
|
653
|
+
this.ui.requestRender();
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
}
|
|
634
657
|
|
|
635
658
|
this.#inputController.setupKeyHandlers();
|
|
636
659
|
this.#inputController.setupEditorSubmitHandler();
|
|
@@ -1078,10 +1101,26 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1078
1101
|
this.editor.setTopBorder(undefined);
|
|
1079
1102
|
}
|
|
1080
1103
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1104
|
+
/**
|
|
1105
|
+
* Single result-returning pet commit policy shared by every entry path
|
|
1106
|
+
* (`/pet`, the pet selector, and the Settings submenu). Capability is
|
|
1107
|
+
* rechecked immediately before mutation, and the preference persists only
|
|
1108
|
+
* after the commit is accepted.
|
|
1109
|
+
*/
|
|
1110
|
+
#commitPetMode(mode: PetMode, apply: (mode: PetMode) => void): boolean {
|
|
1111
|
+
if (mode !== "off" && !isPetAvailable()) {
|
|
1112
|
+
this.showStatus(theme.fg("warning", getPetUnavailableWarning()), { dim: false });
|
|
1113
|
+
this.ui.requestRender();
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
apply(mode);
|
|
1083
1117
|
settings.set("pet.mode", mode);
|
|
1084
1118
|
this.ui.requestRender();
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
setPetMode(mode: PetMode): boolean {
|
|
1123
|
+
return this.#commitPetMode(mode, next => this.petWidget?.setMode(next));
|
|
1085
1124
|
}
|
|
1086
1125
|
|
|
1087
1126
|
previewPetMode(mode: PetMode): void {
|
|
@@ -1089,6 +1128,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1089
1128
|
this.ui.requestRender();
|
|
1090
1129
|
}
|
|
1091
1130
|
|
|
1131
|
+
commitPetPreviewMode(mode: PetMode): boolean {
|
|
1132
|
+
return this.#commitPetMode(mode, next => this.petWidget?.commitPreviewMode(next));
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1092
1135
|
restoreComposer(): void {
|
|
1093
1136
|
if (this.petWidget) {
|
|
1094
1137
|
this.petWidget.remountComposer();
|
|
@@ -2090,6 +2133,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2090
2133
|
stop(): void {
|
|
2091
2134
|
this.#petProtocolUnsubscribe?.();
|
|
2092
2135
|
this.#petProtocolUnsubscribe = undefined;
|
|
2136
|
+
this.#petUnavailableWarningDisposer?.();
|
|
2137
|
+
this.#petUnavailableWarningDisposer = undefined;
|
|
2093
2138
|
this.petWidget?.dispose();
|
|
2094
2139
|
this.petWidget = undefined;
|
|
2095
2140
|
if (this.loadingAnimation) {
|
|
@@ -2223,7 +2268,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2223
2268
|
nextEditor.setText(previousText);
|
|
2224
2269
|
previousEditor.dispose();
|
|
2225
2270
|
|
|
2226
|
-
const petMode =
|
|
2271
|
+
const petMode = settings.get("pet.mode");
|
|
2227
2272
|
this.petWidget?.dispose();
|
|
2228
2273
|
|
|
2229
2274
|
this.editorContainer.clear();
|
package/src/modes/types.ts
CHANGED
|
@@ -176,9 +176,19 @@ export interface InteractiveModeContext {
|
|
|
176
176
|
setWorkingMessage(message?: string): void;
|
|
177
177
|
applyPendingWorkingMessage(): void;
|
|
178
178
|
ensureLoadingAnimation(): void;
|
|
179
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Commit a pet mode through the shared result-returning policy: capability
|
|
181
|
+
* is rechecked immediately before mutation and the preference persists only
|
|
182
|
+
* on acceptance. Returns whether the commit was accepted.
|
|
183
|
+
*/
|
|
184
|
+
setPetMode(mode: PetMode): boolean;
|
|
180
185
|
/** Live-preview a pet skin during a selector without persisting. */
|
|
181
186
|
previewPetMode(mode: PetMode): void;
|
|
187
|
+
/**
|
|
188
|
+
* Commit a settings-overlay pet change without re-mounting the composer.
|
|
189
|
+
* Same shared commit policy and result semantics as `setPetMode`.
|
|
190
|
+
*/
|
|
191
|
+
commitPetPreviewMode(mode: PetMode): boolean;
|
|
182
192
|
/** Re-mount the composer (pet-aware) after an overlay/selector closes. */
|
|
183
193
|
restoreComposer(): void;
|
|
184
194
|
startPendingSubmission(input: {
|
|
@@ -7024,12 +7024,16 @@ export class AgentSession {
|
|
|
7024
7024
|
ThinkingLevel.Off;
|
|
7025
7025
|
await this.waitForIdle();
|
|
7026
7026
|
const previousDefaultModelRole = this.settings.getGlobal("modelRoles")?.default;
|
|
7027
|
+
await this.sessionManager.flush();
|
|
7028
|
+
const previousSessionState = this.sessionManager.captureState();
|
|
7029
|
+
const previousModel = this.model;
|
|
7030
|
+
const previousThinkingLevel = this.thinkingLevel;
|
|
7031
|
+
const previousActiveRetryFallback = this.#activeRetryFallback ? { ...this.#activeRetryFallback } : undefined;
|
|
7027
7032
|
await this.settings.setGlobalModelRoleAndFlush(
|
|
7028
7033
|
"default",
|
|
7029
7034
|
formatModelSelectorValue(`${model.provider}/${model.id}`, effectiveLevel),
|
|
7030
7035
|
);
|
|
7031
7036
|
try {
|
|
7032
|
-
const previousThinkingLevel = this.thinkingLevel;
|
|
7033
7037
|
await this.setModelTemporary(model, effectiveLevel);
|
|
7034
7038
|
if (this.thinkingLevel === previousThinkingLevel) {
|
|
7035
7039
|
this.sessionManager.appendThinkingLevelChange(this.thinkingLevel);
|
|
@@ -7041,6 +7045,42 @@ export class AgentSession {
|
|
|
7041
7045
|
error: String(rollbackError),
|
|
7042
7046
|
});
|
|
7043
7047
|
});
|
|
7048
|
+
let restoredLiveState = false;
|
|
7049
|
+
try {
|
|
7050
|
+
const failedEditMode = this.#resolveActiveEditMode();
|
|
7051
|
+
const currentModel = this.model;
|
|
7052
|
+
const hasCompatibleModelTransport =
|
|
7053
|
+
currentModel !== undefined &&
|
|
7054
|
+
previousModel !== undefined &&
|
|
7055
|
+
currentModel.provider === previousModel.provider &&
|
|
7056
|
+
currentModel.id === previousModel.id &&
|
|
7057
|
+
currentModel.api === previousModel.api;
|
|
7058
|
+
if (hasCompatibleModelTransport) {
|
|
7059
|
+
this.agent.setModel(previousModel);
|
|
7060
|
+
this.#syncAppendOnlyContext(previousModel);
|
|
7061
|
+
} else {
|
|
7062
|
+
this.#setModelWithProviderSessionReset(previousModel);
|
|
7063
|
+
}
|
|
7064
|
+
this.#thinkingLevel = previousThinkingLevel;
|
|
7065
|
+
this.agent.setThinkingLevel(toReasoningEffort(previousThinkingLevel));
|
|
7066
|
+
await this.#syncEditToolModeAfterModelChange(failedEditMode);
|
|
7067
|
+
restoredLiveState = true;
|
|
7068
|
+
} catch (rollbackError) {
|
|
7069
|
+
logger.warn("Failed to restore live default model selection after live apply failure", {
|
|
7070
|
+
error: String(rollbackError),
|
|
7071
|
+
});
|
|
7072
|
+
}
|
|
7073
|
+
if (restoredLiveState) {
|
|
7074
|
+
this.#activeRetryFallback = previousActiveRetryFallback;
|
|
7075
|
+
}
|
|
7076
|
+
try {
|
|
7077
|
+
this.sessionManager.restoreState(previousSessionState);
|
|
7078
|
+
await this.sessionManager.rewriteEntries();
|
|
7079
|
+
} catch (rollbackError) {
|
|
7080
|
+
logger.warn("Failed to restore session state after live default model selection failure", {
|
|
7081
|
+
error: String(rollbackError),
|
|
7082
|
+
});
|
|
7083
|
+
}
|
|
7044
7084
|
throw error;
|
|
7045
7085
|
}
|
|
7046
7086
|
return { provider: model.provider, modelId: model.id, thinkingLevel: effectiveLevel };
|
|
@@ -8409,7 +8449,7 @@ export class AgentSession {
|
|
|
8409
8449
|
return candidate;
|
|
8410
8450
|
}
|
|
8411
8451
|
|
|
8412
|
-
#setModelWithProviderSessionReset(model: Model): void {
|
|
8452
|
+
#setModelWithProviderSessionReset(model: Model | undefined): void {
|
|
8413
8453
|
const currentModel = this.model;
|
|
8414
8454
|
if (currentModel) {
|
|
8415
8455
|
this.#closeProviderSessionsForModelSwitch(currentModel, model);
|
|
@@ -8449,15 +8489,15 @@ export class AgentSession {
|
|
|
8449
8489
|
}
|
|
8450
8490
|
}
|
|
8451
8491
|
|
|
8452
|
-
#closeProviderSessionsForModelSwitch(currentModel: Model, nextModel: Model): void {
|
|
8492
|
+
#closeProviderSessionsForModelSwitch(currentModel: Model, nextModel: Model | undefined): void {
|
|
8453
8493
|
const providerKeys = new Set<string>();
|
|
8454
|
-
if (currentModel.api === "openai-codex-responses" || nextModel
|
|
8494
|
+
if (currentModel.api === "openai-codex-responses" || nextModel?.api === "openai-codex-responses") {
|
|
8455
8495
|
providerKeys.add("openai-codex-responses");
|
|
8456
8496
|
}
|
|
8457
8497
|
if (currentModel.api === "openai-responses") {
|
|
8458
8498
|
providerKeys.add(`openai-responses:${currentModel.provider}`);
|
|
8459
8499
|
}
|
|
8460
|
-
if (nextModel
|
|
8500
|
+
if (nextModel?.api === "openai-responses") {
|
|
8461
8501
|
providerKeys.add(`openai-responses:${nextModel.provider}`);
|
|
8462
8502
|
}
|
|
8463
8503
|
|
|
@@ -10480,8 +10520,9 @@ export class AgentSession {
|
|
|
10480
10520
|
// Accept the ordered pair as one volatile batch before committing its
|
|
10481
10521
|
// roster claim, notifying either UI, or resolving the sender delivery.
|
|
10482
10522
|
args.signal?.throwIfAborted();
|
|
10483
|
-
this.#queueBackgroundExchangeInjection([incomingRecord, replyRecord]);
|
|
10523
|
+
this.#queueBackgroundExchangeInjection([incomingRecord, replyRecord], { deferFlush: true });
|
|
10484
10524
|
commitRosterClaim?.();
|
|
10525
|
+
this.#flushOrSchedulePendingBackgroundExchanges();
|
|
10485
10526
|
announceIncoming();
|
|
10486
10527
|
this.#emitIrcObservation(replyRecord);
|
|
10487
10528
|
this.#forwardIrcRelayToMain({
|
|
@@ -10839,8 +10880,12 @@ export class AgentSession {
|
|
|
10839
10880
|
return messages;
|
|
10840
10881
|
}
|
|
10841
10882
|
|
|
10842
|
-
#queueBackgroundExchangeInjection(messages: CustomMessage[]): void {
|
|
10883
|
+
#queueBackgroundExchangeInjection(messages: CustomMessage[], options?: { deferFlush?: boolean }): void {
|
|
10843
10884
|
this.#pendingBackgroundExchanges.push(messages);
|
|
10885
|
+
if (!options?.deferFlush) this.#flushOrSchedulePendingBackgroundExchanges();
|
|
10886
|
+
}
|
|
10887
|
+
|
|
10888
|
+
#flushOrSchedulePendingBackgroundExchanges(): void {
|
|
10844
10889
|
if (!this.isStreaming) {
|
|
10845
10890
|
this.#flushPendingBackgroundExchanges();
|
|
10846
10891
|
return;
|
|
@@ -3335,6 +3335,11 @@ export class SessionManager {
|
|
|
3335
3335
|
|
|
3336
3336
|
restoreState(snapshot: SessionManagerStateSnapshot): void {
|
|
3337
3337
|
const restoredFileEntries = [...snapshot.materializedFileEntries];
|
|
3338
|
+
const retainsPersistWriter =
|
|
3339
|
+
this.#persistWriter?.isOpen() === true &&
|
|
3340
|
+
this.#sessionId === snapshot.sessionId &&
|
|
3341
|
+
this.#sessionFile === snapshot.sessionFile &&
|
|
3342
|
+
this.#persistWriterPath === snapshot.sessionFile;
|
|
3338
3343
|
this.#sessionId = snapshot.sessionId;
|
|
3339
3344
|
this.#sessionName = snapshot.sessionName;
|
|
3340
3345
|
this.#titleSource = snapshot.titleSource;
|
|
@@ -3342,8 +3347,10 @@ export class SessionManager {
|
|
|
3342
3347
|
this.#flushed = snapshot.flushed;
|
|
3343
3348
|
this.#needsFullRewriteOnNextPersist = snapshot.needsFullRewriteOnNextPersist;
|
|
3344
3349
|
this.#fileEntries = restoredFileEntries;
|
|
3345
|
-
|
|
3346
|
-
|
|
3350
|
+
if (!retainsPersistWriter) {
|
|
3351
|
+
this.#persistWriter = undefined;
|
|
3352
|
+
this.#persistWriterPath = undefined;
|
|
3353
|
+
}
|
|
3347
3354
|
this.#persistChain = Promise.resolve();
|
|
3348
3355
|
this.#persistError = undefined;
|
|
3349
3356
|
this.#persistErrorReported = false;
|
|
@@ -3,15 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { ThinkingLevel } from "@gajae-code/agent-core";
|
|
4
4
|
import { type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
5
5
|
import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
6
|
-
import {
|
|
7
|
-
isPetMode,
|
|
8
|
-
isUnderTerminalMultiplexer,
|
|
9
|
-
PET_MODE_IDS,
|
|
10
|
-
PET_SKIN_IDS,
|
|
11
|
-
PET_SKINS,
|
|
12
|
-
Spacer,
|
|
13
|
-
Text,
|
|
14
|
-
} from "@gajae-code/tui";
|
|
6
|
+
import { PET_SKINS, type PetMode, Spacer, Text } from "@gajae-code/tui";
|
|
15
7
|
import { setProjectDir } from "@gajae-code/utils";
|
|
16
8
|
import { jobElapsedMs } from "../async";
|
|
17
9
|
import { materializeActiveModelProfileAssignments } from "../config/model-profile-activation";
|
|
@@ -30,7 +22,6 @@ import {
|
|
|
30
22
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../discovery/helpers.js";
|
|
31
23
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
32
24
|
import { DynamicBorder } from "../modes/components/dynamic-border";
|
|
33
|
-
import { GajaePetWidget } from "../modes/components/gajae-pet-widget";
|
|
34
25
|
import { theme } from "../modes/theme/theme";
|
|
35
26
|
import type { InteractiveModeContext } from "../modes/types";
|
|
36
27
|
import {
|
|
@@ -73,6 +64,22 @@ export type { BuiltinSlashCommand, SubcommandDef } from "./types";
|
|
|
73
64
|
/** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
|
|
74
65
|
export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime;
|
|
75
66
|
|
|
67
|
+
const PET_COMMAND_OPTIONS: ReadonlyArray<{ name: string; mode: PetMode; description: string }> = [
|
|
68
|
+
{ name: "off", mode: "off", description: "Hide the pet" },
|
|
69
|
+
{ name: "RedGajae", mode: "red", description: PET_SKINS.red.description },
|
|
70
|
+
{ name: "BlueGajae", mode: "blue", description: PET_SKINS.blue.description },
|
|
71
|
+
];
|
|
72
|
+
const PET_COMMAND_HINT = `[${PET_COMMAND_OPTIONS.map(option => option.name).join("|")}]`;
|
|
73
|
+
/**
|
|
74
|
+
* Deprecated inputs kept accepted for compatibility (`/pet on|red|blue`).
|
|
75
|
+
* Display, completion, and inline hints stay canonical (`PET_COMMAND_OPTIONS`).
|
|
76
|
+
*/
|
|
77
|
+
const PET_COMMAND_DEPRECATED_INPUTS: Readonly<Record<string, PetMode>> = {
|
|
78
|
+
on: "red",
|
|
79
|
+
red: "red",
|
|
80
|
+
blue: "blue",
|
|
81
|
+
};
|
|
82
|
+
|
|
76
83
|
type GjcModelBatchAssignmentTargetId = "all-role-agents" | "all-targets";
|
|
77
84
|
type ParsedModelCommandArgs =
|
|
78
85
|
| { kind: "summary" }
|
|
@@ -570,34 +577,29 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
570
577
|
{
|
|
571
578
|
name: "pet",
|
|
572
579
|
description: "Gajae pet living beside the composer",
|
|
573
|
-
subcommands:
|
|
574
|
-
|
|
575
|
-
...PET_SKIN_IDS.map(id => ({ name: id, description: PET_SKINS[id].description })),
|
|
576
|
-
],
|
|
577
|
-
inlineHint: `[${PET_MODE_IDS.join("|")}]`,
|
|
580
|
+
subcommands: PET_COMMAND_OPTIONS.map(option => ({ name: option.name, description: option.description })),
|
|
581
|
+
inlineHint: PET_COMMAND_HINT,
|
|
578
582
|
allowArgs: true,
|
|
579
583
|
handleTui: (command, runtime) => {
|
|
580
584
|
const ctx = runtime.ctx;
|
|
581
585
|
const raw = command.args?.trim().toLowerCase() ?? "";
|
|
582
|
-
const arg =
|
|
583
|
-
|
|
586
|
+
const arg =
|
|
587
|
+
PET_COMMAND_OPTIONS.find(option => option.name.toLowerCase() === raw)?.mode ??
|
|
588
|
+
PET_COMMAND_DEPRECATED_INPUTS[raw];
|
|
589
|
+
if (!raw) {
|
|
584
590
|
ctx.showPetSelector();
|
|
585
591
|
ctx.editor.setText("");
|
|
586
592
|
return;
|
|
587
593
|
}
|
|
588
|
-
if (arg
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
} else if (isPetMode(arg)) {
|
|
596
|
-
ctx.setPetMode(arg);
|
|
597
|
-
const name = arg === "off" ? "Gajae pet hidden" : `${PET_SKINS[arg].label} is here`;
|
|
598
|
-
ctx.showStatus(name);
|
|
594
|
+
if (arg) {
|
|
595
|
+
// The shared commit policy rechecks capability, persists only on
|
|
596
|
+
// acceptance, and surfaces the actionable warning on rejection.
|
|
597
|
+
if (ctx.setPetMode(arg)) {
|
|
598
|
+
const name = arg === "off" ? "Gajae pet hidden" : `${PET_SKINS[arg].label} is here`;
|
|
599
|
+
ctx.showStatus(name);
|
|
600
|
+
}
|
|
599
601
|
} else {
|
|
600
|
-
ctx.showStatus(`Usage: /pet
|
|
602
|
+
ctx.showStatus(`Usage: /pet ${PET_COMMAND_HINT}`, { dim: true });
|
|
601
603
|
}
|
|
602
604
|
ctx.editor.setText("");
|
|
603
605
|
},
|