multiclaw 0.8.2 → 0.10.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/commands/prune.d.ts +2 -2
- package/dist/commands/sessions.d.ts +13 -0
- package/dist/commands/tui.d.ts +4 -0
- package/dist/index.js +136 -32
- package/package.json +3 -3
package/dist/commands/prune.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface PruneOptions {
|
|
2
2
|
config?: string;
|
|
3
|
-
|
|
3
|
+
sessions?: boolean;
|
|
4
4
|
runs?: boolean;
|
|
5
5
|
all?: boolean;
|
|
6
6
|
dryRun?: boolean;
|
|
@@ -11,7 +11,7 @@ export interface PruneItem {
|
|
|
11
11
|
label: string;
|
|
12
12
|
path: string;
|
|
13
13
|
size: number;
|
|
14
|
-
kind: "
|
|
14
|
+
kind: "session" | "run" | "all-runs";
|
|
15
15
|
}
|
|
16
16
|
/** Scan `.multiclaw/` and list prune-able items with their sizes. */
|
|
17
17
|
export declare function scanPruneItems(workDir: string): PruneItem[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SessionsCommandOptions {
|
|
2
|
+
config?: string;
|
|
3
|
+
}
|
|
4
|
+
/** `multiclaw sessions ls` — list saved sessions, newest first. */
|
|
5
|
+
export declare function sessionsListCommand(options: SessionsCommandOptions): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* `multiclaw sessions rm <id>` — delete a session by id or a unique prefix.
|
|
8
|
+
*
|
|
9
|
+
* A prefix is accepted because the ids are UUIDs no one types in full. An
|
|
10
|
+
* ambiguous prefix is refused rather than guessed, so a `rm` never deletes a
|
|
11
|
+
* session the user did not mean.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sessionsRemoveCommand(id: string, options: SessionsCommandOptions): Promise<void>;
|
package/dist/commands/tui.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export interface TuiCommandOptions {
|
|
2
2
|
config?: string;
|
|
3
|
+
/** Start a fresh session instead of continuing the most recent one. */
|
|
4
|
+
new?: boolean;
|
|
5
|
+
/** Resume a session: a specific id, or `true` to open the picker. */
|
|
6
|
+
resume?: string | boolean;
|
|
3
7
|
}
|
|
4
8
|
export declare function tuiCommand(options: TuiCommandOptions): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname5, join as join6 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -475,24 +475,28 @@ import { checkbox as checkbox2, confirm } from "@inquirer/prompts";
|
|
|
475
475
|
function scanPruneItems(workDir) {
|
|
476
476
|
const items = [];
|
|
477
477
|
const multiclawDir = path3.join(workDir, ".multiclaw");
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
478
|
+
const sessionsDir = path3.join(multiclawDir, "sessions");
|
|
479
|
+
const index = readSessionIndex(sessionsDir);
|
|
480
|
+
const metaById = new Map(index.map((meta) => [meta.id, meta]));
|
|
481
|
+
const onDisk = fs3.existsSync(sessionsDir) ? fs3.readdirSync(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
482
|
+
const sessionItems2 = [];
|
|
483
|
+
for (const id of /* @__PURE__ */ new Set([...metaById.keys(), ...onDisk])) {
|
|
484
|
+
const dir = path3.join(sessionsDir, id);
|
|
485
|
+
const size = fs3.existsSync(dir) ? dirSize(dir) : 0;
|
|
486
|
+
const meta = metaById.get(id);
|
|
487
|
+
const title = meta?.title && meta.title.trim() !== "" ? meta.title : "(untitled)";
|
|
488
|
+
const turns = meta?.turnCount ?? 0;
|
|
489
|
+
sessionItems2.push({
|
|
490
|
+
id,
|
|
491
|
+
label: `${title} (${turns} turn${turns === 1 ? "" : "s"}, ${formatBytes(size)})`,
|
|
492
|
+
path: dir,
|
|
493
|
+
size,
|
|
494
|
+
kind: "session",
|
|
495
|
+
lastActiveAt: meta?.lastActiveAt ?? 0
|
|
494
496
|
});
|
|
495
497
|
}
|
|
498
|
+
sessionItems2.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
|
|
499
|
+
for (const { lastActiveAt: _unused, ...item } of sessionItems2) items.push(item);
|
|
496
500
|
const runsDir = path3.join(multiclawDir, "runs");
|
|
497
501
|
if (fs3.existsSync(runsDir)) {
|
|
498
502
|
const entries = fs3.readdirSync(runsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("run-")).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -527,8 +531,9 @@ function deletePruneItems(items) {
|
|
|
527
531
|
const failed = [];
|
|
528
532
|
for (const item of items) {
|
|
529
533
|
try {
|
|
530
|
-
if (item.kind === "
|
|
531
|
-
fs3.
|
|
534
|
+
if (item.kind === "session") {
|
|
535
|
+
fs3.rmSync(item.path, { recursive: true, force: true });
|
|
536
|
+
removeSessionFromIndex(path3.dirname(item.path), path3.basename(item.path));
|
|
532
537
|
deleted.push(item.id);
|
|
533
538
|
} else if (item.kind === "run") {
|
|
534
539
|
fs3.rmSync(item.path, { recursive: true, force: true });
|
|
@@ -555,11 +560,11 @@ function resolvePruneTargets(options, items) {
|
|
|
555
560
|
if (options.all) {
|
|
556
561
|
return items.map((i) => i.id);
|
|
557
562
|
}
|
|
558
|
-
if (options.
|
|
563
|
+
if (options.sessions && options.runs) {
|
|
559
564
|
return items.map((i) => i.id);
|
|
560
565
|
}
|
|
561
|
-
if (options.
|
|
562
|
-
return items.filter((i) => i.kind === "
|
|
566
|
+
if (options.sessions) {
|
|
567
|
+
return items.filter((i) => i.kind === "session").map((i) => i.id);
|
|
563
568
|
}
|
|
564
569
|
if (options.runs) {
|
|
565
570
|
return items.filter((i) => i.kind === "run" || i.kind === "all-runs").map((i) => i.id);
|
|
@@ -570,7 +575,7 @@ async function pruneCommand(options) {
|
|
|
570
575
|
const config = await loadConfig(options.config);
|
|
571
576
|
const items = scanPruneItems(config.workDir);
|
|
572
577
|
if (items.length === 0) {
|
|
573
|
-
console.log("Nothing to prune \u2014 no
|
|
578
|
+
console.log("Nothing to prune \u2014 no sessions or runs found.");
|
|
574
579
|
return;
|
|
575
580
|
}
|
|
576
581
|
let selectedIds;
|
|
@@ -635,6 +640,46 @@ async function pruneCommand(options) {
|
|
|
635
640
|
process.exitCode = 1;
|
|
636
641
|
}
|
|
637
642
|
}
|
|
643
|
+
function readSessionIndex(sessionsDir) {
|
|
644
|
+
try {
|
|
645
|
+
const parsed = JSON.parse(
|
|
646
|
+
fs3.readFileSync(path3.join(sessionsDir, "index.json"), "utf-8")
|
|
647
|
+
);
|
|
648
|
+
if (!Array.isArray(parsed)) return [];
|
|
649
|
+
const entries = [];
|
|
650
|
+
for (const value of parsed) {
|
|
651
|
+
if (typeof value !== "object" || value === null) continue;
|
|
652
|
+
const entry = value;
|
|
653
|
+
if (typeof entry.id !== "string") continue;
|
|
654
|
+
entries.push({
|
|
655
|
+
id: entry.id,
|
|
656
|
+
title: typeof entry.title === "string" ? entry.title : "",
|
|
657
|
+
turnCount: typeof entry.turnCount === "number" ? entry.turnCount : 0,
|
|
658
|
+
lastActiveAt: typeof entry.lastActiveAt === "number" ? entry.lastActiveAt : 0
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return entries;
|
|
662
|
+
} catch {
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function removeSessionFromIndex(sessionsDir, id) {
|
|
667
|
+
const indexPath = path3.join(sessionsDir, "index.json");
|
|
668
|
+
let parsed;
|
|
669
|
+
try {
|
|
670
|
+
parsed = JSON.parse(fs3.readFileSync(indexPath, "utf-8"));
|
|
671
|
+
} catch {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (!Array.isArray(parsed)) return;
|
|
675
|
+
const next = parsed.filter(
|
|
676
|
+
(entry) => !(typeof entry === "object" && entry !== null && entry.id === id)
|
|
677
|
+
);
|
|
678
|
+
if (next.length === parsed.length) return;
|
|
679
|
+
const tmp = `${indexPath}.tmp`;
|
|
680
|
+
fs3.writeFileSync(tmp, JSON.stringify(next), "utf-8");
|
|
681
|
+
fs3.renameSync(tmp, indexPath);
|
|
682
|
+
}
|
|
638
683
|
function formatBytes(bytes) {
|
|
639
684
|
if (bytes === 0) return "0 B";
|
|
640
685
|
const k = 1024;
|
|
@@ -997,9 +1042,9 @@ function resumeOptionsFromReport(report, requirementOverride) {
|
|
|
997
1042
|
if (agents2.length === 0) {
|
|
998
1043
|
throw new Error("The previous run has no agent results \u2014 nothing to resume");
|
|
999
1044
|
}
|
|
1000
|
-
const
|
|
1045
|
+
const sessions2 = {};
|
|
1001
1046
|
for (const result of latest.values()) {
|
|
1002
|
-
if (result.sessionId)
|
|
1047
|
+
if (result.sessionId) sessions2[result.agentId] = result.sessionId;
|
|
1003
1048
|
}
|
|
1004
1049
|
const requirement = requirementOverride ?? report.requirement;
|
|
1005
1050
|
if (requirement === void 0) {
|
|
@@ -1011,7 +1056,7 @@ function resumeOptionsFromReport(report, requirementOverride) {
|
|
|
1011
1056
|
agents: agents2,
|
|
1012
1057
|
// Absent, not `{}`: core reads a missing `sessions` as "start every agent
|
|
1013
1058
|
// fresh", which is exactly the single-agent retry of a failed agent.
|
|
1014
|
-
...Object.keys(
|
|
1059
|
+
...Object.keys(sessions2).length > 0 ? { sessions: sessions2 } : {}
|
|
1015
1060
|
}
|
|
1016
1061
|
};
|
|
1017
1062
|
}
|
|
@@ -1128,13 +1173,67 @@ function walkFiles(root, visit) {
|
|
|
1128
1173
|
}
|
|
1129
1174
|
}
|
|
1130
1175
|
|
|
1176
|
+
// src/commands/sessions.ts
|
|
1177
|
+
function sessionItems(workDir) {
|
|
1178
|
+
return scanPruneItems(workDir).filter((item) => item.kind === "session");
|
|
1179
|
+
}
|
|
1180
|
+
async function sessionsListCommand(options) {
|
|
1181
|
+
const config = await loadConfig(options.config);
|
|
1182
|
+
const items = sessionItems(config.workDir);
|
|
1183
|
+
if (items.length === 0) {
|
|
1184
|
+
console.log("No saved sessions.");
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
for (const item of items) {
|
|
1188
|
+
console.log(` \x1B[2m${item.id}\x1B[0m ${item.label}`);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
async function sessionsRemoveCommand(id, options) {
|
|
1192
|
+
const config = await loadConfig(options.config);
|
|
1193
|
+
const items = sessionItems(config.workDir);
|
|
1194
|
+
const exact = items.find((item) => item.id === id);
|
|
1195
|
+
const matches = exact ? [exact] : items.filter((item) => item.id.startsWith(id));
|
|
1196
|
+
if (matches.length === 0) {
|
|
1197
|
+
console.error(`No session matching "${id}".`);
|
|
1198
|
+
process.exitCode = 1;
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (matches.length > 1) {
|
|
1202
|
+
console.error(
|
|
1203
|
+
`"${id}" matches ${matches.length} sessions \u2014 use more of the id:
|
|
1204
|
+
${matches.map((m) => ` ${m.id}`).join("\n")}`
|
|
1205
|
+
);
|
|
1206
|
+
process.exitCode = 1;
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
const { deleted, failed } = deletePruneItems(matches);
|
|
1210
|
+
if (deleted.length > 0) {
|
|
1211
|
+
console.log(`\x1B[32m\u2713\x1B[0m Removed session ${matches[0].id}`);
|
|
1212
|
+
}
|
|
1213
|
+
if (failed.length > 0) {
|
|
1214
|
+
console.error(`\x1B[31m\u2717\x1B[0m Failed to remove ${matches[0].id}: ${failed[0].error}`);
|
|
1215
|
+
process.exitCode = 1;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1131
1219
|
// src/commands/tui.ts
|
|
1220
|
+
function initialSession(options) {
|
|
1221
|
+
if (options.new) return { kind: "new" };
|
|
1222
|
+
if (options.resume !== void 0) {
|
|
1223
|
+
return {
|
|
1224
|
+
kind: "resume",
|
|
1225
|
+
...typeof options.resume === "string" ? { id: options.resume } : {}
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
return void 0;
|
|
1229
|
+
}
|
|
1132
1230
|
async function tuiCommand(options) {
|
|
1133
1231
|
const config = await loadConfig(options.config);
|
|
1134
1232
|
await assertRuntimesAvailable(config);
|
|
1135
1233
|
const { startTui } = await import("@multiclawcli/dashboard/tui");
|
|
1136
1234
|
await startTui({
|
|
1137
1235
|
config,
|
|
1236
|
+
initialSession: initialSession(options),
|
|
1138
1237
|
onTurnStart: (eventBus, logDir) => {
|
|
1139
1238
|
const writer = new FileLogWriter(logDir);
|
|
1140
1239
|
const detach = writer.attach(eventBus);
|
|
@@ -1152,7 +1251,7 @@ async function tuiCommand(options) {
|
|
|
1152
1251
|
// src/commands/upgrade.ts
|
|
1153
1252
|
import { execSync } from "child_process";
|
|
1154
1253
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1155
|
-
import { dirname as
|
|
1254
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
1156
1255
|
import { fileURLToPath } from "url";
|
|
1157
1256
|
var PACKAGE_NAME = "multiclaw";
|
|
1158
1257
|
var RED2 = "\x1B[31m";
|
|
@@ -1171,7 +1270,7 @@ function findPackageRoot(start) {
|
|
|
1171
1270
|
let dir = resolve4(start);
|
|
1172
1271
|
for (; ; ) {
|
|
1173
1272
|
if (readManifest(dir)?.name === PACKAGE_NAME) return dir;
|
|
1174
|
-
const parent =
|
|
1273
|
+
const parent = dirname4(dir);
|
|
1175
1274
|
if (parent === dir) return void 0;
|
|
1176
1275
|
dir = parent;
|
|
1177
1276
|
}
|
|
@@ -1202,7 +1301,7 @@ var MANUAL = {
|
|
|
1202
1301
|
unknown: "npm install -g multiclaw@latest"
|
|
1203
1302
|
};
|
|
1204
1303
|
function createUpgradeContext() {
|
|
1205
|
-
const pkgRoot = findPackageRoot(
|
|
1304
|
+
const pkgRoot = findPackageRoot(dirname4(fileURLToPath(import.meta.url)));
|
|
1206
1305
|
if (!pkgRoot) return void 0;
|
|
1207
1306
|
const currentVersion = readInstalledVersion(pkgRoot);
|
|
1208
1307
|
if (!currentVersion) return void 0;
|
|
@@ -1275,10 +1374,12 @@ async function upgradeCommand() {
|
|
|
1275
1374
|
|
|
1276
1375
|
// src/index.ts
|
|
1277
1376
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1278
|
-
var __dirname2 =
|
|
1377
|
+
var __dirname2 = dirname5(__filename2);
|
|
1279
1378
|
var pkg = JSON.parse(readFileSync4(join6(__dirname2, "../package.json"), "utf-8"));
|
|
1280
1379
|
var program = new Command();
|
|
1281
|
-
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").
|
|
1380
|
+
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--new", "Start a new session instead of continuing the most recent").option("-r, --resume [id]", "Resume a session by id, or open the session picker").action(
|
|
1381
|
+
(options) => tuiCommand(options)
|
|
1382
|
+
);
|
|
1282
1383
|
program.command("init").description("Scaffold a config file (interactive)").action(() => initCommand());
|
|
1283
1384
|
program.command("run [requirement]").description("Run an orchestration headlessly").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--no-leader", "Batch mode only: skip routing and run the full team").option("--dry-run", "Preview the routed plan without running the pipeline").option(
|
|
1284
1385
|
"--resume [path]",
|
|
@@ -1286,7 +1387,10 @@ program.command("run [requirement]").description("Run an orchestration headlessl
|
|
|
1286
1387
|
).action((requirement, opts) => runCommand(requirement, opts));
|
|
1287
1388
|
program.command("upgrade").description("Upgrade multiclaw to the latest version").action(() => upgradeCommand());
|
|
1288
1389
|
program.command("doctor").description("Check which agent providers are installed and ready").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((opts) => doctorCommand(opts.config));
|
|
1289
|
-
program.command("prune").description("Clean up
|
|
1390
|
+
program.command("prune").description("Clean up saved sessions and old run directories (interactive)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").option("--sessions", "Prune saved sessions only (non-interactive)").option("--runs", "Prune all runs only (non-interactive)").option("--all", "Prune everything (non-interactive)").option("--dry-run", "Show what would be deleted without deleting").option("--yes", "Skip confirmation prompt").action((opts) => pruneCommand(opts));
|
|
1391
|
+
var sessions = program.command("sessions").description("Manage saved console sessions");
|
|
1392
|
+
sessions.command("ls").description("List saved sessions, newest first").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((opts) => sessionsListCommand(opts));
|
|
1393
|
+
sessions.command("rm <id>").description("Delete a saved session by id (or a unique prefix)").option("--config <path>", "Config file path (default: auto-discover ./multiclaw.config.ts)").action((id, opts) => sessionsRemoveCommand(id, opts));
|
|
1290
1394
|
program.parseAsync(process.argv).catch((err) => {
|
|
1291
1395
|
const message = err instanceof Error ? err.message : String(err);
|
|
1292
1396
|
console.error(`\x1B[31mError: ${message}\x1B[0m`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"multiclaw": "dist/index.js"
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@agentclientprotocol/claude-agent-acp": "^0.95.0",
|
|
31
31
|
"@agentclientprotocol/codex-acp": "^2.2.2",
|
|
32
32
|
"@inquirer/prompts": "^8.7.4",
|
|
33
|
-
"@multiclawcli/core": "0.
|
|
34
|
-
"@multiclawcli/dashboard": "0.
|
|
33
|
+
"@multiclawcli/core": "0.10.0",
|
|
34
|
+
"@multiclawcli/dashboard": "0.10.0",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|