topchester-ai 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs
CHANGED
|
@@ -8341,6 +8341,8 @@ const sessionMetadataSchema = z.object({
|
|
|
8341
8341
|
rootSessionId: z.string().optional(),
|
|
8342
8342
|
parentSessionId: z.string().optional(),
|
|
8343
8343
|
parentToolCallId: z.string().optional(),
|
|
8344
|
+
forkedFromSessionId: z.string().optional(),
|
|
8345
|
+
forkedFromRootSessionId: z.string().optional(),
|
|
8344
8346
|
source: z.enum(["user", "subagent"]).optional(),
|
|
8345
8347
|
agentProfileId: z.string().optional(),
|
|
8346
8348
|
title: z.string().optional(),
|
|
@@ -8553,6 +8555,45 @@ async function createChildSession(workspaceRoot, options) {
|
|
|
8553
8555
|
}));
|
|
8554
8556
|
return child;
|
|
8555
8557
|
}
|
|
8558
|
+
async function forkSession(workspaceRoot, sourceSessionIdOrLatest, options = {}) {
|
|
8559
|
+
const source = await loadSession(workspaceRoot, sourceSessionIdOrLatest);
|
|
8560
|
+
const copiedEvents = await readFile(join(source.sessionDir, "events.jsonl"), "utf8");
|
|
8561
|
+
const sessionsPath = getTopchesterSessionsPath(workspaceRoot);
|
|
8562
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8563
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
8564
|
+
const sessionId = generateSessionId();
|
|
8565
|
+
const sessionDir = join(sessionsPath, sessionId);
|
|
8566
|
+
const metadataPath = join(sessionDir, "metadata.json");
|
|
8567
|
+
const eventsPath = join(sessionDir, "events.jsonl");
|
|
8568
|
+
const metadata = {
|
|
8569
|
+
version: 1,
|
|
8570
|
+
sessionId,
|
|
8571
|
+
rootSessionId: sessionId,
|
|
8572
|
+
forkedFromSessionId: source.sessionId,
|
|
8573
|
+
forkedFromRootSessionId: source.metadata.rootSessionId,
|
|
8574
|
+
source: "user",
|
|
8575
|
+
...options.title === void 0 ? {} : { title: options.title },
|
|
8576
|
+
workspaceRoot,
|
|
8577
|
+
createdAt,
|
|
8578
|
+
updatedAt: createdAt,
|
|
8579
|
+
lastEventId: source.metadata.lastEventId
|
|
8580
|
+
};
|
|
8581
|
+
try {
|
|
8582
|
+
await mkdir(sessionDir);
|
|
8583
|
+
await writeMetadata(metadataPath, metadata);
|
|
8584
|
+
await writeFile(eventsPath, copiedEvents, { flag: "wx" });
|
|
8585
|
+
return buildHandle(sessionDir, metadata);
|
|
8586
|
+
} catch (error) {
|
|
8587
|
+
if (isFileExistsError(error)) continue;
|
|
8588
|
+
await rm(sessionDir, {
|
|
8589
|
+
recursive: true,
|
|
8590
|
+
force: true
|
|
8591
|
+
});
|
|
8592
|
+
throw error;
|
|
8593
|
+
}
|
|
8594
|
+
}
|
|
8595
|
+
throw new Error("Could not create forked session after repeated session id collisions");
|
|
8596
|
+
}
|
|
8556
8597
|
async function loadSessionForAppend(workspaceRoot, sessionId) {
|
|
8557
8598
|
const loaded = await loadSession(workspaceRoot, sessionId);
|
|
8558
8599
|
return buildHandle(loaded.sessionDir, loaded.metadata);
|
|
@@ -8579,6 +8620,48 @@ async function loadSession(workspaceRoot, sessionIdOrLatest) {
|
|
|
8579
8620
|
events
|
|
8580
8621
|
};
|
|
8581
8622
|
}
|
|
8623
|
+
async function listSessionSummaries(workspaceRoot, options = {}) {
|
|
8624
|
+
if (options.excludeSessionId !== void 0) validateSessionId(options.excludeSessionId);
|
|
8625
|
+
const sessionsPath = getTopchesterSessionsPath(workspaceRoot);
|
|
8626
|
+
let entries;
|
|
8627
|
+
try {
|
|
8628
|
+
entries = await readdir(sessionsPath);
|
|
8629
|
+
} catch {
|
|
8630
|
+
return [];
|
|
8631
|
+
}
|
|
8632
|
+
const summaries = [];
|
|
8633
|
+
for (const sessionId of entries.filter((entry) => SESSION_ID_PATTERN.test(entry)).sort()) {
|
|
8634
|
+
if (options.excludeSessionId === sessionId) continue;
|
|
8635
|
+
const sessionDir = join(sessionsPath, sessionId);
|
|
8636
|
+
const metadataPath = join(sessionDir, "metadata.json");
|
|
8637
|
+
const eventsPath = join(sessionDir, "events.jsonl");
|
|
8638
|
+
let metadata;
|
|
8639
|
+
let events;
|
|
8640
|
+
try {
|
|
8641
|
+
metadata = await readMetadata(metadataPath);
|
|
8642
|
+
validateMetadataConsistency(metadata, sessionId, workspaceRoot, metadataPath);
|
|
8643
|
+
if (metadata.source === "subagent" && options.includeSubagents !== true) continue;
|
|
8644
|
+
events = await readEvents(eventsPath);
|
|
8645
|
+
validateEventConsistency(metadata, events, eventsPath);
|
|
8646
|
+
} catch {
|
|
8647
|
+
continue;
|
|
8648
|
+
}
|
|
8649
|
+
const prompt = firstUserPrompt(events);
|
|
8650
|
+
summaries.push({
|
|
8651
|
+
sessionId,
|
|
8652
|
+
createdAt: metadata.createdAt,
|
|
8653
|
+
updatedAt: metadata.updatedAt,
|
|
8654
|
+
...metadata.title === void 0 ? {} : { title: metadata.title },
|
|
8655
|
+
...metadata.forkedFromSessionId === void 0 ? {} : { forkedFromSessionId: metadata.forkedFromSessionId },
|
|
8656
|
+
...prompt === void 0 ? {} : { firstUserPrompt: prompt }
|
|
8657
|
+
});
|
|
8658
|
+
}
|
|
8659
|
+
const sorted = summaries.sort((left, right) => {
|
|
8660
|
+
const byUpdatedAt = right.updatedAt.localeCompare(left.updatedAt);
|
|
8661
|
+
return byUpdatedAt === 0 ? right.sessionId.localeCompare(left.sessionId) : byUpdatedAt;
|
|
8662
|
+
});
|
|
8663
|
+
return options.limit === void 0 ? sorted : sorted.slice(0, Math.max(0, options.limit));
|
|
8664
|
+
}
|
|
8582
8665
|
async function resolveLatestSessionId(workspaceRoot) {
|
|
8583
8666
|
const sessionsPath = getTopchesterSessionsPath(workspaceRoot);
|
|
8584
8667
|
let entries;
|
|
@@ -8755,9 +8838,18 @@ function formatZodError(error) {
|
|
|
8755
8838
|
function isVisibleOnlyMessage(meta) {
|
|
8756
8839
|
return typeof meta === "object" && meta !== null && "visibleOnly" in meta && meta.visibleOnly === true;
|
|
8757
8840
|
}
|
|
8841
|
+
function firstUserPrompt(events) {
|
|
8842
|
+
for (const event of events) if (event.kind === "message" && event.role === "user" && !isVisibleOnlySlashCommandMessage(event.meta)) return event.text;
|
|
8843
|
+
}
|
|
8844
|
+
function isVisibleOnlySlashCommandMessage(meta) {
|
|
8845
|
+
return typeof meta === "object" && meta !== null && "source" in meta && meta.source === "slash_command" && "visibleOnly" in meta && meta.visibleOnly === true;
|
|
8846
|
+
}
|
|
8758
8847
|
function isFileNotFoundError(error) {
|
|
8759
8848
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
8760
8849
|
}
|
|
8850
|
+
function isFileExistsError(error) {
|
|
8851
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
8852
|
+
}
|
|
8761
8853
|
//#endregion
|
|
8762
8854
|
//#region src/tui/banner.ts
|
|
8763
8855
|
const ASCII_BANNERS = [
|
|
@@ -9045,6 +9137,14 @@ const slashCommandSuggestions = [
|
|
|
9045
9137
|
{
|
|
9046
9138
|
value: "/new",
|
|
9047
9139
|
description: "start a fresh session"
|
|
9140
|
+
},
|
|
9141
|
+
{
|
|
9142
|
+
value: "/fork",
|
|
9143
|
+
description: "fork the current session"
|
|
9144
|
+
},
|
|
9145
|
+
{
|
|
9146
|
+
value: "/restore",
|
|
9147
|
+
description: "restore a previous session"
|
|
9048
9148
|
}
|
|
9049
9149
|
];
|
|
9050
9150
|
const slashCommands = [
|
|
@@ -9092,6 +9192,16 @@ const slashCommands = [
|
|
|
9092
9192
|
name: "new",
|
|
9093
9193
|
description: "start a fresh interactive TUI session",
|
|
9094
9194
|
execute: executeNewCommand
|
|
9195
|
+
},
|
|
9196
|
+
{
|
|
9197
|
+
name: "fork",
|
|
9198
|
+
description: "fork the current interactive TUI session",
|
|
9199
|
+
execute: executeForkCommand
|
|
9200
|
+
},
|
|
9201
|
+
{
|
|
9202
|
+
name: "restore",
|
|
9203
|
+
description: "restore a previous interactive TUI session",
|
|
9204
|
+
execute: executeRestoreCommand
|
|
9095
9205
|
}
|
|
9096
9206
|
];
|
|
9097
9207
|
function parseSlashCommand(input) {
|
|
@@ -9112,7 +9222,7 @@ async function executeSlashCommand(input, context) {
|
|
|
9112
9222
|
if (!command) {
|
|
9113
9223
|
const shortcutResult = await executeSkillShortcutCommand(parsed.name, parsed.args, context);
|
|
9114
9224
|
if (shortcutResult) return shortcutResult;
|
|
9115
|
-
return { messages: [`Unknown command: /${parsed.name}`, "Try /kb status or /
|
|
9225
|
+
return { messages: [`Unknown command: /${parsed.name}`, "Try /kb status, /new, /fork, or /restore."] };
|
|
9116
9226
|
}
|
|
9117
9227
|
return command.execute(parsed.args, context);
|
|
9118
9228
|
}
|
|
@@ -9209,6 +9319,12 @@ async function executeKbCommand(args, context) {
|
|
|
9209
9319
|
function executeNewCommand() {
|
|
9210
9320
|
return { messages: ["/new starts a fresh session in the interactive TUI."] };
|
|
9211
9321
|
}
|
|
9322
|
+
function executeForkCommand() {
|
|
9323
|
+
return { messages: ["/fork clones the current session in the interactive TUI."] };
|
|
9324
|
+
}
|
|
9325
|
+
function executeRestoreCommand() {
|
|
9326
|
+
return { messages: ["/restore opens a previous-session picker in the interactive TUI."] };
|
|
9327
|
+
}
|
|
9212
9328
|
function executeInteractiveOnlyCommand(command) {
|
|
9213
9329
|
return () => ({ messages: [`${command} is available in the interactive TUI.`] });
|
|
9214
9330
|
}
|
|
@@ -9575,6 +9691,9 @@ var ChatLayout = class {
|
|
|
9575
9691
|
submitMessage;
|
|
9576
9692
|
submitCommand;
|
|
9577
9693
|
modalActionHandler;
|
|
9694
|
+
sessionPicker;
|
|
9695
|
+
sessionPickerSelectionHandler;
|
|
9696
|
+
sessionPickerCancelHandler;
|
|
9578
9697
|
activeModalActionIndex = 0;
|
|
9579
9698
|
activeSlashSuggestionIndex = 0;
|
|
9580
9699
|
threadScrollOffset = 0;
|
|
@@ -9666,12 +9785,34 @@ var ChatLayout = class {
|
|
|
9666
9785
|
setModalActionHandler(handler) {
|
|
9667
9786
|
this.modalActionHandler = handler;
|
|
9668
9787
|
}
|
|
9788
|
+
setSessionPickerHandlers(handlers) {
|
|
9789
|
+
this.sessionPickerSelectionHandler = handlers.select;
|
|
9790
|
+
this.sessionPickerCancelHandler = handlers.cancel;
|
|
9791
|
+
}
|
|
9792
|
+
openSessionPicker(items) {
|
|
9793
|
+
this.sessionPicker = {
|
|
9794
|
+
items,
|
|
9795
|
+
selectedIndex: 0,
|
|
9796
|
+
scrollOffset: 0
|
|
9797
|
+
};
|
|
9798
|
+
this.threadScrollOffset = 0;
|
|
9799
|
+
}
|
|
9800
|
+
closeSessionPicker() {
|
|
9801
|
+
this.sessionPicker = void 0;
|
|
9802
|
+
}
|
|
9803
|
+
isSessionPickerOpen() {
|
|
9804
|
+
return this.sessionPicker !== void 0;
|
|
9805
|
+
}
|
|
9669
9806
|
setInputValue(value) {
|
|
9670
9807
|
this.promptValue = value;
|
|
9671
9808
|
this.promptCursor = value.length;
|
|
9672
9809
|
this.pastedContent.clear();
|
|
9673
9810
|
this.pasteCounter = 0;
|
|
9674
9811
|
}
|
|
9812
|
+
discardLastUserMessage(text) {
|
|
9813
|
+
const last = this.messages.at(-1);
|
|
9814
|
+
if (last?.kind === "user" && last.text === text) this.messages.pop();
|
|
9815
|
+
}
|
|
9675
9816
|
resetForNewSession(messages) {
|
|
9676
9817
|
this.messages.splice(0, this.messages.length, ...messages);
|
|
9677
9818
|
this.promptValue = "";
|
|
@@ -9688,6 +9829,9 @@ var ChatLayout = class {
|
|
|
9688
9829
|
this.taskPlan = void 0;
|
|
9689
9830
|
this.cancelPending = void 0;
|
|
9690
9831
|
this.modalActionHandler = void 0;
|
|
9832
|
+
this.sessionPicker = void 0;
|
|
9833
|
+
this.sessionPickerSelectionHandler = void 0;
|
|
9834
|
+
this.sessionPickerCancelHandler = void 0;
|
|
9691
9835
|
this.activeModalActionIndex = 0;
|
|
9692
9836
|
this.activeSlashSuggestionIndex = 0;
|
|
9693
9837
|
this.threadScrollOffset = 0;
|
|
@@ -9724,6 +9868,7 @@ var ChatLayout = class {
|
|
|
9724
9868
|
this.inputFocused = value;
|
|
9725
9869
|
}
|
|
9726
9870
|
handleInput(data) {
|
|
9871
|
+
if (this.handleSessionPickerInput(data)) return;
|
|
9727
9872
|
if (this.handleModalInput(data)) return;
|
|
9728
9873
|
if (this.cancelPending && matchesKey(data, "escape")) {
|
|
9729
9874
|
this.cancelPending();
|
|
@@ -9741,7 +9886,7 @@ var ChatLayout = class {
|
|
|
9741
9886
|
invalidate() {}
|
|
9742
9887
|
render(width) {
|
|
9743
9888
|
const safeWidth = Math.max(20, width);
|
|
9744
|
-
const footerLines = this.getActiveModal() ? this.renderModalHelp(safeWidth) : this.renderPrompt(safeWidth);
|
|
9889
|
+
const footerLines = this.getActiveModal() && !this.sessionPicker ? this.renderModalHelp(safeWidth) : this.renderPrompt(safeWidth);
|
|
9745
9890
|
const threadHeight = Math.max(1, this.terminal.rows - footerLines.length);
|
|
9746
9891
|
const allThreadLines = this.renderThread(safeWidth, threadHeight);
|
|
9747
9892
|
if (this.transcriptMode === "inline") {
|
|
@@ -9761,6 +9906,7 @@ var ChatLayout = class {
|
|
|
9761
9906
|
}
|
|
9762
9907
|
}
|
|
9763
9908
|
renderThread(width, threadHeight) {
|
|
9909
|
+
if (this.sessionPicker) return this.renderSessionPicker(width, threadHeight);
|
|
9764
9910
|
const innerWidth = Math.max(1, width);
|
|
9765
9911
|
const activeModalIndex = this.getActiveModalIndex();
|
|
9766
9912
|
const lines = this.messages.flatMap((message, index) => {
|
|
@@ -9787,7 +9933,7 @@ var ChatLayout = class {
|
|
|
9787
9933
|
});
|
|
9788
9934
|
}
|
|
9789
9935
|
renderPrompt(width) {
|
|
9790
|
-
const slashSuggestions = this.getSlashSuggestions();
|
|
9936
|
+
const slashSuggestions = this.sessionPicker ? [] : this.getSlashSuggestions();
|
|
9791
9937
|
const top = `┌${"─".repeat(Math.max(0, width - 2))}┐`;
|
|
9792
9938
|
const bottom = `└${"─".repeat(Math.max(0, width - 2))}┘`;
|
|
9793
9939
|
const prefix = "> ";
|
|
@@ -9884,6 +10030,74 @@ var ChatLayout = class {
|
|
|
9884
10030
|
const status = formatStatusLine(this.folderName, this.modelLabel, this.status, this.knowledgeStatus, statusInnerWidth);
|
|
9885
10031
|
return [truncateToWidth(` ${help}`, width, "…", true), truncateToWidth(` ${status}`, width, "…", true)];
|
|
9886
10032
|
}
|
|
10033
|
+
renderSessionPicker(width, threadHeight) {
|
|
10034
|
+
const picker = this.sessionPicker;
|
|
10035
|
+
if (!picker) return [];
|
|
10036
|
+
const innerWidth = Math.max(1, width);
|
|
10037
|
+
const header = truncateToWidth(" Restore previous session", innerWidth, "…", true);
|
|
10038
|
+
const help = truncateToWidth(" Up/Down move Enter restore Esc cancel", innerWidth, "…", true);
|
|
10039
|
+
const availableRows = Math.max(1, threadHeight - 3);
|
|
10040
|
+
const items = picker.items;
|
|
10041
|
+
if (items.length === 0) return padLines([
|
|
10042
|
+
padThreadLine(ui.label(header), width),
|
|
10043
|
+
padThreadLine("", width),
|
|
10044
|
+
padThreadLine(ui.muted(" No previous sessions for this workspace."), width),
|
|
10045
|
+
padThreadLine(ui.muted(help), width)
|
|
10046
|
+
], threadHeight, width);
|
|
10047
|
+
picker.selectedIndex = Math.max(0, Math.min(picker.selectedIndex, items.length - 1));
|
|
10048
|
+
picker.scrollOffset = getVisibleSuggestionWindowStart(items.length, picker.selectedIndex, availableRows);
|
|
10049
|
+
const visibleItems = items.slice(picker.scrollOffset, picker.scrollOffset + availableRows);
|
|
10050
|
+
return padLines([
|
|
10051
|
+
padThreadLine(ui.label(header), width),
|
|
10052
|
+
padThreadLine(ui.muted(help), width),
|
|
10053
|
+
padThreadLine("", width),
|
|
10054
|
+
...visibleItems.map((item, index) => {
|
|
10055
|
+
const selected = picker.scrollOffset + index === picker.selectedIndex;
|
|
10056
|
+
const row = formatSessionPickerRow(item, selected, innerWidth);
|
|
10057
|
+
return selected ? ui.softBackground(padThreadLine(row, width)) : padThreadLine(row, width);
|
|
10058
|
+
})
|
|
10059
|
+
], threadHeight, width);
|
|
10060
|
+
}
|
|
10061
|
+
handleSessionPickerInput(data) {
|
|
10062
|
+
const picker = this.sessionPicker;
|
|
10063
|
+
if (!picker) return false;
|
|
10064
|
+
if (matchesKey(data, "escape")) {
|
|
10065
|
+
this.closeSessionPicker();
|
|
10066
|
+
this.sessionPickerCancelHandler?.();
|
|
10067
|
+
return true;
|
|
10068
|
+
}
|
|
10069
|
+
if (picker.items.length === 0) return true;
|
|
10070
|
+
if (isUpKey(data)) {
|
|
10071
|
+
picker.selectedIndex = Math.max(0, picker.selectedIndex - 1);
|
|
10072
|
+
return true;
|
|
10073
|
+
}
|
|
10074
|
+
if (isDownKey(data)) {
|
|
10075
|
+
picker.selectedIndex = Math.min(picker.items.length - 1, picker.selectedIndex + 1);
|
|
10076
|
+
return true;
|
|
10077
|
+
}
|
|
10078
|
+
if (isPageUpKey(data)) {
|
|
10079
|
+
picker.selectedIndex = Math.max(0, picker.selectedIndex - Math.max(1, Math.floor(this.terminal.rows / 2)));
|
|
10080
|
+
return true;
|
|
10081
|
+
}
|
|
10082
|
+
if (isPageDownKey(data)) {
|
|
10083
|
+
picker.selectedIndex = Math.min(picker.items.length - 1, picker.selectedIndex + Math.max(1, Math.floor(this.terminal.rows / 2)));
|
|
10084
|
+
return true;
|
|
10085
|
+
}
|
|
10086
|
+
if (isHomeKey(data)) {
|
|
10087
|
+
picker.selectedIndex = 0;
|
|
10088
|
+
return true;
|
|
10089
|
+
}
|
|
10090
|
+
if (isEndKey(data)) {
|
|
10091
|
+
picker.selectedIndex = picker.items.length - 1;
|
|
10092
|
+
return true;
|
|
10093
|
+
}
|
|
10094
|
+
if (matchesKey(data, "enter") || data === "\n" || data === "\r") {
|
|
10095
|
+
const item = picker.items[picker.selectedIndex];
|
|
10096
|
+
if (item) this.sessionPickerSelectionHandler?.(item.sessionId);
|
|
10097
|
+
return true;
|
|
10098
|
+
}
|
|
10099
|
+
return true;
|
|
10100
|
+
}
|
|
9887
10101
|
handleModalInput(data) {
|
|
9888
10102
|
const activeModal = this.getActiveModal();
|
|
9889
10103
|
if (!activeModal) return false;
|
|
@@ -10218,6 +10432,17 @@ function getVisibleSuggestionWindowStart(total, activeIndex, visibleRows) {
|
|
|
10218
10432
|
if (total <= visibleRows) return 0;
|
|
10219
10433
|
return Math.max(0, Math.min(activeIndex - visibleRows + 1, total - visibleRows));
|
|
10220
10434
|
}
|
|
10435
|
+
function formatSessionPickerRow(item, selected, width) {
|
|
10436
|
+
return truncateToWidth(`${selected ? ">" : " "} ${formatSessionPickerDate(item.updatedAt)} ${item.sessionId.slice(0, 8)} ${formatSessionPickerPrompt(item.firstUserPrompt)}`, width, "…", true);
|
|
10437
|
+
}
|
|
10438
|
+
function formatSessionPickerDate(updatedAt) {
|
|
10439
|
+
const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})/u.exec(updatedAt);
|
|
10440
|
+
return match ? `${match[1]} ${match[2]}` : updatedAt.slice(0, 16).replace("T", " ");
|
|
10441
|
+
}
|
|
10442
|
+
function formatSessionPickerPrompt(prompt) {
|
|
10443
|
+
const normalized = prompt?.replace(/\s+/gu, " ").trim();
|
|
10444
|
+
return normalized && normalized.length > 0 ? normalized : "(no user prompt)";
|
|
10445
|
+
}
|
|
10221
10446
|
function colorUserMessageBorder(line) {
|
|
10222
10447
|
return line.replace("▌", ui.modelInline("▌"));
|
|
10223
10448
|
}
|
|
@@ -12933,6 +13158,12 @@ function getSlashCommandActivities(command) {
|
|
|
12933
13158
|
function isNewSessionCommand(command) {
|
|
12934
13159
|
return command.trim() === "/new";
|
|
12935
13160
|
}
|
|
13161
|
+
function isForkSessionCommand(command) {
|
|
13162
|
+
return command.trim() === "/fork";
|
|
13163
|
+
}
|
|
13164
|
+
function isRestoreSessionCommand(command) {
|
|
13165
|
+
return command.trim() === "/restore";
|
|
13166
|
+
}
|
|
12936
13167
|
function isConnectCommand(command) {
|
|
12937
13168
|
const name = getSlashCommandName(command);
|
|
12938
13169
|
return name === "connect" || name === "provider" || name === "providers";
|
|
@@ -13298,6 +13529,14 @@ var TopchesterTuiShell = class {
|
|
|
13298
13529
|
await this.startNewSession(app, tui);
|
|
13299
13530
|
return;
|
|
13300
13531
|
}
|
|
13532
|
+
if (isForkSessionCommand(command)) {
|
|
13533
|
+
await this.forkCurrentSession(app, tui);
|
|
13534
|
+
return;
|
|
13535
|
+
}
|
|
13536
|
+
if (isRestoreSessionCommand(command)) {
|
|
13537
|
+
await this.openRestoreSessionPicker(app, tui, command);
|
|
13538
|
+
return;
|
|
13539
|
+
}
|
|
13301
13540
|
if (isConnectCommand(command)) {
|
|
13302
13541
|
await this.submitConnectCommand(app, tui, command);
|
|
13303
13542
|
return;
|
|
@@ -13709,6 +13948,108 @@ var TopchesterTuiShell = class {
|
|
|
13709
13948
|
tui.requestRender();
|
|
13710
13949
|
await this.checkAgent(app, tui);
|
|
13711
13950
|
}
|
|
13951
|
+
async forkCurrentSession(app, tui) {
|
|
13952
|
+
if (this.taskPlanNoticeTimer) {
|
|
13953
|
+
clearTimeout(this.taskPlanNoticeTimer);
|
|
13954
|
+
this.taskPlanNoticeTimer = void 0;
|
|
13955
|
+
}
|
|
13956
|
+
const sourceSession = this.session;
|
|
13957
|
+
if (!sourceSession) {
|
|
13958
|
+
app.addMessage(systemMessage("Fork failed: no active session."));
|
|
13959
|
+
tui.requestRender();
|
|
13960
|
+
return;
|
|
13961
|
+
}
|
|
13962
|
+
const fork = await forkSession(this.context.workspaceRoot, sourceSession.sessionId);
|
|
13963
|
+
const rehydrated = rehydrateSession((await loadSession(this.context.workspaceRoot, fork.sessionId)).events);
|
|
13964
|
+
const forkNoticeText = `Forked session from ${sourceSession.sessionId.slice(0, 8)}.`;
|
|
13965
|
+
const forkNotice = systemMessage(forkNoticeText);
|
|
13966
|
+
const resetMessages = [...rehydrated.messages, forkNotice];
|
|
13967
|
+
this.session = fork;
|
|
13968
|
+
this.sessionStartedAt = Date.now();
|
|
13969
|
+
this.pendingSkillActivations = [];
|
|
13970
|
+
try {
|
|
13971
|
+
await fork.append({
|
|
13972
|
+
kind: "message",
|
|
13973
|
+
role: "system",
|
|
13974
|
+
text: forkNoticeText
|
|
13975
|
+
});
|
|
13976
|
+
} catch (error) {
|
|
13977
|
+
resetMessages.push(systemMessage(`Session save failed: ${formatPlainError(error)}`));
|
|
13978
|
+
}
|
|
13979
|
+
app.resetForNewSession(resetMessages);
|
|
13980
|
+
app.setTaskPlan(rehydrated.taskPlan);
|
|
13981
|
+
if (rehydrated.status) app.setStatus(rehydrated.status);
|
|
13982
|
+
tui.requestRender();
|
|
13983
|
+
}
|
|
13984
|
+
async openRestoreSessionPicker(app, tui, command) {
|
|
13985
|
+
app.discardLastUserMessage(command);
|
|
13986
|
+
const activeSession = this.session;
|
|
13987
|
+
if (!activeSession) {
|
|
13988
|
+
app.addMessage(systemMessage("Restore failed: no active session."));
|
|
13989
|
+
tui.requestRender();
|
|
13990
|
+
return;
|
|
13991
|
+
}
|
|
13992
|
+
if (!app.isReady()) {
|
|
13993
|
+
app.addMessage(systemMessage("Restore is unavailable while another operation is running."));
|
|
13994
|
+
tui.requestRender();
|
|
13995
|
+
return;
|
|
13996
|
+
}
|
|
13997
|
+
const summaries = await listSessionSummaries(this.context.workspaceRoot, { excludeSessionId: activeSession.sessionId });
|
|
13998
|
+
app.setSessionPickerHandlers({
|
|
13999
|
+
select: (sessionId) => {
|
|
14000
|
+
this.startBackgroundTask(app, tui, "Restore", () => this.restoreSelectedSession(app, tui, sessionId));
|
|
14001
|
+
},
|
|
14002
|
+
cancel: () => {
|
|
14003
|
+
tui.requestRender();
|
|
14004
|
+
}
|
|
14005
|
+
});
|
|
14006
|
+
app.openSessionPicker(summaries.map((summary) => ({
|
|
14007
|
+
sessionId: summary.sessionId,
|
|
14008
|
+
updatedAt: summary.updatedAt,
|
|
14009
|
+
...summary.firstUserPrompt === void 0 ? {} : { firstUserPrompt: summary.firstUserPrompt }
|
|
14010
|
+
})));
|
|
14011
|
+
tui.requestRender();
|
|
14012
|
+
}
|
|
14013
|
+
async restoreSelectedSession(app, tui, sessionId) {
|
|
14014
|
+
let restoredSession;
|
|
14015
|
+
let restoredMessages;
|
|
14016
|
+
let restoredTaskPlan;
|
|
14017
|
+
let restoredStatus;
|
|
14018
|
+
try {
|
|
14019
|
+
restoredSession = await loadSessionForAppend(this.context.workspaceRoot, sessionId);
|
|
14020
|
+
const rehydrated = rehydrateSession((await loadSession(this.context.workspaceRoot, sessionId)).events);
|
|
14021
|
+
restoredTaskPlan = rehydrated.taskPlan;
|
|
14022
|
+
restoredStatus = rehydrated.status;
|
|
14023
|
+
const noticeText = `Restored session ${sessionId.slice(0, 8)}.`;
|
|
14024
|
+
restoredMessages = [...rehydrated.messages, systemMessage(noticeText)];
|
|
14025
|
+
this.session = restoredSession;
|
|
14026
|
+
this.sessionStartedAt = Date.now();
|
|
14027
|
+
this.pendingSkillActivations = [];
|
|
14028
|
+
if (this.taskPlanNoticeTimer) {
|
|
14029
|
+
clearTimeout(this.taskPlanNoticeTimer);
|
|
14030
|
+
this.taskPlanNoticeTimer = void 0;
|
|
14031
|
+
}
|
|
14032
|
+
try {
|
|
14033
|
+
await restoredSession.append({
|
|
14034
|
+
kind: "message",
|
|
14035
|
+
role: "system",
|
|
14036
|
+
text: noticeText
|
|
14037
|
+
});
|
|
14038
|
+
} catch (error) {
|
|
14039
|
+
restoredMessages.push(systemMessage(`Session save failed: ${formatPlainError(error)}`));
|
|
14040
|
+
}
|
|
14041
|
+
} catch (error) {
|
|
14042
|
+
app.closeSessionPicker();
|
|
14043
|
+
app.addMessage(systemMessage(`Restore failed: ${formatPlainError(error)}`));
|
|
14044
|
+
tui.requestRender();
|
|
14045
|
+
return;
|
|
14046
|
+
}
|
|
14047
|
+
app.closeSessionPicker();
|
|
14048
|
+
app.resetForNewSession(restoredMessages);
|
|
14049
|
+
app.setTaskPlan(restoredTaskPlan);
|
|
14050
|
+
if (restoredStatus) app.setStatus(restoredStatus);
|
|
14051
|
+
tui.requestRender();
|
|
14052
|
+
}
|
|
13712
14053
|
async appendStartupRuntimeEvents(session, messages, events) {
|
|
13713
14054
|
for (const event of events) {
|
|
13714
14055
|
messages.push(...renderRuntimeEvent(event));
|
|
@@ -14284,6 +14625,18 @@ function createTopchesterProgram() {
|
|
|
14284
14625
|
process.exitCode = 1;
|
|
14285
14626
|
}
|
|
14286
14627
|
});
|
|
14628
|
+
program.command("fork").description("fork a saved project session").argument("[session]", "session id to fork").option("--last", "fork the latest project session").action(async (sessionId, options) => {
|
|
14629
|
+
const context = createContextFromOptions(program);
|
|
14630
|
+
try {
|
|
14631
|
+
if (options.last && sessionId) throw new Error("Usage: topchester fork [--last] [session-id]");
|
|
14632
|
+
const source = options.last ? "latest" : sessionId;
|
|
14633
|
+
if (!source) throw new Error("topchester fork requires --last or a session id until a saved-session picker exists.");
|
|
14634
|
+
await openForkedSession(context, source);
|
|
14635
|
+
} catch (error) {
|
|
14636
|
+
console.error(formatStartupError(error));
|
|
14637
|
+
process.exitCode = 1;
|
|
14638
|
+
}
|
|
14639
|
+
});
|
|
14287
14640
|
program.command("search").description("search compiled L1 knowledge entries").argument("<query...>", "search query").option("--limit <count>", "maximum number of matches", parsePositiveInteger).option("--json", "write full JSON search result to stdout").action(async (queryParts, options) => {
|
|
14288
14641
|
await executeKbSearchCommand(program, queryParts, options);
|
|
14289
14642
|
});
|
|
@@ -14372,6 +14725,17 @@ function printStartupSummary(context) {
|
|
|
14372
14725
|
}
|
|
14373
14726
|
}
|
|
14374
14727
|
}
|
|
14728
|
+
async function openForkedSession(context, sourceSession) {
|
|
14729
|
+
const fork = await forkSession(context.workspaceRoot, sourceSession);
|
|
14730
|
+
const loaded = await loadSession(context.workspaceRoot, fork.sessionId);
|
|
14731
|
+
const session = await loadSessionForAppend(context.workspaceRoot, loaded.sessionId);
|
|
14732
|
+
const rehydrated = rehydrateSession(loaded.events);
|
|
14733
|
+
await new TopchesterTuiShell(context, void 0, {
|
|
14734
|
+
session,
|
|
14735
|
+
initialMessages: rehydrated.messages,
|
|
14736
|
+
initialTaskPlan: rehydrated.taskPlan
|
|
14737
|
+
}).render();
|
|
14738
|
+
}
|
|
14375
14739
|
function createContextFromOptions(program) {
|
|
14376
14740
|
return createAppContext(getContextOptionsFromProgram(program));
|
|
14377
14741
|
}
|
|
@@ -14434,4 +14798,4 @@ function formatDryRunSyncStatus(status) {
|
|
|
14434
14798
|
//#endregion
|
|
14435
14799
|
export { runTopchesterCli as t };
|
|
14436
14800
|
|
|
14437
|
-
//# sourceMappingURL=cli-
|
|
14801
|
+
//# sourceMappingURL=cli-BZmxcvol.mjs.map
|