@lazyingart/agintiflow 0.20.42 → 0.20.44
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/README.md +7 -0
- package/package.json +1 -1
- package/scripts/smoke-cli-chat.js +5 -0
- package/scripts/smoke-inbox.js +72 -1
- package/src/cli.js +290 -19
- package/src/project.js +140 -3
- package/src/session-index.js +13 -3
package/README.md
CHANGED
|
@@ -138,8 +138,11 @@ aginti update
|
|
|
138
138
|
aginti sessions list
|
|
139
139
|
aginti sessions show <session-id>
|
|
140
140
|
aginti sessions rename <session-id> "friendly title"
|
|
141
|
+
aginti --remove-empty-sessions
|
|
142
|
+
aginti --remove-sessions
|
|
141
143
|
aginti storage migrate
|
|
142
144
|
aginti resume
|
|
145
|
+
aginti resume --all-sessions
|
|
143
146
|
aginti resume latest
|
|
144
147
|
aginti resume <session-id> "continue with a short follow-up"
|
|
145
148
|
aginti queue <session-id> "extra instruction for the running agent"
|
|
@@ -148,6 +151,10 @@ aginti --latex "draw a figure, write a short LaTeX report, and compile the PDF"
|
|
|
148
151
|
aginti "set up this project and run the tests"
|
|
149
152
|
```
|
|
150
153
|
|
|
154
|
+
Bare `aginti resume` lists sessions for the current cwd by default. Use `--all-sessions` to browse the global session index; in the interactive selector, type a number to resume, `q` to quit, `/text` to filter the visible list, or `/` to clear the filter.
|
|
155
|
+
|
|
156
|
+
Session cleanup is cwd-scoped by default. `aginti --remove-empty-sessions` shows only empty sessions and preselects them; `aginti --remove-sessions` shows all cwd sessions with nothing preselected. The cleanup selector uses Space to select, Up/Down to move, Tab to switch to OK/Cancel, and a second Yes/Cancel confirmation before deleting the project pointer and central `~/.agintiflow/sessions/<session-id>` data.
|
|
157
|
+
|
|
151
158
|
Run from a source checkout:
|
|
152
159
|
|
|
153
160
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.44",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -235,6 +235,11 @@ try {
|
|
|
235
235
|
if (classifyEscapeAction({ active: true, pendingAsap: [] }) !== "abort") {
|
|
236
236
|
throw new Error("active Esc should abort when no ASAP pipe messages are pending");
|
|
237
237
|
}
|
|
238
|
+
await runChat("/exit\n");
|
|
239
|
+
const idleSessionEntries = await fs.readdir(path.join(agintiflowHome, "sessions"), { withFileTypes: true }).catch(() => []);
|
|
240
|
+
if (idleSessionEntries.some((entry) => entry.isDirectory())) {
|
|
241
|
+
throw new Error("idle interactive chat created a session before any user task");
|
|
242
|
+
}
|
|
238
243
|
if (
|
|
239
244
|
canonicalSlashPromptBuffer("/ve") !== "/venice" ||
|
|
240
245
|
canonicalSlashPromptBuffer("/v") !== "/venice" ||
|
package/scripts/smoke-inbox.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ensureProjectSessionStorage,
|
|
7
|
+
listProjectSessionRemovalCandidates,
|
|
8
|
+
listProjectSessions,
|
|
9
|
+
removeProjectSessions,
|
|
10
|
+
sessionStoreOptions,
|
|
11
|
+
} from "../src/project.js";
|
|
6
12
|
import { SessionStore } from "../src/session-store.js";
|
|
7
13
|
|
|
8
14
|
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-inbox-"));
|
|
@@ -58,6 +64,67 @@ try {
|
|
|
58
64
|
"project session pointer was not created"
|
|
59
65
|
);
|
|
60
66
|
|
|
67
|
+
const nestedCwd = path.join(legacyProject, "nested");
|
|
68
|
+
await fs.mkdir(nestedCwd, { recursive: true });
|
|
69
|
+
const otherCwdStore = new SessionStore(paths.globalSessionsDir, "other-cwd-smoke", sessionStoreOptions(legacyProject, "other-cwd-smoke"));
|
|
70
|
+
await otherCwdStore.saveState({
|
|
71
|
+
sessionId: "other-cwd-smoke",
|
|
72
|
+
createdAt: "2026-01-01T00:02:00.000Z",
|
|
73
|
+
updatedAt: "2026-01-01T00:03:00.000Z",
|
|
74
|
+
provider: "mock",
|
|
75
|
+
model: "mock-agent",
|
|
76
|
+
goal: "nested cwd smoke",
|
|
77
|
+
projectRoot: legacyProject,
|
|
78
|
+
commandCwd: nestedCwd,
|
|
79
|
+
chat: [],
|
|
80
|
+
});
|
|
81
|
+
const cwdFiltered = await listProjectSessions(legacyProject, { limit: 10, commandCwd: legacyProject });
|
|
82
|
+
assert(!cwdFiltered.some((session) => session.sessionId === "other-cwd-smoke"), "default cwd filtering included a different cwd session");
|
|
83
|
+
const allSessions = await listProjectSessions(legacyProject, { limit: 10, allSessions: true });
|
|
84
|
+
assert(allSessions.some((session) => session.sessionId === "other-cwd-smoke"), "--all-sessions mode did not include a different cwd session");
|
|
85
|
+
|
|
86
|
+
const emptyStore = new SessionStore(paths.globalSessionsDir, "empty-session-smoke", sessionStoreOptions(legacyProject, "empty-session-smoke"));
|
|
87
|
+
await emptyStore.saveState({
|
|
88
|
+
sessionId: "empty-session-smoke",
|
|
89
|
+
createdAt: "2026-01-01T00:04:00.000Z",
|
|
90
|
+
updatedAt: "2026-01-01T00:04:00.000Z",
|
|
91
|
+
provider: "mock",
|
|
92
|
+
model: "mock-agent",
|
|
93
|
+
projectRoot: legacyProject,
|
|
94
|
+
commandCwd: legacyProject,
|
|
95
|
+
chat: [],
|
|
96
|
+
stepsCompleted: 0,
|
|
97
|
+
});
|
|
98
|
+
const nonEmptyStore = new SessionStore(paths.globalSessionsDir, "nonempty-session-smoke", sessionStoreOptions(legacyProject, "nonempty-session-smoke"));
|
|
99
|
+
await nonEmptyStore.saveState({
|
|
100
|
+
sessionId: "nonempty-session-smoke",
|
|
101
|
+
createdAt: "2026-01-01T00:05:00.000Z",
|
|
102
|
+
updatedAt: "2026-01-01T00:05:00.000Z",
|
|
103
|
+
provider: "mock",
|
|
104
|
+
model: "mock-agent",
|
|
105
|
+
goal: "keep this non-empty session",
|
|
106
|
+
projectRoot: legacyProject,
|
|
107
|
+
commandCwd: legacyProject,
|
|
108
|
+
chat: [{ role: "user", content: "hello" }],
|
|
109
|
+
stepsCompleted: 1,
|
|
110
|
+
});
|
|
111
|
+
const removalCandidates = await listProjectSessionRemovalCandidates(legacyProject, { limit: 20, commandCwd: legacyProject });
|
|
112
|
+
assert(removalCandidates.find((session) => session.sessionId === "empty-session-smoke")?.isEmpty, "empty session was not detected");
|
|
113
|
+
assert(removalCandidates.find((session) => session.sessionId === "nonempty-session-smoke")?.isEmpty === false, "non-empty session was classified as empty");
|
|
114
|
+
const emptyOnly = await listProjectSessionRemovalCandidates(legacyProject, { limit: 20, commandCwd: legacyProject, emptyOnly: true });
|
|
115
|
+
assert(emptyOnly.some((session) => session.sessionId === "empty-session-smoke"), "empty-only removal list omitted the empty session");
|
|
116
|
+
assert(!emptyOnly.some((session) => session.sessionId === "nonempty-session-smoke"), "empty-only removal list included a non-empty session");
|
|
117
|
+
const removed = await removeProjectSessions(legacyProject, ["empty-session-smoke"]);
|
|
118
|
+
assert(removed.removed.length === 1, "empty session removal did not report one removed session");
|
|
119
|
+
assert(
|
|
120
|
+
!(await fs.stat(path.join(paths.globalSessionsDir, "empty-session-smoke", "state.json")).then((stat) => stat.isFile()).catch(() => false)),
|
|
121
|
+
"empty session global state was not removed"
|
|
122
|
+
);
|
|
123
|
+
assert(
|
|
124
|
+
!(await fs.stat(path.join(paths.sessionsDir, "empty-session-smoke", "session.json")).then((stat) => stat.isFile()).catch(() => false)),
|
|
125
|
+
"empty session project pointer was not removed"
|
|
126
|
+
);
|
|
127
|
+
|
|
61
128
|
console.log(
|
|
62
129
|
JSON.stringify(
|
|
63
130
|
{
|
|
@@ -68,6 +135,10 @@ try {
|
|
|
68
135
|
"session-inbox-asap-priority",
|
|
69
136
|
"legacy-session-migration",
|
|
70
137
|
"global-session-store",
|
|
138
|
+
"cwd-session-filter",
|
|
139
|
+
"all-sessions-list",
|
|
140
|
+
"empty-session-detection",
|
|
141
|
+
"empty-session-removal",
|
|
71
142
|
],
|
|
72
143
|
},
|
|
73
144
|
null,
|
package/src/cli.js
CHANGED
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
doctorReport,
|
|
18
18
|
ensureProjectSessionStorage,
|
|
19
19
|
initProject,
|
|
20
|
+
listProjectSessionRemovalCandidates,
|
|
20
21
|
listProjectSessions,
|
|
21
22
|
renameProjectSession,
|
|
23
|
+
removeProjectSessions,
|
|
22
24
|
providerKeyStatus,
|
|
23
25
|
setProviderKey,
|
|
24
26
|
showProjectSession,
|
|
@@ -34,6 +36,7 @@ import fs from "node:fs/promises";
|
|
|
34
36
|
import path from "node:path";
|
|
35
37
|
import { fileURLToPath } from "node:url";
|
|
36
38
|
import readline from "node:readline/promises";
|
|
39
|
+
import * as readlineRaw from "node:readline";
|
|
37
40
|
|
|
38
41
|
const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
42
|
const packageJson = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
|
|
@@ -378,7 +381,7 @@ export function parseArgs(argv) {
|
|
|
378
381
|
|
|
379
382
|
function printUsage() {
|
|
380
383
|
console.log(
|
|
381
|
-
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
|
|
384
|
+
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
|
|
382
385
|
);
|
|
383
386
|
console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
|
|
384
387
|
}
|
|
@@ -600,12 +603,219 @@ function printAuthWizardResult(result) {
|
|
|
600
603
|
}
|
|
601
604
|
}
|
|
602
605
|
|
|
606
|
+
const removeSessionAnsi = {
|
|
607
|
+
reset: "\x1b[0m",
|
|
608
|
+
bold: "\x1b[1m",
|
|
609
|
+
dim: "\x1b[2m",
|
|
610
|
+
inverse: "\x1b[7m",
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
function removeSessionColor(text, ...codes) {
|
|
614
|
+
return `${codes.join("")}${text}${removeSessionAnsi.reset}`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function stripAnsi(text) {
|
|
618
|
+
return String(text || "").replace(/\x1b\[[0-9;]*m/g, "");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function ellipsize(text, width) {
|
|
622
|
+
const value = String(text || "").replace(/\s+/g, " ").trim();
|
|
623
|
+
if (value.length <= width) return value.padEnd(width, " ");
|
|
624
|
+
return `${value.slice(0, Math.max(0, width - 1))}…`;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function buttonLabel(label, focused, disabled = false) {
|
|
628
|
+
const text = ` ${label} `;
|
|
629
|
+
if (disabled) return removeSessionColor(text, removeSessionAnsi.dim);
|
|
630
|
+
return focused ? removeSessionColor(text, removeSessionAnsi.inverse, removeSessionAnsi.bold) : text;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function renderSessionRemovalWizard(state) {
|
|
634
|
+
const width = Math.min(Math.max(process.stdout.columns || 100, 80), 128);
|
|
635
|
+
const rows = process.stdout.rows || 28;
|
|
636
|
+
const bodyWidth = width - 4;
|
|
637
|
+
const visibleCount = Math.max(5, Math.min(state.items.length || 1, rows - 11));
|
|
638
|
+
if (state.cursor < state.scroll) state.scroll = state.cursor;
|
|
639
|
+
if (state.cursor >= state.scroll + visibleCount) state.scroll = state.cursor - visibleCount + 1;
|
|
640
|
+
const shown = state.items.slice(state.scroll, state.scroll + visibleCount);
|
|
641
|
+
const selectedCount = state.selected.size;
|
|
642
|
+
const border = "─".repeat(width - 2);
|
|
643
|
+
const line = (content = "") => {
|
|
644
|
+
const value = String(content || "");
|
|
645
|
+
const clipped = stripAnsi(value).length > bodyWidth ? ellipsize(stripAnsi(value), bodyWidth) : value;
|
|
646
|
+
return `│ ${clipped}${" ".repeat(Math.max(0, bodyWidth - stripAnsi(clipped).length))} │`;
|
|
647
|
+
};
|
|
648
|
+
const listRows = shown.map((session, offset) => {
|
|
649
|
+
const index = state.scroll + offset;
|
|
650
|
+
const checked = state.selected.has(session.sessionId) ? "[x]" : "[ ]";
|
|
651
|
+
const cursor = index === state.cursor ? ">" : " ";
|
|
652
|
+
const badge = session.isEmpty ? "empty" : "work";
|
|
653
|
+
const title = session.title || session.goal || "(no title)";
|
|
654
|
+
const meta = `${session.provider || "unknown"}/${session.model || "unknown"} chat=${session.chatCount} steps=${session.stepsCompleted} files=${session.artifactFileCount}`;
|
|
655
|
+
const row = `${cursor} ${checked} ${badge.padEnd(5)} ${session.sessionId} ${meta} ${title}`;
|
|
656
|
+
const clipped = ellipsize(row, bodyWidth);
|
|
657
|
+
return state.focus === "list" && index === state.cursor ? line(removeSessionColor(clipped, removeSessionAnsi.inverse)) : line(clipped);
|
|
658
|
+
});
|
|
659
|
+
const footer =
|
|
660
|
+
state.phase === "confirm"
|
|
661
|
+
? `Confirm deletion: ${buttonLabel("Yes, delete", state.confirmFocus === "yes")} ${buttonLabel("Cancel", state.confirmFocus === "cancel")}`
|
|
662
|
+
: `Actions: ${buttonLabel(`OK delete ${selectedCount}`, state.focus === "ok", selectedCount === 0)} ${buttonLabel("Cancel", state.focus === "cancel")}`;
|
|
663
|
+
const guidance =
|
|
664
|
+
state.phase === "confirm"
|
|
665
|
+
? "Left/Right switches choice. Enter confirms. Esc/q cancels."
|
|
666
|
+
: "Space toggles. Up/Down moves. Tab changes focus. Enter opens confirm. Esc/q cancels.";
|
|
667
|
+
const lines = [
|
|
668
|
+
`╭${border}╮`,
|
|
669
|
+
line(state.title),
|
|
670
|
+
line(state.subtitle),
|
|
671
|
+
line(`Showing ${state.items.length === 0 ? 0 : state.scroll + 1}-${Math.min(state.items.length, state.scroll + visibleCount)} of ${state.items.length}; selected ${selectedCount}`),
|
|
672
|
+
`├${border}┤`,
|
|
673
|
+
...listRows,
|
|
674
|
+
`├${border}┤`,
|
|
675
|
+
line(footer),
|
|
676
|
+
line(state.message || guidance),
|
|
677
|
+
`╰${border}╯`,
|
|
678
|
+
];
|
|
679
|
+
process.stdout.write(`\x1b[H\x1b[2J${lines.join("\n")}`);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
async function promptRemoveSessions(candidates, { defaultSelectedIds = [], title = "Remove sessions", subtitle = "" } = {}) {
|
|
683
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
|
|
684
|
+
console.log("Interactive terminal required; no sessions removed.");
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const state = {
|
|
688
|
+
items: candidates,
|
|
689
|
+
selected: new Set(defaultSelectedIds),
|
|
690
|
+
cursor: 0,
|
|
691
|
+
scroll: 0,
|
|
692
|
+
focus: "list",
|
|
693
|
+
phase: "select",
|
|
694
|
+
confirmFocus: "cancel",
|
|
695
|
+
message: "",
|
|
696
|
+
title,
|
|
697
|
+
subtitle,
|
|
698
|
+
};
|
|
699
|
+
return await new Promise((resolve) => {
|
|
700
|
+
const input = process.stdin;
|
|
701
|
+
const output = process.stdout;
|
|
702
|
+
const cleanup = (value) => {
|
|
703
|
+
input.off("keypress", onKeypress);
|
|
704
|
+
if (input.isTTY) input.setRawMode(false);
|
|
705
|
+
input.pause();
|
|
706
|
+
output.write("\x1b[?25h\x1b[?1049l");
|
|
707
|
+
resolve(value);
|
|
708
|
+
};
|
|
709
|
+
const moveCursor = (delta) => {
|
|
710
|
+
state.focus = "list";
|
|
711
|
+
state.cursor = Math.min(Math.max(state.cursor + delta, 0), Math.max(0, state.items.length - 1));
|
|
712
|
+
state.message = "";
|
|
713
|
+
};
|
|
714
|
+
const toggleCurrent = () => {
|
|
715
|
+
const session = state.items[state.cursor];
|
|
716
|
+
if (!session) return;
|
|
717
|
+
if (state.selected.has(session.sessionId)) state.selected.delete(session.sessionId);
|
|
718
|
+
else state.selected.add(session.sessionId);
|
|
719
|
+
state.message = `${state.selected.size} session(s) selected.`;
|
|
720
|
+
};
|
|
721
|
+
const openConfirm = () => {
|
|
722
|
+
if (state.selected.size === 0) {
|
|
723
|
+
state.message = "Select at least one session before confirming.";
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
state.phase = "confirm";
|
|
727
|
+
state.confirmFocus = "cancel";
|
|
728
|
+
state.message = "Second confirmation required before deleting session data.";
|
|
729
|
+
};
|
|
730
|
+
function onKeypress(char, key = {}) {
|
|
731
|
+
if (key.ctrl && key.name === "c") return cleanup(null);
|
|
732
|
+
const name = key.name || char;
|
|
733
|
+
if (name === "escape" || name === "q") return cleanup(null);
|
|
734
|
+
if (state.phase === "confirm") {
|
|
735
|
+
if (name === "left" || name === "right" || name === "tab") state.confirmFocus = state.confirmFocus === "yes" ? "cancel" : "yes";
|
|
736
|
+
else if (name === "return" || name === "enter") return cleanup(state.confirmFocus === "yes" ? [...state.selected] : null);
|
|
737
|
+
renderSessionRemovalWizard(state);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (name === "up") moveCursor(-1);
|
|
741
|
+
else if (name === "down") moveCursor(1);
|
|
742
|
+
else if (name === "pageup") moveCursor(-8);
|
|
743
|
+
else if (name === "pagedown") moveCursor(8);
|
|
744
|
+
else if (name === "space") toggleCurrent();
|
|
745
|
+
else if (name === "tab") state.focus = state.focus === "list" ? "ok" : state.focus === "ok" ? "cancel" : "list";
|
|
746
|
+
else if (name === "left" || name === "right") state.focus = state.focus === "cancel" ? "ok" : "cancel";
|
|
747
|
+
else if (name === "return" || name === "enter") {
|
|
748
|
+
if (state.focus === "cancel") return cleanup(null);
|
|
749
|
+
openConfirm();
|
|
750
|
+
}
|
|
751
|
+
renderSessionRemovalWizard(state);
|
|
752
|
+
}
|
|
753
|
+
readlineRaw.emitKeypressEvents(input);
|
|
754
|
+
input.setRawMode(true);
|
|
755
|
+
input.resume();
|
|
756
|
+
output.write("\x1b[?1049h\x1b[?25l");
|
|
757
|
+
input.on("keypress", onKeypress);
|
|
758
|
+
renderSessionRemovalWizard(state);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function printRemovalPreview(candidates) {
|
|
763
|
+
for (const session of candidates) {
|
|
764
|
+
const title = session.title || session.goal || "(no title)";
|
|
765
|
+
const status = session.isEmpty ? "empty" : "work";
|
|
766
|
+
console.log(`${status.padEnd(5)} ${session.sessionId} ${session.updatedAt || ""} ${title.slice(0, 90)}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
async function handleRemoveSessionsCommand({ emptyOnly = false } = {}) {
|
|
771
|
+
const candidates = await listProjectSessionRemovalCandidates(process.cwd(), {
|
|
772
|
+
limit: 1000,
|
|
773
|
+
emptyOnly,
|
|
774
|
+
});
|
|
775
|
+
if (candidates.length === 0) {
|
|
776
|
+
console.log(emptyOnly ? "No empty sessions found for this cwd." : "No sessions found for this cwd.");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
780
|
+
printRemovalPreview(candidates);
|
|
781
|
+
console.log("No sessions removed because this command needs an interactive terminal.");
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const defaultSelectedIds = emptyOnly ? candidates.map((session) => session.sessionId) : [];
|
|
785
|
+
const selected = await promptRemoveSessions(candidates, {
|
|
786
|
+
defaultSelectedIds,
|
|
787
|
+
title: emptyOnly ? "Remove empty AgInTiFlow sessions in this cwd" : "Remove AgInTiFlow sessions in this cwd",
|
|
788
|
+
subtitle: emptyOnly
|
|
789
|
+
? "Only empty sessions are shown and selected by default."
|
|
790
|
+
: "All cwd sessions are shown; nothing is selected by default.",
|
|
791
|
+
});
|
|
792
|
+
if (!selected || selected.length === 0) {
|
|
793
|
+
console.log("No sessions removed.");
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const allowedIds = new Set(candidates.map((session) => session.sessionId));
|
|
797
|
+
const safeSelected = selected.filter((sessionId) => allowedIds.has(sessionId));
|
|
798
|
+
const result = await removeProjectSessions(process.cwd(), safeSelected);
|
|
799
|
+
console.log(`Removed ${result.removed.length} session(s) from this cwd:`);
|
|
800
|
+
for (const item of result.removed) console.log(`- ${item.sessionId}`);
|
|
801
|
+
}
|
|
802
|
+
|
|
603
803
|
async function handleSessionsCommand(argv) {
|
|
604
|
-
const
|
|
804
|
+
const normalizedArgv = argv[0] === "--all-sessions" ? ["list", ...argv] : argv;
|
|
805
|
+
const [verb = "list", sessionId = "", ...rest] = normalizedArgv;
|
|
806
|
+
if (verb === "remove-empty" || verb === "delete-empty") {
|
|
807
|
+
await handleRemoveSessionsCommand({ emptyOnly: true });
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (verb === "remove" || verb === "delete") {
|
|
811
|
+
await handleRemoveSessionsCommand({ emptyOnly: false });
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
605
814
|
if (verb === "list") {
|
|
606
|
-
const
|
|
815
|
+
const allSessions = normalizedArgv.includes("--all-sessions");
|
|
816
|
+
const sessions = await listProjectSessions(process.cwd(), { limit: 80, allSessions });
|
|
607
817
|
if (sessions.length === 0) {
|
|
608
|
-
console.log("No
|
|
818
|
+
console.log(allSessions ? "No sessions found." : "No sessions found for this cwd.");
|
|
609
819
|
return;
|
|
610
820
|
}
|
|
611
821
|
for (const session of sessions) {
|
|
@@ -636,39 +846,86 @@ async function handleSessionsCommand(argv) {
|
|
|
636
846
|
return;
|
|
637
847
|
}
|
|
638
848
|
|
|
639
|
-
console.error('Usage: aginti sessions list OR aginti sessions show <session-id> OR aginti sessions rename <session-id> "title"');
|
|
849
|
+
console.error('Usage: aginti sessions list OR aginti sessions show <session-id> OR aginti sessions rename <session-id> "title" OR aginti sessions remove-empty OR aginti sessions remove');
|
|
640
850
|
process.exit(1);
|
|
641
851
|
}
|
|
642
852
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
853
|
+
function sessionSearchText(session) {
|
|
854
|
+
return [
|
|
855
|
+
session.sessionId,
|
|
856
|
+
session.provider,
|
|
857
|
+
session.model,
|
|
858
|
+
session.updatedAt,
|
|
859
|
+
session.title,
|
|
860
|
+
session.goal,
|
|
861
|
+
session.projectRoot,
|
|
862
|
+
session.commandCwd,
|
|
863
|
+
]
|
|
864
|
+
.filter(Boolean)
|
|
865
|
+
.join(" ")
|
|
866
|
+
.toLowerCase();
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function filterSessions(sessions, filterText = "") {
|
|
870
|
+
const needle = String(filterText || "").trim().toLowerCase();
|
|
871
|
+
if (!needle) return sessions;
|
|
872
|
+
return sessions.filter((session) => sessionSearchText(session).includes(needle));
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function printSessionChoices(sessions, { filterText = "", allSessions = false, maxShown = 20 } = {}) {
|
|
876
|
+
const scope = allSessions ? "all sessions" : `cwd ${process.cwd()}`;
|
|
877
|
+
const shown = sessions.slice(0, maxShown);
|
|
878
|
+
console.log(`Select a session to resume (${scope}${filterText ? `, filter="${filterText}"` : ""}):`);
|
|
879
|
+
if (shown.length === 0) {
|
|
880
|
+
console.log("No matching sessions. Type /text to change the filter, / to clear it, or q to quit.");
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
shown.forEach((session, index) => {
|
|
647
884
|
const title = session.title || session.goal || "(untitled)";
|
|
648
885
|
console.log(
|
|
649
886
|
`${index + 1}. ${session.sessionId} ${session.provider || "unknown"}/${session.model || "unknown"} ${session.updatedAt || ""} ${title.slice(0, 90)}`
|
|
650
887
|
);
|
|
651
888
|
});
|
|
889
|
+
if (sessions.length > shown.length) console.log(`... ${sessions.length - shown.length} more hidden by display limit; type /text to narrow.`);
|
|
890
|
+
console.log("Type a number to select, /text to filter, / to clear, or q to quit.");
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
async function promptSelectSession(sessions, { allSessions = false } = {}) {
|
|
894
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return sessions[0]?.sessionId || "";
|
|
652
895
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
896
|
+
let filterText = "";
|
|
653
897
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
898
|
+
while (true) {
|
|
899
|
+
const filtered = filterSessions(sessions, filterText);
|
|
900
|
+
printSessionChoices(filtered, { filterText, allSessions });
|
|
901
|
+
const answer = (await rl.question("Session number/filter: ")).trim();
|
|
902
|
+
if (!answer || answer.toLowerCase() === "q" || answer.toLowerCase() === "quit") return "";
|
|
903
|
+
if (answer.startsWith("/")) {
|
|
904
|
+
filterText = answer.slice(1).trim();
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
const index = Number(answer) - 1;
|
|
908
|
+
if (Number.isInteger(index) && index >= 0 && index < Math.min(filtered.length, 20)) {
|
|
909
|
+
return filtered[index]?.sessionId || "";
|
|
910
|
+
}
|
|
911
|
+
console.log("Invalid selection. Type a shown number, /text to filter, or q to quit.");
|
|
912
|
+
}
|
|
657
913
|
} finally {
|
|
658
914
|
rl.close();
|
|
659
915
|
}
|
|
660
916
|
}
|
|
661
917
|
|
|
662
|
-
async function resolveResumeSessionId(sessionId) {
|
|
918
|
+
async function resolveResumeSessionId(sessionId, { allSessions = false } = {}) {
|
|
663
919
|
if (sessionId && sessionId !== "latest") return sessionId;
|
|
664
|
-
const sessions = await listProjectSessions(process.cwd(),
|
|
920
|
+
const sessions = await listProjectSessions(process.cwd(), { limit: 1000, allSessions });
|
|
665
921
|
if (sessionId === "latest" || sessions.length <= 1 || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
666
922
|
if (sessions[0]?.sessionId) return sessions[0].sessionId;
|
|
667
923
|
} else {
|
|
668
|
-
const selected = await promptSelectSession(sessions);
|
|
924
|
+
const selected = await promptSelectSession(sessions, { allSessions });
|
|
669
925
|
if (selected) return selected;
|
|
926
|
+
return "";
|
|
670
927
|
}
|
|
671
|
-
throw new Error("No
|
|
928
|
+
throw new Error(allSessions ? "No sessions found." : "No sessions found for this cwd. Use `aginti resume --all-sessions` to browse all sessions.");
|
|
672
929
|
}
|
|
673
930
|
|
|
674
931
|
async function handleQueueCommand(argv) {
|
|
@@ -739,6 +996,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
739
996
|
});
|
|
740
997
|
if (autoUpdateResult.restarted) process.exit(autoUpdateResult.exitCode ?? 0);
|
|
741
998
|
|
|
999
|
+
if (argv.includes("--remove-empty-sessions") || argv[0] === "remove-empty-sessions") {
|
|
1000
|
+
await handleRemoveSessionsCommand({ emptyOnly: true });
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (argv.includes("--remove-sessions") || argv[0] === "remove-sessions") {
|
|
1005
|
+
await handleRemoveSessionsCommand({ emptyOnly: false });
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
742
1009
|
if (argv[0] === "init") {
|
|
743
1010
|
printInitResult(await initProject(process.cwd()));
|
|
744
1011
|
return;
|
|
@@ -837,14 +1104,18 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
837
1104
|
}
|
|
838
1105
|
|
|
839
1106
|
if (argv[0] === "resume") {
|
|
840
|
-
|
|
841
|
-
const
|
|
1107
|
+
const resumeArgv = argv.slice(1);
|
|
1108
|
+
const allSessions = resumeArgv.includes("--all-sessions");
|
|
1109
|
+
const positional = resumeArgv.filter((arg) => arg !== "--all-sessions");
|
|
1110
|
+
let sessionId = positional[0] || "";
|
|
1111
|
+
const prompt = positional.slice(1).join(" ").trim();
|
|
842
1112
|
try {
|
|
843
|
-
sessionId = await resolveResumeSessionId(sessionId);
|
|
1113
|
+
sessionId = await resolveResumeSessionId(sessionId, { allSessions });
|
|
844
1114
|
} catch (error) {
|
|
845
1115
|
console.error(error instanceof Error ? error.message : String(error));
|
|
846
1116
|
process.exit(1);
|
|
847
1117
|
}
|
|
1118
|
+
if (!sessionId) return;
|
|
848
1119
|
if (!prompt) {
|
|
849
1120
|
await startInteractiveCli(agentDefaults({ ...parseArgs([]), resume: sessionId }), {
|
|
850
1121
|
packageDir,
|
package/src/project.js
CHANGED
|
@@ -9,6 +9,7 @@ import { platformInfo, platformLabel, platformSetupHints } from "./platform.js";
|
|
|
9
9
|
import {
|
|
10
10
|
LEGACY_PROJECT_SESSIONS_DIR_NAME,
|
|
11
11
|
PROJECT_SESSIONS_DIR_NAME,
|
|
12
|
+
deleteSessionIndex,
|
|
12
13
|
globalSessionPaths,
|
|
13
14
|
isSafeSessionId,
|
|
14
15
|
listSessionIndex,
|
|
@@ -484,11 +485,34 @@ export async function setProviderKey(projectRoot, provider, value) {
|
|
|
484
485
|
};
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
|
|
488
|
+
function normalizeSessionListOptions(projectRoot, limitOrOptions = 50) {
|
|
489
|
+
const options = typeof limitOrOptions === "object" && limitOrOptions !== null ? limitOrOptions : { limit: limitOrOptions };
|
|
490
|
+
const root = resolveProjectRoot(projectRoot);
|
|
491
|
+
const commandCwd = options.commandCwd === false ? "" : path.resolve(options.commandCwd || root);
|
|
492
|
+
return {
|
|
493
|
+
limit: Math.min(Math.max(Number(options.limit) || 50, 1), 1000),
|
|
494
|
+
commandCwd,
|
|
495
|
+
allSessions: Boolean(options.allSessions),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function sessionMatchesCommandCwd(session, commandCwd = "") {
|
|
500
|
+
if (!commandCwd) return true;
|
|
501
|
+
const value = session.commandCwd || session.projectRoot || "";
|
|
502
|
+
if (!value) return false;
|
|
503
|
+
return path.resolve(value) === path.resolve(commandCwd);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function listProjectSessions(projectRoot = process.cwd(), limitOrOptions = 50) {
|
|
507
|
+
const options = normalizeSessionListOptions(projectRoot, limitOrOptions);
|
|
488
508
|
const paths = await ensureProjectSessionStorage(projectRoot);
|
|
489
509
|
const indexed = (() => {
|
|
490
510
|
try {
|
|
491
|
-
return listSessionIndex({
|
|
511
|
+
return listSessionIndex({
|
|
512
|
+
projectRoot: options.allSessions ? "" : paths.root,
|
|
513
|
+
commandCwd: options.allSessions ? "" : options.commandCwd,
|
|
514
|
+
limit: Math.max(options.limit, 100),
|
|
515
|
+
});
|
|
492
516
|
} catch {
|
|
493
517
|
return [];
|
|
494
518
|
}
|
|
@@ -521,6 +545,7 @@ export async function listProjectSessions(projectRoot = process.cwd(), limit = 5
|
|
|
521
545
|
updatedAt: state?.updatedAt || pointer.updatedAt || byId.get(sessionId)?.updatedAt || state?.createdAt || "",
|
|
522
546
|
stepsCompleted: state?.stepsCompleted || 0,
|
|
523
547
|
};
|
|
548
|
+
if (!options.allSessions && !sessionMatchesCommandCwd(record, options.commandCwd)) continue;
|
|
524
549
|
byId.set(sessionId, record);
|
|
525
550
|
try {
|
|
526
551
|
upsertSessionIndex({
|
|
@@ -534,7 +559,119 @@ export async function listProjectSessions(projectRoot = process.cwd(), limit = 5
|
|
|
534
559
|
}
|
|
535
560
|
|
|
536
561
|
const sessions = [...byId.values()];
|
|
537
|
-
return sessions.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt))).slice(0, limit);
|
|
562
|
+
return sessions.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt))).slice(0, options.limit);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function readJsonFile(filePath, fallback = {}) {
|
|
566
|
+
try {
|
|
567
|
+
return JSON.parse(await fsp.readFile(filePath, "utf8"));
|
|
568
|
+
} catch {
|
|
569
|
+
return fallback;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function readJsonLines(filePath) {
|
|
574
|
+
try {
|
|
575
|
+
const raw = await fsp.readFile(filePath, "utf8");
|
|
576
|
+
return raw
|
|
577
|
+
.split("\n")
|
|
578
|
+
.map((line) => line.trim())
|
|
579
|
+
.filter(Boolean)
|
|
580
|
+
.map((line) => JSON.parse(line));
|
|
581
|
+
} catch {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function countFilesRecursive(dirPath, limit = 200) {
|
|
587
|
+
let count = 0;
|
|
588
|
+
async function walk(current) {
|
|
589
|
+
if (count >= limit) return;
|
|
590
|
+
const entries = await fsp.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
591
|
+
for (const entry of entries) {
|
|
592
|
+
if (count >= limit) return;
|
|
593
|
+
const child = path.join(current, entry.name);
|
|
594
|
+
if (entry.isDirectory()) await walk(child);
|
|
595
|
+
else if (entry.isFile()) count += 1;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
await walk(dirPath);
|
|
599
|
+
return count;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function isMeaningfulSessionEvent(event = {}) {
|
|
603
|
+
const type = String(event.type || "");
|
|
604
|
+
if (!type) return false;
|
|
605
|
+
return /^(agent|assistant|browser|canvas|conversation|file|image|model|patch|plan|run|shell|tool|workspace|write)[.:_-]/.test(type);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export async function listProjectSessionRemovalCandidates(projectRoot = process.cwd(), options = {}) {
|
|
609
|
+
const paths = await ensureProjectSessionStorage(projectRoot);
|
|
610
|
+
const sessions = await listProjectSessions(projectRoot, {
|
|
611
|
+
limit: options.limit || 1000,
|
|
612
|
+
commandCwd: options.commandCwd,
|
|
613
|
+
allSessions: options.allSessions,
|
|
614
|
+
});
|
|
615
|
+
const candidates = [];
|
|
616
|
+
for (const session of sessions) {
|
|
617
|
+
const safeId = String(session.sessionId || "");
|
|
618
|
+
if (!isSafeSessionId(safeId)) continue;
|
|
619
|
+
const pointerPath = path.join(paths.sessionsDir, safeId, "session.json");
|
|
620
|
+
const pointer = await readJsonFile(pointerPath, {});
|
|
621
|
+
const sessionDir = session.sessionDir || pointer.sessionDir || path.join(paths.globalSessionsDir, safeId);
|
|
622
|
+
const state = await readJsonFile(path.join(sessionDir, "state.json"), {});
|
|
623
|
+
const events = await readJsonLines(path.join(sessionDir, "events.jsonl"));
|
|
624
|
+
const chat = Array.isArray(state.chat) ? state.chat : [];
|
|
625
|
+
const goal = String(state.goal || pointer.goal || session.goal || "").trim();
|
|
626
|
+
const title = String(state.title || pointer.title || session.title || "").trim();
|
|
627
|
+
const stepsCompleted = Number(state.stepsCompleted || 0);
|
|
628
|
+
const artifactsDir = state.artifactsDir || pointer.artifactsDir || path.join(sessionDir, "artifacts");
|
|
629
|
+
const artifactFileCount = await countFilesRecursive(artifactsDir);
|
|
630
|
+
const meaningfulEventCount = events.filter(isMeaningfulSessionEvent).length;
|
|
631
|
+
const isEmpty =
|
|
632
|
+
!goal &&
|
|
633
|
+
!title &&
|
|
634
|
+
chat.length === 0 &&
|
|
635
|
+
stepsCompleted === 0 &&
|
|
636
|
+
artifactFileCount === 0 &&
|
|
637
|
+
meaningfulEventCount === 0;
|
|
638
|
+
candidates.push({
|
|
639
|
+
...session,
|
|
640
|
+
sessionId: safeId,
|
|
641
|
+
sessionDir,
|
|
642
|
+
pointerPath,
|
|
643
|
+
chatCount: chat.length,
|
|
644
|
+
eventCount: events.length,
|
|
645
|
+
meaningfulEventCount,
|
|
646
|
+
artifactFileCount,
|
|
647
|
+
stepsCompleted,
|
|
648
|
+
isEmpty,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return options.emptyOnly ? candidates.filter((session) => session.isEmpty) : candidates;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export async function removeProjectSessions(projectRoot = process.cwd(), sessionIds = []) {
|
|
655
|
+
const paths = await ensureProjectSessionStorage(projectRoot);
|
|
656
|
+
const removed = [];
|
|
657
|
+
for (const rawId of sessionIds) {
|
|
658
|
+
const safeId = String(rawId || "");
|
|
659
|
+
if (!isSafeSessionId(safeId)) throw new Error(`Invalid session id: ${rawId}`);
|
|
660
|
+
const pointerDir = path.join(paths.sessionsDir, safeId);
|
|
661
|
+
const pointer = await readJsonFile(path.join(pointerDir, "session.json"), {});
|
|
662
|
+
const sessionDir = pointer.sessionDir || path.join(paths.globalSessionsDir, safeId);
|
|
663
|
+
const legacyDir = path.join(paths.legacySessionsDir, safeId);
|
|
664
|
+
await fsp.rm(sessionDir, { recursive: true, force: true });
|
|
665
|
+
await fsp.rm(pointerDir, { recursive: true, force: true });
|
|
666
|
+
await fsp.rm(legacyDir, { recursive: true, force: true }).catch(() => {});
|
|
667
|
+
try {
|
|
668
|
+
deleteSessionIndex(safeId);
|
|
669
|
+
} catch {
|
|
670
|
+
// The on-disk session and pointer are already removed; stale index cleanup can be retried later.
|
|
671
|
+
}
|
|
672
|
+
removed.push({ sessionId: safeId, sessionDir, pointerDir });
|
|
673
|
+
}
|
|
674
|
+
return { ok: true, removed };
|
|
538
675
|
}
|
|
539
676
|
|
|
540
677
|
export async function showProjectSession(projectRoot, sessionId) {
|
package/src/session-index.js
CHANGED
|
@@ -120,16 +120,26 @@ export function deleteSessionIndex(sessionId) {
|
|
|
120
120
|
return result.changes > 0;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
export function listSessionIndex({ projectRoot = "", limit = 100 } = {}) {
|
|
123
|
+
export function listSessionIndex({ projectRoot = "", commandCwd = "", limit = 100 } = {}) {
|
|
124
124
|
const db = ensureIndexDb();
|
|
125
125
|
const maxRows = Math.min(Math.max(Number(limit) || 100, 1), 1000);
|
|
126
126
|
const columns = `session_id AS sessionId, project_root AS projectRoot, command_cwd AS commandCwd, project_sessions_dir AS projectSessionsDir,
|
|
127
127
|
session_dir AS sessionDir, provider, model, goal, title, status,
|
|
128
128
|
created_at AS createdAt, updated_at AS updatedAt, ended_at AS endedAt, result, error`;
|
|
129
|
+
const clauses = [];
|
|
130
|
+
const params = [];
|
|
129
131
|
if (projectRoot) {
|
|
132
|
+
clauses.push("project_root = ?");
|
|
133
|
+
params.push(path.resolve(projectRoot));
|
|
134
|
+
}
|
|
135
|
+
if (commandCwd) {
|
|
136
|
+
clauses.push("command_cwd = ?");
|
|
137
|
+
params.push(path.resolve(commandCwd));
|
|
138
|
+
}
|
|
139
|
+
if (clauses.length > 0) {
|
|
130
140
|
return db
|
|
131
|
-
.prepare(`SELECT ${columns} FROM sessions WHERE
|
|
132
|
-
.all(
|
|
141
|
+
.prepare(`SELECT ${columns} FROM sessions WHERE ${clauses.join(" AND ")} ORDER BY updated_at DESC LIMIT ?`)
|
|
142
|
+
.all(...params, maxRows);
|
|
133
143
|
}
|
|
134
144
|
return db.prepare(`SELECT ${columns} FROM sessions ORDER BY updated_at DESC LIMIT ?`).all(maxRows);
|
|
135
145
|
}
|