@sayknow-cli/coding-agent 0.6.5 → 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 +10 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/i18n/messages/en.d.ts +4 -29
- package/dist/types/modes/components/welcome.d.ts +17 -40
- package/dist/types/modes/interactive-mode.d.ts +7 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +2 -2
- package/package.json +7 -7
- package/src/config/keybindings.ts +7 -0
- package/src/i18n/messages/de.ts +2 -27
- package/src/i18n/messages/en.ts +4 -29
- package/src/i18n/messages/es.ts +2 -27
- package/src/i18n/messages/fr.ts +2 -27
- package/src/i18n/messages/ja.ts +2 -27
- package/src/i18n/messages/ko.ts +3 -28
- package/src/i18n/messages/zh.ts +2 -27
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/action-registry.ts +1 -0
- package/src/modes/components/welcome.ts +222 -396
- package/src/modes/controllers/input-controller.ts +13 -0
- package/src/modes/interactive-mode.ts +52 -94
- package/src/modes/types.ts +2 -0
|
@@ -112,6 +112,9 @@ export class InputController {
|
|
|
112
112
|
"app.session.tree": () => this.ctx.showTreeSelector(),
|
|
113
113
|
"app.session.fork": () => this.ctx.showUserMessageSelector(),
|
|
114
114
|
"app.session.resume": () => this.ctx.showSessionSelector(),
|
|
115
|
+
"app.session.continue": async () => {
|
|
116
|
+
await this.ctx.continueRecentSession();
|
|
117
|
+
},
|
|
115
118
|
"app.session.observe": async () => {
|
|
116
119
|
await this.ctx.showSessionObserver();
|
|
117
120
|
},
|
|
@@ -710,6 +713,16 @@ export class InputController {
|
|
|
710
713
|
return true;
|
|
711
714
|
});
|
|
712
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
|
+
}
|
|
713
726
|
this.#registerCommandPaletteAction(
|
|
714
727
|
"app.message.followUp",
|
|
715
728
|
() => this.#executeAction("app.message.followUp"),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { type Agent, type AgentMessage, ThinkingLevel } from "@sayknow-cli/agent-core";
|
|
3
4
|
import type { CompactionOutcome } from "@sayknow-cli/agent-core/compaction";
|
|
@@ -25,7 +26,6 @@ import {
|
|
|
25
26
|
KeybindingsManager,
|
|
26
27
|
type KeyDisplayContext,
|
|
27
28
|
} from "../config/keybindings";
|
|
28
|
-
import { getModelProfilePresentation } from "../config/model-profiles";
|
|
29
29
|
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
30
30
|
import { DEFAULT_SKC_DEFINITION_NAMES } from "../defaults/skc-defaults";
|
|
31
31
|
import type {
|
|
@@ -48,7 +48,6 @@ import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
|
48
48
|
import { HistoryStorage } from "../session/history-storage";
|
|
49
49
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
50
50
|
import { getRecentSessions, getSessionMessageEntryId } from "../session/session-manager";
|
|
51
|
-
import { loadProjectContextFiles } from "../system-prompt";
|
|
52
51
|
import type { LspStartupServerInfo } from "../tools";
|
|
53
52
|
import { shortenPath } from "../tools/render-utils";
|
|
54
53
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
@@ -72,19 +71,12 @@ import {
|
|
|
72
71
|
isPetCapabilityProbePending,
|
|
73
72
|
warnWhenPetCapabilitySettled,
|
|
74
73
|
} from "./components/pet-capability";
|
|
75
|
-
import { type PetMode, SayknowPetWidget } from "./components/sayknow-pet-widget";
|
|
74
|
+
import { type PetMode, type PetSkinId, SayknowPetWidget } from "./components/sayknow-pet-widget";
|
|
76
75
|
import { resolveCurrentBranch } from "./components/status-line/git-utils";
|
|
77
76
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
78
77
|
import { StatusLineComponent } from "./components/tool-status-header";
|
|
79
78
|
import { composeToolText } from "./components/tool-transcript-format";
|
|
80
|
-
import {
|
|
81
|
-
type RecentSession,
|
|
82
|
-
WelcomeComponent,
|
|
83
|
-
type WelcomeLogoMode,
|
|
84
|
-
type LspServerInfo as WelcomeLspServerInfo,
|
|
85
|
-
type WelcomeRoleBinding,
|
|
86
|
-
type WelcomeSnapshot,
|
|
87
|
-
} from "./components/welcome";
|
|
79
|
+
import { type RecentSession, WelcomeComponent, type WelcomeLogoMode, type WelcomeSnapshot } from "./components/welcome";
|
|
88
80
|
import { BtwController } from "./controllers/btw-controller";
|
|
89
81
|
import { CommandController } from "./controllers/command-controller";
|
|
90
82
|
import { EventController } from "./controllers/event-controller";
|
|
@@ -107,7 +99,14 @@ import { shouldShowExtensionCommand } from "./slash-command-visibility";
|
|
|
107
99
|
import { TasksAggregator } from "./tasks-aggregator";
|
|
108
100
|
import { type ShimmerPalette, shimmerSegments, shimmerText } from "./theme/shimmer";
|
|
109
101
|
import type { Theme } from "./theme/theme";
|
|
110
|
-
import {
|
|
102
|
+
import {
|
|
103
|
+
getCurrentThemeName,
|
|
104
|
+
getEditorTheme,
|
|
105
|
+
getSymbolTheme,
|
|
106
|
+
onTerminalAppearanceChange,
|
|
107
|
+
onThemeChange,
|
|
108
|
+
theme,
|
|
109
|
+
} from "./theme/theme";
|
|
111
110
|
import { type RegisterTranscriptItem, TranscriptItemRegistry, transcriptItemId } from "./transcript-item-registry";
|
|
112
111
|
import {
|
|
113
112
|
type CompactionQueuedMessage,
|
|
@@ -211,6 +210,15 @@ function getShellInputPrefix(isNoContext: boolean): string {
|
|
|
211
210
|
return `${shellLabel} `;
|
|
212
211
|
}
|
|
213
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";
|
|
220
|
+
}
|
|
221
|
+
|
|
214
222
|
/**
|
|
215
223
|
* The composer is an open rail, not a box: every input row starts with the same
|
|
216
224
|
* rail glyph a submitted prompt keeps in the transcript, colored by the editor's
|
|
@@ -743,7 +751,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
743
751
|
modelName,
|
|
744
752
|
providerName,
|
|
745
753
|
recentSessions,
|
|
746
|
-
this.#getWelcomeLspServers(),
|
|
747
754
|
welcomeLogoMode,
|
|
748
755
|
{
|
|
749
756
|
getViewportRows: () => this.ui.terminal.rows,
|
|
@@ -755,6 +762,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
755
762
|
skipLogoAnimation,
|
|
756
763
|
snapshot: this.#buildWelcomeSnapshot(),
|
|
757
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()),
|
|
758
767
|
},
|
|
759
768
|
);
|
|
760
769
|
|
|
@@ -1611,36 +1620,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1611
1620
|
}
|
|
1612
1621
|
|
|
1613
1622
|
#handleLspStartupEvent(event: LspStartupEvent): void {
|
|
1614
|
-
this.#updateWelcomeLspServers();
|
|
1615
|
-
|
|
1616
1623
|
const warningMessage = getLspStartupWarningMessage(event);
|
|
1617
1624
|
if (warningMessage) {
|
|
1618
1625
|
this.showWarning(warningMessage);
|
|
1619
1626
|
}
|
|
1620
1627
|
}
|
|
1621
1628
|
|
|
1622
|
-
|
|
1623
|
-
return (
|
|
1624
|
-
this.lspServers?.map(server => ({
|
|
1625
|
-
name: server.name,
|
|
1626
|
-
status: server.status,
|
|
1627
|
-
fileTypes: server.fileTypes,
|
|
1628
|
-
})) ?? []
|
|
1629
|
-
);
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
#updateWelcomeLspServers(): void {
|
|
1633
|
-
if (!this.#welcomeComponent) {
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
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() });
|
|
1640
|
-
this.ui.requestRender();
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/** Facts the launch ledger can show synchronously, before any probe runs. */
|
|
1629
|
+
/** Facts the launch card can show synchronously, before the git probe runs. */
|
|
1644
1630
|
#buildWelcomeSnapshot(): WelcomeSnapshot {
|
|
1645
1631
|
const projectDir = getProjectDir();
|
|
1646
1632
|
let branch: string | null = null;
|
|
@@ -1649,68 +1635,19 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1649
1635
|
} catch {
|
|
1650
1636
|
branch = null;
|
|
1651
1637
|
}
|
|
1652
|
-
|
|
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 };
|
|
1638
|
+
return { cwd: shortenPath(projectDir), branch, thinkingLevel: this.session.thinkingLevel };
|
|
1682
1639
|
}
|
|
1683
1640
|
|
|
1684
|
-
/** Fill in the
|
|
1641
|
+
/** Fill in the git change counts, which need I/O. */
|
|
1685
1642
|
#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
1643
|
void gitUtils.status
|
|
1693
|
-
.summary(
|
|
1694
|
-
.then(gitChanges =>
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1644
|
+
.summary(getProjectDir())
|
|
1645
|
+
.then(gitChanges => {
|
|
1646
|
+
if (this.#welcomeComponent !== welcomeComponent) return;
|
|
1647
|
+
welcomeComponent.setSnapshot({ gitChanges });
|
|
1648
|
+
this.ui.requestRender();
|
|
1649
|
+
})
|
|
1699
1650
|
.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
1651
|
}
|
|
1715
1652
|
|
|
1716
1653
|
#getWorkingMessageAccent(): WorkingMessageAccent | undefined {
|
|
@@ -2246,6 +2183,27 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2246
2183
|
this.#selectorController.showSessionSelector();
|
|
2247
2184
|
}
|
|
2248
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
|
+
|
|
2249
2207
|
handleResumeSession(sessionPath: string, options?: { requireIdle?: boolean }): Promise<boolean> {
|
|
2250
2208
|
return this.#selectorController.handleResumeSession(sessionPath, options, () => {
|
|
2251
2209
|
this.#btwController.dispose();
|
package/src/modes/types.ts
CHANGED
|
@@ -327,6 +327,8 @@ export interface InteractiveModeContext {
|
|
|
327
327
|
showUserMessageSelector(): void;
|
|
328
328
|
showTreeSelector(): void;
|
|
329
329
|
showSessionSelector(): void;
|
|
330
|
+
/** Resume the most recent saved session other than this one (the launch card's first row). */
|
|
331
|
+
continueRecentSession(): Promise<void>;
|
|
330
332
|
showSessionsDashboard(): void;
|
|
331
333
|
handleResumeSession(sessionPath: string, options?: { requireIdle?: boolean }): Promise<boolean>;
|
|
332
334
|
handleSessionDeleteCommand(): Promise<void>;
|