@sayknow-cli/coding-agent 0.6.4 → 0.6.6
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 +35 -1
- package/README.md +2 -2
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/settings-schema.d.ts +7 -3
- package/dist/types/i18n/messages/en.d.ts +5 -14
- 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 +34 -14
- 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 +14 -2
- 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 +3 -1
- package/dist/types/session/session-manager.d.ts +9 -0
- package/dist/types/session/session-stars.d.ts +15 -0
- package/dist/types/tools/debug.d.ts +2 -2
- 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 +8 -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 +3 -12
- package/src/i18n/messages/en.ts +5 -14
- package/src/i18n/messages/es.messages.ts +4 -1
- package/src/i18n/messages/es.settings.ts +2 -0
- package/src/i18n/messages/es.ts +3 -12
- package/src/i18n/messages/fr.messages.ts +4 -1
- package/src/i18n/messages/fr.settings.ts +2 -0
- package/src/i18n/messages/fr.ts +3 -12
- package/src/i18n/messages/ja.messages.ts +4 -1
- package/src/i18n/messages/ja.settings.ts +2 -0
- package/src/i18n/messages/ja.ts +3 -12
- package/src/i18n/messages/ko.messages.ts +4 -1
- package/src/i18n/messages/ko.settings.ts +2 -0
- package/src/i18n/messages/ko.ts +4 -13
- package/src/i18n/messages/zh.messages.ts +2 -1
- package/src/i18n/messages/zh.settings.ts +2 -0
- package/src/i18n/messages/zh.ts +3 -12
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/render.ts +0 -1
- package/src/modes/action-registry.ts +1 -0
- 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 +298 -576
- package/src/modes/controllers/command-controller.ts +56 -7
- package/src/modes/controllers/input-controller.ts +17 -0
- package/src/modes/controllers/selector-controller.ts +91 -10
- package/src/modes/interactive-mode.ts +85 -38
- 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 +3 -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";
|
|
@@ -111,6 +112,9 @@ export class InputController {
|
|
|
111
112
|
"app.session.tree": () => this.ctx.showTreeSelector(),
|
|
112
113
|
"app.session.fork": () => this.ctx.showUserMessageSelector(),
|
|
113
114
|
"app.session.resume": () => this.ctx.showSessionSelector(),
|
|
115
|
+
"app.session.continue": async () => {
|
|
116
|
+
await this.ctx.continueRecentSession();
|
|
117
|
+
},
|
|
114
118
|
"app.session.observe": async () => {
|
|
115
119
|
await this.ctx.showSessionObserver();
|
|
116
120
|
},
|
|
@@ -709,6 +713,16 @@ export class InputController {
|
|
|
709
713
|
return true;
|
|
710
714
|
});
|
|
711
715
|
}
|
|
716
|
+
this.#registerCommandPaletteAction("app.session.continue", () => {
|
|
717
|
+
void this.ctx.continueRecentSession();
|
|
718
|
+
return undefined;
|
|
719
|
+
});
|
|
720
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.continue")) {
|
|
721
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
722
|
+
this.#executeAction("app.session.continue");
|
|
723
|
+
return true;
|
|
724
|
+
});
|
|
725
|
+
}
|
|
712
726
|
this.#registerCommandPaletteAction(
|
|
713
727
|
"app.message.followUp",
|
|
714
728
|
() => this.#executeAction("app.message.followUp"),
|
|
@@ -971,6 +985,7 @@ export class InputController {
|
|
|
971
985
|
|
|
972
986
|
// Queue input during compaction
|
|
973
987
|
if (this.ctx.session.isCompacting) {
|
|
988
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
974
989
|
if ((inputImages?.length ?? 0) > 0) {
|
|
975
990
|
this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
|
|
976
991
|
return;
|
|
@@ -984,6 +999,7 @@ export class InputController {
|
|
|
984
999
|
// prompt to run after the active turn completes (in submission order).
|
|
985
1000
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
986
1001
|
if (this.ctx.session.isStreaming) {
|
|
1002
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
987
1003
|
if (this.#canModifyComposer(composer)) {
|
|
988
1004
|
this.ctx.editor.addToHistory(text);
|
|
989
1005
|
this.ctx.editor.setText("");
|
|
@@ -1008,6 +1024,7 @@ export class InputController {
|
|
|
1008
1024
|
}
|
|
1009
1025
|
|
|
1010
1026
|
// Normal message submission
|
|
1027
|
+
invalidateSessionTitleGeneration(this.ctx.sessionManager);
|
|
1011
1028
|
// First, move any pending bash components to chat
|
|
1012
1029
|
this.ctx.flushPendingBashComponents();
|
|
1013
1030
|
|
|
@@ -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,5 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import { type Agent, type AgentMessage, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
2
4
|
import type { CompactionOutcome } from "@sayknow-cli/agent-core/compaction";
|
|
3
5
|
import type { AssistantMessage, ImageContent, Message, UsageReport } from "@sayknow-cli/ai";
|
|
@@ -47,9 +49,11 @@ 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";
|
|
49
51
|
import type { LspStartupServerInfo } from "../tools";
|
|
52
|
+
import { shortenPath } from "../tools/render-utils";
|
|
50
53
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
51
54
|
import { copyToClipboard } from "../utils/clipboard";
|
|
52
55
|
import type { EventBus } from "../utils/event-bus";
|
|
56
|
+
import * as gitUtils from "../utils/git";
|
|
53
57
|
import { getSessionAccentAnsi, getSessionAccentHex } from "../utils/session-color";
|
|
54
58
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
55
59
|
import type { AssistantMessageComponent } from "./components/assistant-message";
|
|
@@ -67,16 +71,12 @@ import {
|
|
|
67
71
|
isPetCapabilityProbePending,
|
|
68
72
|
warnWhenPetCapabilitySettled,
|
|
69
73
|
} from "./components/pet-capability";
|
|
70
|
-
import { type PetMode, SayknowPetWidget } from "./components/sayknow-pet-widget";
|
|
74
|
+
import { type PetMode, type PetSkinId, SayknowPetWidget } from "./components/sayknow-pet-widget";
|
|
75
|
+
import { resolveCurrentBranch } from "./components/status-line/git-utils";
|
|
71
76
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
72
77
|
import { StatusLineComponent } from "./components/tool-status-header";
|
|
73
78
|
import { composeToolText } from "./components/tool-transcript-format";
|
|
74
|
-
import {
|
|
75
|
-
type RecentSession,
|
|
76
|
-
WelcomeComponent,
|
|
77
|
-
type WelcomeLogoMode,
|
|
78
|
-
type LspServerInfo as WelcomeLspServerInfo,
|
|
79
|
-
} from "./components/welcome";
|
|
79
|
+
import { type RecentSession, WelcomeComponent, type WelcomeLogoMode, type WelcomeSnapshot } from "./components/welcome";
|
|
80
80
|
import { BtwController } from "./controllers/btw-controller";
|
|
81
81
|
import { CommandController } from "./controllers/command-controller";
|
|
82
82
|
import { EventController } from "./controllers/event-controller";
|
|
@@ -99,7 +99,14 @@ import { shouldShowExtensionCommand } from "./slash-command-visibility";
|
|
|
99
99
|
import { TasksAggregator } from "./tasks-aggregator";
|
|
100
100
|
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
101
101
|
import type { Theme } from "./theme/theme";
|
|
102
|
-
import {
|
|
102
|
+
import {
|
|
103
|
+
getCurrentThemeName,
|
|
104
|
+
getEditorTheme,
|
|
105
|
+
getSymbolTheme,
|
|
106
|
+
onTerminalAppearanceChange,
|
|
107
|
+
onThemeChange,
|
|
108
|
+
theme,
|
|
109
|
+
} from "./theme/theme";
|
|
103
110
|
import { type RegisterTranscriptItem, TranscriptItemRegistry, transcriptItemId } from "./transcript-item-registry";
|
|
104
111
|
import {
|
|
105
112
|
type CompactionQueuedMessage,
|
|
@@ -196,23 +203,33 @@ const HINT_SHIMMER_PALETTE: ShimmerPalette = {
|
|
|
196
203
|
high: "borderAccent",
|
|
197
204
|
};
|
|
198
205
|
|
|
199
|
-
function getDefaultInputPrefix(): string {
|
|
200
|
-
return `${theme.fg("accent", ">")} `;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
206
|
function getShellInputPrefix(isNoContext: boolean): string {
|
|
204
207
|
const shellLabel = isNoContext
|
|
205
208
|
? theme.fg("warning", theme.bold("shell no-context"))
|
|
206
209
|
: theme.fg("bashMode", theme.bold("shell"));
|
|
207
|
-
return `${shellLabel}
|
|
210
|
+
return `${shellLabel} `;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The pet on the launch card: the one the user keeps beside the composer, or —
|
|
215
|
+
* with the pet off — the skin that matches the theme (blue for blue-octopus).
|
|
216
|
+
*/
|
|
217
|
+
export function resolveWelcomePetSkin(petMode: PetMode, themeName: string | undefined): PetSkinId {
|
|
218
|
+
if (petMode !== "off") return petMode;
|
|
219
|
+
return themeName?.startsWith("blue") ? "blue" : "red";
|
|
208
220
|
}
|
|
209
221
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
/**
|
|
223
|
+
* The composer is an open rail, not a box: every input row starts with the same
|
|
224
|
+
* rail glyph a submitted prompt keeps in the transcript, colored by the editor's
|
|
225
|
+
* border color (session accent, thinking level, shell/python mode).
|
|
226
|
+
*/
|
|
227
|
+
export function configureDefaultComposerChrome(editor: CustomEditor): void {
|
|
228
|
+
editor.setBorderVisible(false);
|
|
229
|
+
editor.setClosedBorderBox(false);
|
|
214
230
|
editor.setPromptGutter(undefined);
|
|
215
|
-
editor.
|
|
231
|
+
editor.setRailGutter(theme.rail.user);
|
|
232
|
+
editor.setInputPrefix(undefined);
|
|
216
233
|
editor.setPlaceholder(getDefaultComposerPlaceholder());
|
|
217
234
|
editor.setPaddingX(1);
|
|
218
235
|
editor.setRightGutterWidth(COMPOSER_RIGHT_GUTTER_WIDTH);
|
|
@@ -734,7 +751,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
734
751
|
modelName,
|
|
735
752
|
providerName,
|
|
736
753
|
recentSessions,
|
|
737
|
-
this.#getWelcomeLspServers(),
|
|
738
754
|
welcomeLogoMode,
|
|
739
755
|
{
|
|
740
756
|
getViewportRows: () => this.ui.terminal.rows,
|
|
@@ -744,6 +760,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
744
760
|
collapseChangelog: settings.get("collapseChangelog"),
|
|
745
761
|
keyDisplayContext: this.#keyDisplayContext,
|
|
746
762
|
skipLogoAnimation,
|
|
763
|
+
snapshot: this.#buildWelcomeSnapshot(),
|
|
764
|
+
resumeKey: this.keybindings.getKeys("app.session.resume")[0],
|
|
765
|
+
continueKey: this.keybindings.getKeys("app.session.continue")[0],
|
|
766
|
+
petSkin: resolveWelcomePetSkin(settings.get("pet.mode"), getCurrentThemeName()),
|
|
747
767
|
},
|
|
748
768
|
);
|
|
749
769
|
|
|
@@ -873,6 +893,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
873
893
|
error: error instanceof Error ? error.message : String(error),
|
|
874
894
|
});
|
|
875
895
|
});
|
|
896
|
+
this.#probeWelcomeWorkspace(welcomeComponent);
|
|
876
897
|
}, 0);
|
|
877
898
|
recentSessionsTimer.unref?.();
|
|
878
899
|
}
|
|
@@ -1206,8 +1227,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1206
1227
|
}
|
|
1207
1228
|
}
|
|
1208
1229
|
if (!this.isBashMode) {
|
|
1209
|
-
this.editor.setInputPrefix(
|
|
1230
|
+
this.editor.setInputPrefix(undefined);
|
|
1210
1231
|
}
|
|
1232
|
+
// Re-read the glyph so a symbol-preset switch (unicode/nerd/ascii) reaches the rail.
|
|
1233
|
+
this.editor.setRailGutter(theme.rail.user);
|
|
1211
1234
|
this.editor.setPlaceholder(this.#getComposerPlaceholder());
|
|
1212
1235
|
this.#setComposerTopBorder();
|
|
1213
1236
|
this.ui.requestRender();
|
|
@@ -1597,31 +1620,34 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1597
1620
|
}
|
|
1598
1621
|
|
|
1599
1622
|
#handleLspStartupEvent(event: LspStartupEvent): void {
|
|
1600
|
-
this.#updateWelcomeLspServers();
|
|
1601
|
-
|
|
1602
1623
|
const warningMessage = getLspStartupWarningMessage(event);
|
|
1603
1624
|
if (warningMessage) {
|
|
1604
1625
|
this.showWarning(warningMessage);
|
|
1605
1626
|
}
|
|
1606
1627
|
}
|
|
1607
1628
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
#updateWelcomeLspServers(): void {
|
|
1619
|
-
if (!this.#welcomeComponent) {
|
|
1620
|
-
return;
|
|
1629
|
+
/** Facts the launch card can show synchronously, before the git probe runs. */
|
|
1630
|
+
#buildWelcomeSnapshot(): WelcomeSnapshot {
|
|
1631
|
+
const projectDir = getProjectDir();
|
|
1632
|
+
let branch: string | null = null;
|
|
1633
|
+
try {
|
|
1634
|
+
branch = resolveCurrentBranch(projectDir).branch;
|
|
1635
|
+
} catch {
|
|
1636
|
+
branch = null;
|
|
1621
1637
|
}
|
|
1638
|
+
return { cwd: shortenPath(projectDir), branch, thinkingLevel: this.session.thinkingLevel };
|
|
1639
|
+
}
|
|
1622
1640
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1641
|
+
/** Fill in the git change counts, which need I/O. */
|
|
1642
|
+
#probeWelcomeWorkspace(welcomeComponent: WelcomeComponent): void {
|
|
1643
|
+
void gitUtils.status
|
|
1644
|
+
.summary(getProjectDir())
|
|
1645
|
+
.then(gitChanges => {
|
|
1646
|
+
if (this.#welcomeComponent !== welcomeComponent) return;
|
|
1647
|
+
welcomeComponent.setSnapshot({ gitChanges });
|
|
1648
|
+
this.ui.requestRender();
|
|
1649
|
+
})
|
|
1650
|
+
.catch(() => {});
|
|
1625
1651
|
}
|
|
1626
1652
|
|
|
1627
1653
|
#getWorkingMessageAccent(): WorkingMessageAccent | undefined {
|
|
@@ -1866,7 +1892,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1866
1892
|
return this.#commandController.handleMoveCommand(targetPath);
|
|
1867
1893
|
}
|
|
1868
1894
|
|
|
1869
|
-
handleRenameCommand(title
|
|
1895
|
+
handleRenameCommand(title?: string): Promise<void> {
|
|
1870
1896
|
return this.#commandController.handleRenameCommand(title);
|
|
1871
1897
|
}
|
|
1872
1898
|
|
|
@@ -2157,6 +2183,27 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2157
2183
|
this.#selectorController.showSessionSelector();
|
|
2158
2184
|
}
|
|
2159
2185
|
|
|
2186
|
+
async continueRecentSession(): Promise<void> {
|
|
2187
|
+
// Listing returns canonical paths (/private/var on macOS); compare canonically so the
|
|
2188
|
+
// live session is never "continued" into itself.
|
|
2189
|
+
const canonical = (file: string): string => {
|
|
2190
|
+
try {
|
|
2191
|
+
return fs.realpathSync(file);
|
|
2192
|
+
} catch {
|
|
2193
|
+
return path.resolve(file);
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
const currentFile = this.sessionManager.getSessionFile();
|
|
2197
|
+
const current = currentFile ? canonical(currentFile) : undefined;
|
|
2198
|
+
const recent = await getRecentSessions(this.sessionManager.getSessionDir());
|
|
2199
|
+
const target = recent.find(session => canonical(session.path) !== current);
|
|
2200
|
+
if (!target) {
|
|
2201
|
+
this.showStatus("No earlier session to continue");
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
await this.handleResumeSession(target.path, { requireIdle: true });
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2160
2207
|
handleResumeSession(sessionPath: string, options?: { requireIdle?: boolean }): Promise<boolean> {
|
|
2161
2208
|
return this.#selectorController.handleResumeSession(sessionPath, options, () => {
|
|
2162
2209
|
this.#btwController.dispose();
|
|
@@ -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
|
};
|