session-steward 0.11.1 → 0.11.2
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 +11 -0
- package/README.md +3 -1
- package/bin/session-steward.mjs +16 -0
- package/lib/providers/claude-code/store.mjs +3 -3
- package/lib/providers/codex/database-families.mjs +22 -25
- package/lib/providers/codex/store.mjs +8 -2
- package/lib/self-update.mjs +95 -0
- package/lib/update-check.mjs +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.2] - 2026-09-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added `session-steward update` with version output and a sudo prompt when needed.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Updated compatibility coverage for current Codex and Claude releases, including Codex memory v2 storage.
|
|
12
|
+
|
|
3
13
|
## [0.11.1] - 2026-09-14
|
|
4
14
|
|
|
5
15
|
### Added
|
|
@@ -174,6 +184,7 @@
|
|
|
174
184
|
- Support for custom Codex home folders and a saved folder preference.
|
|
175
185
|
- Streaming and bounded-memory discovery for large session collections and transcripts.
|
|
176
186
|
|
|
187
|
+
[0.11.2]: https://github.com/mallikcheripally/session-steward/compare/v0.11.1...v0.11.2
|
|
177
188
|
[0.11.1]: https://github.com/mallikcheripally/session-steward/compare/v0.11.0...v0.11.1
|
|
178
189
|
[0.11.0]: https://github.com/mallikcheripally/session-steward/compare/v0.10.3...v0.11.0
|
|
179
190
|
[0.10.3]: https://github.com/mallikcheripally/session-steward/compare/v0.10.2...v0.10.3
|
package/README.md
CHANGED
|
@@ -192,10 +192,12 @@ Archiving a Claude Desktop session does not delete it. It remains available unti
|
|
|
192
192
|
## Update or uninstall
|
|
193
193
|
|
|
194
194
|
```bash
|
|
195
|
-
|
|
195
|
+
session-steward update
|
|
196
196
|
npm uninstall --global session-steward
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
If npm needs root access, the update command asks before retrying with `sudo`.
|
|
200
|
+
|
|
199
201
|
Uninstalling does not remove provider sessions, recovery backups, or saved folder preferences.
|
|
200
202
|
|
|
201
203
|
## Troubleshooting
|
package/bin/session-steward.mjs
CHANGED
|
@@ -13,6 +13,20 @@ assertSupportedNode();
|
|
|
13
13
|
if (process.argv[2] === "mcp") {
|
|
14
14
|
process.argv.splice(2, 1);
|
|
15
15
|
await import("./session-steward-mcp.mjs");
|
|
16
|
+
} else if (process.argv[2] === "update") {
|
|
17
|
+
process.argv.splice(2, 1);
|
|
18
|
+
if (process.argv.length !== 2) {
|
|
19
|
+
process.stderr.write("Usage: session-steward update\n");
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
} else {
|
|
22
|
+
const { updateSessionSteward } = await import("../lib/self-update.mjs");
|
|
23
|
+
try {
|
|
24
|
+
process.exitCode = await updateSessionSteward();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
process.stderr.write(`Could not run the update: ${error.message}\n`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
16
30
|
} else {
|
|
17
31
|
await startBrowser();
|
|
18
32
|
}
|
|
@@ -33,9 +47,11 @@ async function startBrowser() {
|
|
|
33
47
|
if (values.help) {
|
|
34
48
|
process.stdout.write(`Usage: session-steward [options]
|
|
35
49
|
session-steward mcp [options]
|
|
50
|
+
session-steward update
|
|
36
51
|
|
|
37
52
|
Commands:
|
|
38
53
|
mcp Run the MCP server over stdio
|
|
54
|
+
update Install the latest version with npm
|
|
39
55
|
|
|
40
56
|
Options:
|
|
41
57
|
--codex-home <path> Use a custom Codex session folder
|
|
@@ -19,15 +19,15 @@ const PROVIDER_ID = "claude-code";
|
|
|
19
19
|
const COMPATIBILITY_PROFILE = Object.freeze({
|
|
20
20
|
id: "claude-local-store-2026-08",
|
|
21
21
|
builtFor: {
|
|
22
|
-
claudeCli: ["2.1.199", "2.1.220", "2.1.228", "2.1.237", "2.1.267"],
|
|
23
|
-
claudeDesktop: ["1.24012.9", "1.28929.0", "1.32885.1", "1.40609.0", "1.49585.0"],
|
|
22
|
+
claudeCli: ["2.1.199", "2.1.220", "2.1.228", "2.1.237", "2.1.267", "2.1.280"],
|
|
23
|
+
claudeDesktop: ["1.24012.9", "1.28929.0", "1.32885.1", "1.40609.0", "1.49585.0", "2.2553.1"],
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
26
|
const SUPPORTED_ENTRYPOINTS = new Set(["cli", "claude-desktop"]);
|
|
27
27
|
const SHARED_DESKTOP_STATE_FILES = new Set(["scheduled-tasks.json"]);
|
|
28
28
|
const KNOWN_TOP_LEVEL = new Set([
|
|
29
29
|
".DS_Store", ".last-cleanup", ".last-update-result.json", "agents", "backups", "cache", "commands", "debug", "downloads", "file-history", "history.jsonl", "history.jsonl.lock",
|
|
30
|
-
"ide", "paste-cache", "plans", "plugins", "projects", "session-env", "sessions", "settings.json",
|
|
30
|
+
"ide", "image-cache", "paste-cache", "plans", "plugins", "projects", "session-env", "sessions", "settings.json",
|
|
31
31
|
"settings.local.json", "shell-snapshots", "skills", "stats-cache.json", "tasks", "telemetry",
|
|
32
32
|
"todos", "uploads", "usage-data", "mcp-needs-auth-cache.json", "session-steward-backups",
|
|
33
33
|
]);
|
|
@@ -4,12 +4,24 @@ import path from "node:path";
|
|
|
4
4
|
import { queryRows } from "../../storage/sqlite.mjs";
|
|
5
5
|
|
|
6
6
|
const CACHE_TTL_MS = 2_000;
|
|
7
|
+
const MEMORY_TABLES = [
|
|
8
|
+
{ name: "stage1_outputs", requiredColumns: ["thread_id"] },
|
|
9
|
+
{
|
|
10
|
+
name: "jobs",
|
|
11
|
+
optional: true,
|
|
12
|
+
requiredColumns: [
|
|
13
|
+
"kind", "job_key", "status", "worker_id", "ownership_token", "started_at",
|
|
14
|
+
"finished_at", "lease_until", "retry_at", "retry_remaining", "last_error",
|
|
15
|
+
"input_watermark", "last_success_watermark",
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
];
|
|
7
19
|
|
|
8
20
|
export const CODEX_DATABASE_PROFILE = Object.freeze({
|
|
9
|
-
id: "codex-local-store-2026-
|
|
21
|
+
id: "codex-local-store-2026-09",
|
|
10
22
|
builtFor: {
|
|
11
|
-
chatgptDesktop: ["26.727.40816", "26.803.61601", "26.818.21641", "26.818.22352", "26.825.41651", "26.903.61454", "26.903.71938"],
|
|
12
|
-
codexCli: ["0.144.1", "0.146.0", "0.147.0", "0.148.0", "0.153.4", "0.154.0"],
|
|
23
|
+
chatgptDesktop: ["26.727.40816", "26.803.61601", "26.818.21641", "26.818.22352", "26.825.41651", "26.903.61454", "26.903.71938", "26.917.51856"],
|
|
24
|
+
codexCli: ["0.144.1", "0.146.0", "0.147.0", "0.148.0", "0.153.4", "0.154.0", "0.156.1"],
|
|
13
25
|
},
|
|
14
26
|
});
|
|
15
27
|
|
|
@@ -30,28 +42,13 @@ const SCHEMA_REQUIREMENTS = Object.freeze({
|
|
|
30
42
|
fallback: "memories_1.sqlite",
|
|
31
43
|
pattern: /^memories_(\d+)\.sqlite$/u,
|
|
32
44
|
required: false,
|
|
33
|
-
tables:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"job_key",
|
|
41
|
-
"status",
|
|
42
|
-
"worker_id",
|
|
43
|
-
"ownership_token",
|
|
44
|
-
"started_at",
|
|
45
|
-
"finished_at",
|
|
46
|
-
"lease_until",
|
|
47
|
-
"retry_at",
|
|
48
|
-
"retry_remaining",
|
|
49
|
-
"last_error",
|
|
50
|
-
"input_watermark",
|
|
51
|
-
"last_success_watermark",
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
45
|
+
tables: MEMORY_TABLES,
|
|
46
|
+
},
|
|
47
|
+
memoriesV2: {
|
|
48
|
+
fallback: "memories_v2_1.sqlite",
|
|
49
|
+
pattern: /^memories_v2_(\d+)\.sqlite$/u,
|
|
50
|
+
required: false,
|
|
51
|
+
tables: MEMORY_TABLES,
|
|
55
52
|
},
|
|
56
53
|
goals: {
|
|
57
54
|
fallback: "goals_1.sqlite",
|
|
@@ -479,7 +479,7 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
|
|
|
479
479
|
const archivedSessionsDirectory = path.join(codexHome, "archived_sessions");
|
|
480
480
|
const sessionsDirectory = path.join(codexHome, "sessions");
|
|
481
481
|
const resolution = resolveCodexDatabases(codexHome, { refresh });
|
|
482
|
-
const { goals, logs, memories, queue, state, threadHistory } = resolution.families;
|
|
482
|
+
const { goals, logs, memories, memoriesV2, queue, state, threadHistory } = resolution.families;
|
|
483
483
|
const stateDatabasePath = state.primary?.path ?? path.join(codexHome, "state_5.sqlite");
|
|
484
484
|
const logsDatabasePath = logs.primary?.path ?? path.join(codexHome, "logs_2.sqlite");
|
|
485
485
|
const memoryDatabasePath = memories.primary?.path ?? path.join(codexHome, "memories_1.sqlite");
|
|
@@ -500,7 +500,8 @@ export function getCodexPaths(codexHomeInput, { refresh = false } = {}) {
|
|
|
500
500
|
logsDatabasePath,
|
|
501
501
|
logsDatabasePaths: allValidDatabases(logs).map((database) => database.path),
|
|
502
502
|
memoryDatabasePath,
|
|
503
|
-
memoryDatabasePaths:
|
|
503
|
+
memoryDatabasePaths: [memories, memoriesV2].flatMap((family) =>
|
|
504
|
+
allValidDatabases(family).map((database) => database.path)),
|
|
504
505
|
queueDatabasePath,
|
|
505
506
|
queueDatabasePaths: allValidDatabases(queue).map((database) => database.path),
|
|
506
507
|
resolvedDatabases: databaseFamilySummary(resolution),
|
|
@@ -971,6 +972,8 @@ function stateSchema(database) {
|
|
|
971
972
|
? database.tables.thread_dynamic_tools
|
|
972
973
|
: inspectSqliteTable(database.path, "thread_dynamic_tools");
|
|
973
974
|
const schema = {
|
|
975
|
+
attachmentTables: ["thread_artifacts", "thread_attachments"]
|
|
976
|
+
.filter((tableName) => inspectSqliteTable(database.path, tableName).exists),
|
|
974
977
|
columns,
|
|
975
978
|
hasDynamicTools: Boolean(dynamicTools?.exists),
|
|
976
979
|
hasSpawnEdges: Boolean(spawn?.exists),
|
|
@@ -2408,6 +2411,9 @@ export async function fingerprintSessionDeletion({ plan, scope, store }) {
|
|
|
2408
2411
|
|
|
2409
2412
|
for (const database of store.stateDatabases ?? [{ path: store.stateDatabasePath }]) {
|
|
2410
2413
|
fingerprintRowsForIds(hash, database.path, "threads", "id", plan.ids);
|
|
2414
|
+
for (const tableName of stateSchema(database).attachmentTables) {
|
|
2415
|
+
fingerprintRowsForIds(hash, database.path, tableName, "thread_id", plan.ids);
|
|
2416
|
+
}
|
|
2411
2417
|
if (stateSchema(database).hasDynamicTools) {
|
|
2412
2418
|
fingerprintRowsForIds(hash, database.path, "thread_dynamic_tools", "thread_id", plan.ids);
|
|
2413
2419
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
|
|
4
|
+
import packageMetadata from "../package.json" with { type: "json" };
|
|
5
|
+
import { getCommandInvocation } from "./platform.mjs";
|
|
6
|
+
|
|
7
|
+
const PACKAGE_SPEC = `${packageMetadata.name}@latest`;
|
|
8
|
+
const INSTALL_ARGS = ["install", "--global", PACKAGE_SPEC];
|
|
9
|
+
const VERSION_ARGS = ["list", "--global", "--depth=0", "--json", packageMetadata.name];
|
|
10
|
+
|
|
11
|
+
async function runCommand(command, args, { captureStdout = false, platform, stderr, stdout }) {
|
|
12
|
+
const invocation = getCommandInvocation(command, args, { platform });
|
|
13
|
+
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
16
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
17
|
+
windowsHide: invocation.windowsHide,
|
|
18
|
+
});
|
|
19
|
+
let errorOutput = "";
|
|
20
|
+
let output = "";
|
|
21
|
+
|
|
22
|
+
child.stdout.on("data", (chunk) => {
|
|
23
|
+
if (captureStdout) output = (output + chunk.toString()).slice(-16_384);
|
|
24
|
+
else stdout.write(chunk);
|
|
25
|
+
});
|
|
26
|
+
child.stderr.on("data", (chunk) => {
|
|
27
|
+
stderr.write(chunk);
|
|
28
|
+
errorOutput = (errorOutput + chunk.toString()).slice(-8_192);
|
|
29
|
+
});
|
|
30
|
+
child.on("error", reject);
|
|
31
|
+
child.on("close", (exitCode) => resolve({ exitCode: exitCode ?? 1, errorOutput, output }));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function confirmSudo({ stdin, stdout }) {
|
|
36
|
+
const prompt = createInterface({ input: stdin, output: stdout });
|
|
37
|
+
try {
|
|
38
|
+
const answer = await prompt.question("Retry the update with sudo? [y/N] ");
|
|
39
|
+
return /^(?:y|yes)$/iu.test(answer.trim());
|
|
40
|
+
} finally {
|
|
41
|
+
prompt.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function updateSessionSteward({
|
|
46
|
+
confirm = confirmSudo,
|
|
47
|
+
currentVersion = packageMetadata.version,
|
|
48
|
+
platform = process.platform,
|
|
49
|
+
run = runCommand,
|
|
50
|
+
stdin = process.stdin,
|
|
51
|
+
stdout = process.stdout,
|
|
52
|
+
stderr = process.stderr,
|
|
53
|
+
} = {}) {
|
|
54
|
+
stdout.write(`Current version: ${currentVersion}\nUpdating Session Steward with npm...\n`);
|
|
55
|
+
let result = await run("npm", INSTALL_ARGS, { platform, stderr, stdout });
|
|
56
|
+
let usedSudo = false;
|
|
57
|
+
|
|
58
|
+
if (result.exitCode !== 0 && /\b(?:EACCES|EPERM)\b/u.test(result.errorOutput)) {
|
|
59
|
+
if (platform === "win32") {
|
|
60
|
+
stderr.write("Global npm install needs permission. Open an elevated terminal and run session-steward update.\n");
|
|
61
|
+
} else if (stdin.isTTY && stdout.isTTY) {
|
|
62
|
+
stdout.write("The global npm install needs elevated permission.\n");
|
|
63
|
+
if (await confirm({ stdin, stdout })) {
|
|
64
|
+
usedSudo = true;
|
|
65
|
+
result = await run("sudo", ["npm", ...INSTALL_ARGS], { platform, stderr, stdout });
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
stderr.write("Global npm install needs elevated permission. Run sudo session-steward update in a terminal.\n");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (result.exitCode === 0) {
|
|
73
|
+
let installedVersion;
|
|
74
|
+
try {
|
|
75
|
+
const versionResult = await run(
|
|
76
|
+
usedSudo ? "sudo" : "npm",
|
|
77
|
+
usedSudo ? ["npm", ...VERSION_ARGS] : VERSION_ARGS,
|
|
78
|
+
{ captureStdout: true, platform, stderr, stdout },
|
|
79
|
+
);
|
|
80
|
+
if (versionResult.exitCode === 0) {
|
|
81
|
+
installedVersion = JSON.parse(versionResult.output)?.dependencies?.[packageMetadata.name]?.version;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Installation succeeded, but npm could not confirm the installed version.
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof installedVersion === "string" && /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.+-]+)?$/u.test(installedVersion)) {
|
|
88
|
+
stdout.write(`Updated version: ${installedVersion}\n`);
|
|
89
|
+
} else {
|
|
90
|
+
stderr.write("Update completed, but the installed version could not be confirmed. Run session-steward --version.\n");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result.exitCode;
|
|
95
|
+
}
|
package/lib/update-check.mjs
CHANGED
|
@@ -80,6 +80,6 @@ export async function findAvailableUpdate({
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export function formatUpdateNotice({ latestVersion
|
|
84
|
-
return `Session Steward ${latestVersion} is available. Update with:
|
|
83
|
+
export function formatUpdateNotice({ latestVersion }) {
|
|
84
|
+
return `Session Steward ${latestVersion} is available. Update with: session-steward update`;
|
|
85
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "session-steward",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"mcpName": "io.github.mallikcheripally/session-steward",
|
|
5
5
|
"description": "Codex and Claude Code session manager - browse and delete old sessions. Browser UI + CLI + MCP",
|
|
6
6
|
"license": "MIT",
|