@sayknow-cli/coding-agent 0.6.3 → 0.6.5
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 +25 -1
- package/README.md +2 -2
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +7 -3
- package/dist/types/i18n/messages/en.d.ts +16 -0
- package/dist/types/modes/components/session-selector.d.ts +7 -3
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message.d.ts +3 -1
- package/dist/types/modes/components/welcome.d.ts +49 -6
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/theme/defaults/index.d.ts +342 -0
- package/dist/types/modes/theme/theme.d.ts +5 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +9 -0
- package/dist/types/session/session-stars.d.ts +15 -0
- package/dist/types/tui/output-block.d.ts +12 -1
- package/dist/types/tui/utils.d.ts +1 -3
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +8 -0
- package/src/cli/session-picker.ts +4 -0
- package/src/config/keybindings.ts +1 -1
- package/src/config/settings-schema.ts +4 -3
- package/src/i18n/messages/de.messages.ts +4 -1
- package/src/i18n/messages/de.settings.ts +2 -0
- package/src/i18n/messages/de.ts +16 -0
- package/src/i18n/messages/en.ts +16 -0
- package/src/i18n/messages/es.messages.ts +4 -1
- package/src/i18n/messages/es.settings.ts +2 -0
- package/src/i18n/messages/es.ts +16 -0
- package/src/i18n/messages/fr.messages.ts +4 -1
- package/src/i18n/messages/fr.settings.ts +2 -0
- package/src/i18n/messages/fr.ts +16 -0
- package/src/i18n/messages/ja.messages.ts +4 -1
- package/src/i18n/messages/ja.settings.ts +2 -0
- package/src/i18n/messages/ja.ts +16 -0
- package/src/i18n/messages/ko.messages.ts +4 -1
- package/src/i18n/messages/ko.settings.ts +2 -0
- package/src/i18n/messages/ko.ts +16 -0
- package/src/i18n/messages/zh.messages.ts +2 -1
- package/src/i18n/messages/zh.settings.ts +2 -0
- package/src/i18n/messages/zh.ts +16 -0
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/render.ts +0 -1
- package/src/modes/components/session-selector.ts +71 -21
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/presets.ts +5 -5
- package/src/modes/components/status-line/separators.ts +4 -0
- package/src/modes/components/tool-execution.ts +6 -19
- package/src/modes/components/tool-status-header.ts +103 -12
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/components/user-message.ts +25 -15
- package/src/modes/components/welcome.ts +374 -478
- package/src/modes/controllers/command-controller.ts +56 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +91 -10
- package/src/modes/interactive-mode.ts +101 -12
- package/src/modes/theme/defaults/glow-octopus.json +114 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/ink-octopus.json +114 -0
- package/src/modes/theme/defaults/violet-octopus.json +114 -0
- package/src/modes/theme/theme.ts +18 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/title-system.md +1 -1
- package/src/sdk/protocol/operation-inventory.generated.json +40 -0
- package/src/session/agent-session.ts +12 -0
- package/src/session/internal/managed-session-scope.ts +10 -0
- package/src/session/session-manager.ts +26 -1
- package/src/session/session-stars.ts +104 -0
- package/src/slash-commands/builtin-registry.ts +104 -4
- package/src/tools/debug.ts +0 -1
- package/src/tools/fetch.ts +0 -1
- package/src/tui/output-block.ts +29 -61
- package/src/tui/utils.ts +1 -8
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
|
@@ -48,7 +48,16 @@ import { replaceTabs } from "../../tools/render-utils";
|
|
|
48
48
|
import { getDisplayChangelogEntries } from "../../utils/changelog";
|
|
49
49
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
50
50
|
import { openPath } from "../../utils/open";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
beginSessionTitleGeneration,
|
|
53
|
+
invalidateSessionTitleGeneration,
|
|
54
|
+
isSessionTitleGenerationCurrent,
|
|
55
|
+
} from "../../utils/session-title-generation";
|
|
56
|
+
import {
|
|
57
|
+
buildConversationTitleInput,
|
|
58
|
+
generateSessionTitle,
|
|
59
|
+
setSessionTerminalTitle,
|
|
60
|
+
} from "../../utils/title-generator";
|
|
52
61
|
import { prepareTranscriptRebuild } from "../utils/ui-helpers";
|
|
53
62
|
|
|
54
63
|
function showMarkdownPanel(ctx: InteractiveModeContext, title: string, markdown: string): void {
|
|
@@ -1122,23 +1131,63 @@ export class CommandController {
|
|
|
1122
1131
|
}
|
|
1123
1132
|
}
|
|
1124
1133
|
|
|
1125
|
-
async handleRenameCommand(title
|
|
1134
|
+
async handleRenameCommand(title?: string): Promise<void> {
|
|
1126
1135
|
try {
|
|
1136
|
+
if (!title?.trim()) {
|
|
1137
|
+
const generation = beginSessionTitleGeneration(this.ctx.sessionManager);
|
|
1138
|
+
const input = buildConversationTitleInput(this.ctx.session.messages);
|
|
1139
|
+
if (!input) {
|
|
1140
|
+
this.ctx.showError("Nothing to summarize yet — pass a title: /rename <title>");
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const generated = await generateSessionTitle(
|
|
1145
|
+
input,
|
|
1146
|
+
this.ctx.session.modelRegistry,
|
|
1147
|
+
this.ctx.settings,
|
|
1148
|
+
this.ctx.session.sessionId,
|
|
1149
|
+
this.ctx.session.model,
|
|
1150
|
+
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
1151
|
+
);
|
|
1152
|
+
if (
|
|
1153
|
+
!isSessionTitleGenerationCurrent(this.ctx.sessionManager, generation) ||
|
|
1154
|
+
buildConversationTitleInput(this.ctx.session.messages) !== input
|
|
1155
|
+
)
|
|
1156
|
+
return;
|
|
1157
|
+
if (!generated) {
|
|
1158
|
+
this.ctx.showError("Could not generate a session title — pass one: /rename <title>");
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const stored = await this.ctx.sessionManager.setSessionName(generated, "user");
|
|
1163
|
+
if (!stored) {
|
|
1164
|
+
this.ctx.showError("Session name cannot be empty.");
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
this.#finishRename();
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
1127
1172
|
const stored = await this.ctx.sessionManager.setSessionName(title, "user");
|
|
1128
1173
|
if (!stored) {
|
|
1129
1174
|
this.ctx.showError("Session name cannot be empty.");
|
|
1130
1175
|
return;
|
|
1131
1176
|
}
|
|
1132
|
-
|
|
1133
|
-
setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
|
|
1134
|
-
this.ctx.statusLine.invalidate();
|
|
1135
|
-
this.ctx.updateEditorBorderColor();
|
|
1136
|
-
this.ctx.showStatus(`Session renamed to "${name}".`);
|
|
1177
|
+
this.#finishRename();
|
|
1137
1178
|
} catch (err) {
|
|
1138
1179
|
this.ctx.showError(`Rename failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1139
1180
|
}
|
|
1140
1181
|
}
|
|
1141
1182
|
|
|
1183
|
+
#finishRename(): void {
|
|
1184
|
+
const name = this.ctx.sessionManager.getSessionName()!;
|
|
1185
|
+
setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
|
|
1186
|
+
this.ctx.statusLine.invalidate();
|
|
1187
|
+
this.ctx.updateEditorBorderColor();
|
|
1188
|
+
this.ctx.showStatus(`Session renamed to "${name}".`);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1142
1191
|
async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
|
|
1143
1192
|
const isDeferred = this.ctx.session.isStreaming;
|
|
1144
1193
|
this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
|
|
@@ -26,6 +26,7 @@ import { ensureSupportedImageInput, ImageInputTooLargeError } from "../../utils/
|
|
|
26
26
|
import { resizeImage } from "../../utils/image-resize";
|
|
27
27
|
import { loadPastedImageBatch, PastedImageBatchError } from "../../utils/pasted-image-loading";
|
|
28
28
|
import { formatPastedImageReference, parsePastedImagePaths } from "../../utils/pasted-image-path";
|
|
29
|
+
import { invalidateSessionTitleGeneration } from "../../utils/session-title-generation";
|
|
29
30
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
30
31
|
import { ActionRegistry, APP_ACTION_METADATA } from "../action-registry";
|
|
31
32
|
import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } from "../components/command-palette";
|
|
@@ -971,6 +972,7 @@ export class InputController {
|
|
|
971
972
|
|
|
972
973
|
// Queue input during compaction
|
|
973
974
|
if (this.ctx.session.isCompacting) {
|
|
975
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
974
976
|
if ((inputImages?.length ?? 0) > 0) {
|
|
975
977
|
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
976
978
|
return;
|
|
@@ -984,6 +986,7 @@ export class InputController {
|
|
|
984
986
|
// prompt to run after the active turn completes (in submission order).
|
|
985
987
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
986
988
|
if (this.ctx.session.isStreaming) {
|
|
989
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
987
990
|
if (this.#canModifyComposer(composer)) {
|
|
988
991
|
this.ctx.editor.addToHistory(text);
|
|
989
992
|
this.ctx.editor.setText("");
|
|
@@ -1008,6 +1011,7 @@ export class InputController {
|
|
|
1008
1011
|
}
|
|
1009
1012
|
|
|
1010
1013
|
// Normal message submission
|
|
1014
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
1011
1015
|
// First, move any pending bash components to chat
|
|
1012
1016
|
this.ctx.flushPendingBashComponents();
|
|
1013
1017
|
|
|
@@ -1895,36 +1895,114 @@ export class SelectorController {
|
|
|
1895
1895
|
});
|
|
1896
1896
|
}
|
|
1897
1897
|
|
|
1898
|
+
#forkBusyReason(): string | undefined {
|
|
1899
|
+
if (this.ctx.session.isStreaming) return "Wait for the current response to finish or abort it before forking.";
|
|
1900
|
+
if (this.ctx.session.isCompacting) return "Wait for compaction to finish or abort it before forking.";
|
|
1901
|
+
if (this.ctx.bashComponent || this.ctx.pythonComponent)
|
|
1902
|
+
return "Wait for the foreground command to finish or cancel it before forking.";
|
|
1903
|
+
return undefined;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* Pick an earlier prompt and continue from it in a new session. The original
|
|
1908
|
+
* transcript is kept; the selected prompt comes back as an editable draft,
|
|
1909
|
+
* never auto-submitted.
|
|
1910
|
+
*/
|
|
1898
1911
|
showUserMessageSelector(): void {
|
|
1899
|
-
const
|
|
1912
|
+
const openedSessionId = this.ctx.sessionManager.getSessionId();
|
|
1913
|
+
const openedSessionFile = this.ctx.sessionManager.getSessionFile();
|
|
1914
|
+
if (!openedSessionFile) {
|
|
1915
|
+
this.ctx.showError("Fork requires a persisted session");
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const busyReason = this.#forkBusyReason();
|
|
1919
|
+
if (busyReason) {
|
|
1920
|
+
this.ctx.showWarning(busyReason);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1900
1923
|
|
|
1924
|
+
const userMessages = this.ctx.session.getUserMessagesForBranching();
|
|
1901
1925
|
if (userMessages.length === 0) {
|
|
1902
1926
|
this.ctx.showStatus("No messages to branch from");
|
|
1903
1927
|
return;
|
|
1904
1928
|
}
|
|
1905
1929
|
|
|
1930
|
+
// Close the side-chat composer before the picker takes the editor area.
|
|
1931
|
+
if (this.ctx.hasActiveBtw()) this.ctx.handleBtwEscape();
|
|
1932
|
+
|
|
1906
1933
|
this.showSelector(done => {
|
|
1934
|
+
let selectionInFlight = false;
|
|
1935
|
+
const closeWithoutBranch = (message: string, error = false): void => {
|
|
1936
|
+
done();
|
|
1937
|
+
if (error) this.ctx.showError(message);
|
|
1938
|
+
else this.ctx.showStatus(message);
|
|
1939
|
+
this.ctx.ui.requestRender();
|
|
1940
|
+
};
|
|
1941
|
+
const sessionChanged = (): boolean =>
|
|
1942
|
+
this.ctx.sessionManager.getSessionId() !== openedSessionId ||
|
|
1943
|
+
this.ctx.sessionManager.getSessionFile() !== openedSessionFile;
|
|
1907
1944
|
const selector = new UserMessageSelectorComponent(
|
|
1908
1945
|
userMessages.map(m => ({ id: m.entryId, text: m.text })),
|
|
1909
1946
|
async entryId => {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1947
|
+
if (selectionInFlight) return;
|
|
1948
|
+
selectionInFlight = true;
|
|
1949
|
+
if (sessionChanged()) {
|
|
1950
|
+
closeWithoutBranch("Fork cancelled because the active session changed", true);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
const busyReason = this.#forkBusyReason();
|
|
1954
|
+
if (busyReason) {
|
|
1955
|
+
closeWithoutBranch(busyReason);
|
|
1915
1956
|
return;
|
|
1916
1957
|
}
|
|
1917
|
-
this.ctx.resetIrcSidebarSession();
|
|
1918
1958
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1959
|
+
let selectedText = userMessages.find(message => message.entryId === entryId)?.text ?? "";
|
|
1960
|
+
let setupFailure: unknown;
|
|
1961
|
+
try {
|
|
1962
|
+
const result = await this.ctx.session.branch(entryId);
|
|
1963
|
+
if (result.cancelled) {
|
|
1964
|
+
closeWithoutBranch("Fork cancelled");
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
selectedText = result.selectedText;
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
// Only a failure before the successor was committed is a failed fork.
|
|
1970
|
+
if (!sessionChanged()) {
|
|
1971
|
+
closeWithoutBranch(`Fork failed: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
setupFailure = error;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
try {
|
|
1978
|
+
this.ctx.resetIrcSidebarSession();
|
|
1979
|
+
this.ctx.resetObserverRegistry();
|
|
1980
|
+
this.#refreshSessionTerminalTitle();
|
|
1981
|
+
this.ctx.statusLine.invalidate();
|
|
1982
|
+
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
1983
|
+
this.ctx.updateEditorTopBorder();
|
|
1984
|
+
this.ctx.updateEditorBorderColor();
|
|
1985
|
+
this.ctx.rebuildInitialMessages("replace-identity");
|
|
1986
|
+
await this.ctx.reloadTodos();
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
setupFailure ??= error;
|
|
1989
|
+
}
|
|
1990
|
+
this.ctx.editor.setText(selectedText);
|
|
1921
1991
|
done();
|
|
1922
|
-
|
|
1992
|
+
if (setupFailure !== undefined) {
|
|
1993
|
+
this.ctx.showError(
|
|
1994
|
+
`Fork created, but session setup failed: ${setupFailure instanceof Error ? setupFailure.message : String(setupFailure)}`,
|
|
1995
|
+
);
|
|
1996
|
+
} else {
|
|
1997
|
+
this.ctx.showStatus("Forked to a new session; edit the selected prompt to continue");
|
|
1998
|
+
}
|
|
1999
|
+
this.ctx.ui.requestRender();
|
|
1923
2000
|
},
|
|
1924
2001
|
() => {
|
|
1925
2002
|
done();
|
|
1926
2003
|
this.ctx.ui.requestRender();
|
|
1927
2004
|
},
|
|
2005
|
+
() => this.ctx.ui.terminal.rows,
|
|
1928
2006
|
);
|
|
1929
2007
|
return { component: selector, focus: selector.getMessageList() };
|
|
1930
2008
|
});
|
|
@@ -2086,6 +2164,9 @@ export class SelectorController {
|
|
|
2086
2164
|
});
|
|
2087
2165
|
}
|
|
2088
2166
|
},
|
|
2167
|
+
undefined,
|
|
2168
|
+
undefined,
|
|
2169
|
+
(session, starred) => this.ctx.sessionManager.setSessionStarredForPicker(session, starred),
|
|
2089
2170
|
);
|
|
2090
2171
|
selector.setOnRequestRender(() => this.ctx.ui.requestRender());
|
|
2091
2172
|
return { component: selector, focus: selector };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import { type Agent, type AgentMessage, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
2
3
|
import type { CompactionOutcome } from "@sayknow-cli/agent-core/compaction";
|
|
3
4
|
import type { AssistantMessage, ImageContent, Message, UsageReport } from "@sayknow-cli/ai";
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
KeybindingsManager,
|
|
25
26
|
type KeyDisplayContext,
|
|
26
27
|
} from "../config/keybindings";
|
|
28
|
+
import { getModelProfilePresentation } from "../config/model-profiles";
|
|
27
29
|
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
28
30
|
import { DEFAULT_SKC_DEFINITION_NAMES } from "../defaults/skc-defaults";
|
|
29
31
|
import type {
|
|
@@ -46,10 +48,13 @@ import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
|
46
48
|
import { HistoryStorage } from "../session/history-storage";
|
|
47
49
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
48
50
|
import { getRecentSessions, getSessionMessageEntryId } from "../session/session-manager";
|
|
51
|
+
import { loadProjectContextFiles } from "../system-prompt";
|
|
49
52
|
import type { LspStartupServerInfo } from "../tools";
|
|
53
|
+
import { shortenPath } from "../tools/render-utils";
|
|
50
54
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
51
55
|
import { copyToClipboard } from "../utils/clipboard";
|
|
52
56
|
import type { EventBus } from "../utils/event-bus";
|
|
57
|
+
import * as gitUtils from "../utils/git";
|
|
53
58
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
54
59
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
55
60
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
@@ -68,6 +73,7 @@ import {
|
|
|
68
73
|
warnWhenPetCapabilitySettled,
|
|
69
74
|
} from "./components/pet-capability";
|
|
70
75
|
import { type PetMode, SayknowPetWidget } from "./components/sayknow-pet-widget";
|
|
76
|
+
import { resolveCurrentBranch } from "./components/status-line/git-utils";
|
|
71
77
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
72
78
|
import { StatusLineComponent } from "./components/tool-status-header";
|
|
73
79
|
import { composeToolText } from "./components/tool-transcript-format";
|
|
@@ -76,6 +82,8 @@ import {
|
|
|
76
82
|
WelcomeComponent,
|
|
77
83
|
type WelcomeLogoMode,
|
|
78
84
|
type LspServerInfo as WelcomeLspServerInfo,
|
|
85
|
+
type WelcomeRoleBinding,
|
|
86
|
+
type WelcomeSnapshot,
|
|
79
87
|
} from "./components/welcome";
|
|
80
88
|
import { BtwController } from "./controllers/btw-controller";
|
|
81
89
|
import { CommandController } from "./controllers/command-controller";
|
|
@@ -196,23 +204,24 @@ const HINT_SHIMMER_PALETTE: ShimmerPalette = {
|
|
|
196
204
|
high: "borderAccent",
|
|
197
205
|
};
|
|
198
206
|
|
|
199
|
-
function getDefaultInputPrefix(): string {
|
|
200
|
-
return `${theme.fg("accent", ">")} `;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
207
|
function getShellInputPrefix(isNoContext: boolean): string {
|
|
204
208
|
const shellLabel = isNoContext
|
|
205
209
|
? theme.fg("warning", theme.bold("shell no-context"))
|
|
206
210
|
: theme.fg("bashMode", theme.bold("shell"));
|
|
207
|
-
return `${shellLabel}
|
|
211
|
+
return `${shellLabel} `;
|
|
208
212
|
}
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
/**
|
|
215
|
+
* The composer is an open rail, not a box: every input row starts with the same
|
|
216
|
+
* rail glyph a submitted prompt keeps in the transcript, colored by the editor's
|
|
217
|
+
* border color (session accent, thinking level, shell/python mode).
|
|
218
|
+
*/
|
|
219
|
+
export function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
220
|
+
editor.setBorderVisible(false);
|
|
221
|
+
editor.setClosedBorderBox(false);
|
|
214
222
|
editor.setPromptGutter(undefined);
|
|
215
|
-
editor.
|
|
223
|
+
editor.setRailGutter(theme.rail.user);
|
|
224
|
+
editor.setInputPrefix(undefined);
|
|
216
225
|
editor.setPlaceholder(getDefaultComposerPlaceholder());
|
|
217
226
|
editor.setPaddingX(1);
|
|
218
227
|
editor.setRightGutterWidth(COMPOSER_RIGHT_GUTTER_WIDTH);
|
|
@@ -744,6 +753,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
744
753
|
collapseChangelog: settings.get("collapseChangelog"),
|
|
745
754
|
keyDisplayContext: this.#keyDisplayContext,
|
|
746
755
|
skipLogoAnimation,
|
|
756
|
+
snapshot: this.#buildWelcomeSnapshot(),
|
|
757
|
+
resumeKey: this.keybindings.getKeys("app.session.resume")[0],
|
|
747
758
|
},
|
|
748
759
|
);
|
|
749
760
|
|
|
@@ -873,6 +884,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
873
884
|
error: error instanceof Error ? error.message : String(error),
|
|
874
885
|
});
|
|
875
886
|
});
|
|
887
|
+
this.#probeWelcomeWorkspace(welcomeComponent);
|
|
876
888
|
}, 0);
|
|
877
889
|
recentSessionsTimer.unref?.();
|
|
878
890
|
}
|
|
@@ -1206,8 +1218,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1206
1218
|
}
|
|
1207
1219
|
}
|
|
1208
1220
|
if (!this.isBashMode) {
|
|
1209
|
-
this.editor.setInputPrefix(
|
|
1221
|
+
this.editor.setInputPrefix(undefined);
|
|
1210
1222
|
}
|
|
1223
|
+
// Re-read the glyph so a symbol-preset switch (unicode/nerd/ascii) reaches the rail.
|
|
1224
|
+
this.editor.setRailGutter(theme.rail.user);
|
|
1211
1225
|
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
1212
1226
|
this.#setComposerTopBorder();
|
|
1213
1227
|
this.ui.requestRender();
|
|
@@ -1621,9 +1635,84 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1621
1635
|
}
|
|
1622
1636
|
|
|
1623
1637
|
this.#welcomeComponent.setLspServers(this.#getWelcomeLspServers());
|
|
1638
|
+
// LSP startup settles around the same time the MCP connections do.
|
|
1639
|
+
this.#welcomeComponent.setSnapshot({ mcp: this.#welcomeMcpSummary() });
|
|
1624
1640
|
this.ui.requestRender();
|
|
1625
1641
|
}
|
|
1626
1642
|
|
|
1643
|
+
/** Facts the launch ledger can show synchronously, before any probe runs. */
|
|
1644
|
+
#buildWelcomeSnapshot(): WelcomeSnapshot {
|
|
1645
|
+
const projectDir = getProjectDir();
|
|
1646
|
+
let branch: string | null = null;
|
|
1647
|
+
try {
|
|
1648
|
+
branch = resolveCurrentBranch(projectDir).branch;
|
|
1649
|
+
} catch {
|
|
1650
|
+
branch = null;
|
|
1651
|
+
}
|
|
1652
|
+
const profileName = this.session.getActiveModelProfile() ?? settings.get("modelProfile.default");
|
|
1653
|
+
return {
|
|
1654
|
+
cwd: shortenPath(projectDir),
|
|
1655
|
+
branch,
|
|
1656
|
+
thinkingLevel: this.session.thinkingLevel,
|
|
1657
|
+
profile: profileName ? getModelProfilePresentation(profileName).displayName : undefined,
|
|
1658
|
+
roles: this.#welcomeRoleBindings(),
|
|
1659
|
+
mcp: this.#welcomeMcpSummary(),
|
|
1660
|
+
skills: this.session.skills.length,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
/** Role agents in their canonical order, with the provider prefix dropped for width. */
|
|
1665
|
+
#welcomeRoleBindings(): WelcomeRoleBinding[] {
|
|
1666
|
+
const overrides = settings.get("task.agentModelOverrides") as Record<string, unknown> | undefined;
|
|
1667
|
+
if (!overrides) return [];
|
|
1668
|
+
const bindings: WelcomeRoleBinding[] = [];
|
|
1669
|
+
for (const role of ["executor", "planner", "critic", "architect"]) {
|
|
1670
|
+
const selector = overrides[role];
|
|
1671
|
+
if (typeof selector !== "string" || selector.length === 0) continue;
|
|
1672
|
+
const slash = selector.indexOf("/");
|
|
1673
|
+
bindings.push({ role, model: slash >= 0 ? selector.slice(slash + 1) : selector });
|
|
1674
|
+
}
|
|
1675
|
+
return bindings;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
#welcomeMcpSummary(): { connected: number; total: number } {
|
|
1679
|
+
const manager = this.mcpManager;
|
|
1680
|
+
if (!manager) return { connected: 0, total: 0 };
|
|
1681
|
+
return { connected: manager.getConnectedServers().length, total: manager.getAllServerNames().length };
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/** Fill in the ledger facts that need I/O: git change counts and project instruction files. */
|
|
1685
|
+
#probeWelcomeWorkspace(welcomeComponent: WelcomeComponent): void {
|
|
1686
|
+
const projectDir = getProjectDir();
|
|
1687
|
+
const apply = (patch: WelcomeSnapshot): void => {
|
|
1688
|
+
if (this.#welcomeComponent !== welcomeComponent) return;
|
|
1689
|
+
welcomeComponent.setSnapshot(patch);
|
|
1690
|
+
this.ui.requestRender();
|
|
1691
|
+
};
|
|
1692
|
+
void gitUtils.status
|
|
1693
|
+
.summary(projectDir)
|
|
1694
|
+
.then(gitChanges => apply({ gitChanges }))
|
|
1695
|
+
.catch(() => apply({ gitChanges: null }));
|
|
1696
|
+
void gitUtils.log
|
|
1697
|
+
.onelines(projectDir, 3)
|
|
1698
|
+
.then(recentCommits => apply({ recentCommits }))
|
|
1699
|
+
.catch(() => {});
|
|
1700
|
+
void loadProjectContextFiles({ cwd: projectDir })
|
|
1701
|
+
.then(files =>
|
|
1702
|
+
apply({
|
|
1703
|
+
contextFiles: files.map(file => {
|
|
1704
|
+
const relative = path.relative(projectDir, file.path);
|
|
1705
|
+
return relative.startsWith("..") ? shortenPath(file.path) : relative;
|
|
1706
|
+
}),
|
|
1707
|
+
}),
|
|
1708
|
+
)
|
|
1709
|
+
.catch(error => {
|
|
1710
|
+
logger.debug("Failed to load context files for welcome screen", {
|
|
1711
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1712
|
+
});
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1627
1716
|
#getWorkingMessageAccent(): WorkingMessageAccent | undefined {
|
|
1628
1717
|
const accentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
|
|
1629
1718
|
const sessionName = accentEnabled ? this.sessionManager.getSessionName() : undefined;
|
|
@@ -1866,7 +1955,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1866
1955
|
return this.#commandController.handleMoveCommand(targetPath);
|
|
1867
1956
|
}
|
|
1868
1957
|
|
|
1869
|
-
handleRenameCommand(title
|
|
1958
|
+
handleRenameCommand(title?: string): Promise<void> {
|
|
1870
1959
|
return this.#commandController.handleRenameCommand(title);
|
|
1871
1960
|
}
|
|
1872
1961
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/jaybeyond/Sayknow_CLI/main/packages/coding-agent/theme-schema.json",
|
|
3
|
+
"name": "glow-octopus",
|
|
4
|
+
"vars": {
|
|
5
|
+
"ink": "#031210",
|
|
6
|
+
"mantle": "#061c19",
|
|
7
|
+
"surface": "#0a2723",
|
|
8
|
+
"surfaceBright": "#10362f",
|
|
9
|
+
"text": "#dff7ef",
|
|
10
|
+
"muted": "#9cc2b6",
|
|
11
|
+
"dim": "#4f7a70",
|
|
12
|
+
"accent": "#3df5b0",
|
|
13
|
+
"secondary": "#a98bff",
|
|
14
|
+
"success": "#5ee3a0",
|
|
15
|
+
"error": "#ff6b8b",
|
|
16
|
+
"warning": "#ffd166",
|
|
17
|
+
"link": "#8fe7ff",
|
|
18
|
+
"keyword": "#a98bff",
|
|
19
|
+
"function": "#ffd166",
|
|
20
|
+
"string": "#6ef0c0",
|
|
21
|
+
"number": "#8fe7ff",
|
|
22
|
+
"type": "#c9b3ff",
|
|
23
|
+
"diffRemoved": "#ff6b8b",
|
|
24
|
+
"toolSuccessBg": "#0b2a20",
|
|
25
|
+
"toolErrorBg": "#2c1320",
|
|
26
|
+
"think1": "#8fe7ff",
|
|
27
|
+
"think2": "#6ef0c0",
|
|
28
|
+
"think3": "#3df5b0",
|
|
29
|
+
"think4": "#a98bff"
|
|
30
|
+
},
|
|
31
|
+
"colors": {
|
|
32
|
+
"accent": "accent",
|
|
33
|
+
"border": "surfaceBright",
|
|
34
|
+
"borderAccent": "accent",
|
|
35
|
+
"borderMuted": "dim",
|
|
36
|
+
"success": "success",
|
|
37
|
+
"error": "error",
|
|
38
|
+
"warning": "warning",
|
|
39
|
+
"muted": "muted",
|
|
40
|
+
"dim": "dim",
|
|
41
|
+
"text": "text",
|
|
42
|
+
"thinkingText": "muted",
|
|
43
|
+
"selectedBg": "surfaceBright",
|
|
44
|
+
"userMessageBg": "surface",
|
|
45
|
+
"userMessageText": "text",
|
|
46
|
+
"customMessageBg": "mantle",
|
|
47
|
+
"customMessageText": "text",
|
|
48
|
+
"customMessageLabel": "accent",
|
|
49
|
+
"toolPendingBg": "mantle",
|
|
50
|
+
"toolSuccessBg": "toolSuccessBg",
|
|
51
|
+
"toolErrorBg": "toolErrorBg",
|
|
52
|
+
"toolTitle": "text",
|
|
53
|
+
"toolOutput": "muted",
|
|
54
|
+
"mdHeading": "accent",
|
|
55
|
+
"mdLink": "link",
|
|
56
|
+
"mdLinkUrl": "dim",
|
|
57
|
+
"mdCode": "secondary",
|
|
58
|
+
"mdCodeBlock": "string",
|
|
59
|
+
"mdCodeBlockBorder": "surfaceBright",
|
|
60
|
+
"mdQuote": "muted",
|
|
61
|
+
"mdQuoteBorder": "surfaceBright",
|
|
62
|
+
"mdHr": "dim",
|
|
63
|
+
"mdListBullet": "accent",
|
|
64
|
+
"toolDiffAdded": "success",
|
|
65
|
+
"toolDiffRemoved": "diffRemoved",
|
|
66
|
+
"toolDiffContext": "muted",
|
|
67
|
+
"link": "link",
|
|
68
|
+
"syntaxComment": "dim",
|
|
69
|
+
"syntaxKeyword": "keyword",
|
|
70
|
+
"syntaxFunction": "function",
|
|
71
|
+
"syntaxVariable": "text",
|
|
72
|
+
"syntaxString": "string",
|
|
73
|
+
"syntaxNumber": "number",
|
|
74
|
+
"syntaxType": "type",
|
|
75
|
+
"syntaxOperator": "muted",
|
|
76
|
+
"syntaxPunctuation": "dim",
|
|
77
|
+
"thinkingOff": "dim",
|
|
78
|
+
"thinkingMinimal": "muted",
|
|
79
|
+
"thinkingLow": "think1",
|
|
80
|
+
"thinkingMedium": "think2",
|
|
81
|
+
"thinkingHigh": "think3",
|
|
82
|
+
"thinkingXhigh": "think4",
|
|
83
|
+
"bashMode": "accent",
|
|
84
|
+
"statusLineBg": "surfaceBright",
|
|
85
|
+
"statusLineSep": "dim",
|
|
86
|
+
"statusLineModel": "accent",
|
|
87
|
+
"statusLinePath": "secondary",
|
|
88
|
+
"statusLineGitClean": "success",
|
|
89
|
+
"statusLineGitDirty": "warning",
|
|
90
|
+
"statusLineContext": "string",
|
|
91
|
+
"statusLineSpend": "warning",
|
|
92
|
+
"statusLineStaged": "success",
|
|
93
|
+
"statusLineDirty": "warning",
|
|
94
|
+
"statusLineUntracked": "diffRemoved",
|
|
95
|
+
"statusLineOutput": "text",
|
|
96
|
+
"statusLineCost": "accent",
|
|
97
|
+
"statusLineSubagents": "secondary",
|
|
98
|
+
"pythonMode": "function"
|
|
99
|
+
},
|
|
100
|
+
"export": {
|
|
101
|
+
"pageBg": "#031210",
|
|
102
|
+
"cardBg": "#061c19",
|
|
103
|
+
"infoBg": "#0a2723"
|
|
104
|
+
},
|
|
105
|
+
"symbols": {
|
|
106
|
+
"preset": "unicode",
|
|
107
|
+
"overrides": {
|
|
108
|
+
"icon.pi": "🐙",
|
|
109
|
+
"icon.agents": "🦑",
|
|
110
|
+
"status.running": "◌",
|
|
111
|
+
"md.bullet": "▸"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import blue_octopus from "./blue-octopus.json" with { type: "json" };
|
|
2
2
|
import claude_code from "./claude-code.json" with { type: "json" };
|
|
3
3
|
import codex from "./codex.json" with { type: "json" };
|
|
4
|
+
import glow_octopus from "./glow-octopus.json" with { type: "json" };
|
|
4
5
|
import gruvbox_dark from "./gruvbox-dark.json" with { type: "json" };
|
|
6
|
+
import ink_octopus from "./ink-octopus.json" with { type: "json" };
|
|
5
7
|
import opencode from "./opencode.json" with { type: "json" };
|
|
6
8
|
import red_octopus from "./red-octopus.json" with { type: "json" };
|
|
9
|
+
import violet_octopus from "./violet-octopus.json" with { type: "json" };
|
|
7
10
|
|
|
8
11
|
export const defaultThemes = {
|
|
9
12
|
"blue-octopus": blue_octopus,
|
|
10
13
|
"claude-code": claude_code,
|
|
11
14
|
codex,
|
|
15
|
+
"glow-octopus": glow_octopus,
|
|
12
16
|
"gruvbox-dark": gruvbox_dark,
|
|
17
|
+
"ink-octopus": ink_octopus,
|
|
13
18
|
opencode,
|
|
14
19
|
"red-octopus": red_octopus,
|
|
20
|
+
"violet-octopus": violet_octopus,
|
|
15
21
|
};
|