multiclaw 0.4.0 → 0.5.1
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/doctor.d.ts +32 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/init.d.ts +32 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/resume.d.ts +32 -0
- package/dist/commands/resume.test.d.ts +1 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.test.d.ts +1 -0
- package/dist/commands/tui.d.ts +4 -0
- package/dist/commands/upgrade.d.ts +30 -0
- package/dist/commands/upgrade.test.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +390 -54
- package/dist/lib.d.ts +7 -8
- package/dist/lib.test.d.ts +1 -0
- package/dist/loader/loadConfig.d.ts +2 -0
- package/dist/loader/loadConfig.test.d.ts +1 -0
- package/dist/reporters/ConsoleReporter.d.ts +5 -0
- package/dist/reporters/ConsoleReporter.test.d.ts +1 -0
- package/dist/reporters/FileLogWriter.d.ts +29 -0
- package/dist/reporters/FileLogWriter.test.d.ts +1 -0
- package/package.json +8 -8
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type DetectedProvider, type ModelSelectionMode, type MultiClawConfig, type ProviderSource, type ProviderStatus } from "@multiclawcli/core";
|
|
2
|
+
export interface DoctorProviderRow {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
status: ProviderStatus;
|
|
6
|
+
command: string;
|
|
7
|
+
source?: ProviderSource;
|
|
8
|
+
resolvedPath?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
modelCount: number;
|
|
11
|
+
modelSelection: ModelSelectionMode;
|
|
12
|
+
authHint: string;
|
|
13
|
+
installHint?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorReport {
|
|
16
|
+
providers: DoctorProviderRow[];
|
|
17
|
+
installedCount: number;
|
|
18
|
+
config?: {
|
|
19
|
+
name?: string;
|
|
20
|
+
defaultsProvider?: string;
|
|
21
|
+
defaultsProviderOk: boolean;
|
|
22
|
+
agentsWithMissingProvider: string[];
|
|
23
|
+
agentsWithUnsupportedTools: string[];
|
|
24
|
+
};
|
|
25
|
+
runnable: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Build the diagnostic model from detection results and an optional config. */
|
|
28
|
+
export declare function buildDoctorReport(detected: DetectedProvider[], config?: MultiClawConfig): DoctorReport;
|
|
29
|
+
/** Render the report as human-readable text for stdout. */
|
|
30
|
+
export declare function formatDoctorReport(report: DoctorReport, useColor?: boolean): string;
|
|
31
|
+
/** Run the `doctor` command: detect providers, print the report, set exit code. */
|
|
32
|
+
export declare function doctorCommand(configPath?: string): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agents each preset actually contains, by their `agents.<id>()` name.
|
|
3
|
+
*
|
|
4
|
+
* Kept adjacent to {@link AGENT_BLOCKS} because the two must stay in step: the
|
|
5
|
+
* override checkbox offers exactly these ids, and the regex that injects an
|
|
6
|
+
* override only matches an id that really appears in the block. A mismatch is a
|
|
7
|
+
* silently dropped override, which is what this list exists to prevent. The
|
|
8
|
+
* `init.test.ts` consistency check asserts every id here has a matching
|
|
9
|
+
* `agents.<id>(` in the block string.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PRESET_AGENT_IDS: Record<string, string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* The override choices for a preset: the leader (which belongs to no preset)
|
|
14
|
+
* plus the preset's own agents. Filtering by preset is what stops an override
|
|
15
|
+
* for an agent the preset does not contain from being offered and then dropped.
|
|
16
|
+
*/
|
|
17
|
+
export declare function overrideChoices(preset: string): Array<{
|
|
18
|
+
value: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function generateConfig(opts: {
|
|
22
|
+
preset: string;
|
|
23
|
+
workDir: string;
|
|
24
|
+
projectName: string;
|
|
25
|
+
provider: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
overrides?: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
model: string;
|
|
30
|
+
}>;
|
|
31
|
+
}): string;
|
|
32
|
+
export declare function initCommand(): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MultiClawConfig, OrchestratorResult } from "@multiclawcli/core";
|
|
2
|
+
/** A previous run's report, as much of it as resume needs. */
|
|
3
|
+
export interface ResumeTarget {
|
|
4
|
+
runDir: string;
|
|
5
|
+
report: OrchestratorResult;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve `--resume` into the run directory it names.
|
|
9
|
+
*
|
|
10
|
+
* `true` (a bare `--resume`) and `"latest"` both mean "the newest run". Run
|
|
11
|
+
* directories are named `run-<ISO stamp>` with a fixed-width stamp, so a plain
|
|
12
|
+
* lexicographic maximum is the most recent one — deterministic, and testable
|
|
13
|
+
* without a clock.
|
|
14
|
+
*
|
|
15
|
+
* Any other value is treated as a path: either the run directory itself or its
|
|
16
|
+
* `logs/` subdirectory, since that is where the report lives and a caller may
|
|
17
|
+
* well point at it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveRunDir(runsRoot: string, target: string | boolean): string;
|
|
20
|
+
/** Read a run's `report.json`. */
|
|
21
|
+
export declare function readReport(runDir: string): OrchestratorResult;
|
|
22
|
+
/**
|
|
23
|
+
* Turn a report (plus an optional requirement override) into run config.
|
|
24
|
+
*
|
|
25
|
+
* The requirement is the one thing a report cannot always supply — an older
|
|
26
|
+
* report predates the field — so an explicit override wins, and its absence on
|
|
27
|
+
* both sides is an error rather than a silently empty run.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resumeOptionsFromReport(report: OrchestratorResult, requirementOverride?: string): {
|
|
30
|
+
requirement: string;
|
|
31
|
+
resume: NonNullable<MultiClawConfig["resume"]>;
|
|
32
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface RunOptions {
|
|
2
|
+
config?: string;
|
|
3
|
+
noLeader?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* `true` for a bare `--resume` (latest run), or a path to a specific run.
|
|
6
|
+
* Commander only ever produces `undefined`, `true` or a string, so `false` is
|
|
7
|
+
* not part of the domain.
|
|
8
|
+
*/
|
|
9
|
+
resume?: string | true;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function runCommand(requirementArg: string | undefined, options: RunOptions): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Which package manager owns the copy of multiclaw that is currently running. */
|
|
2
|
+
export type Installer = "npm" | "pnpm" | "bun" | "yarn" | "unknown";
|
|
3
|
+
/** Walk up from `start` to the package.json that declares this package. */
|
|
4
|
+
export declare function findPackageRoot(start: string): string | undefined;
|
|
5
|
+
/** Read the installed version straight from disk (no module cache to go stale). */
|
|
6
|
+
export declare function readInstalledVersion(pkgRoot: string): string | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Identify the installer from the on-disk location of the running package.
|
|
9
|
+
*
|
|
10
|
+
* This is the whole point of the command: `npm install -g` writes to npm's
|
|
11
|
+
* prefix, which is not necessarily where the binary on PATH came from. A user
|
|
12
|
+
* with a pnpm global install used to get a silent no-op upgrade — npm installed
|
|
13
|
+
* a fresh copy somewhere else while `multiclaw --version` kept reporting the
|
|
14
|
+
* pnpm one. Upgrading must therefore target the manager that owns this copy.
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectInstaller(pkgRoot: string): Installer;
|
|
17
|
+
type Exec = (command: string, mode: "capture" | "inherit") => string;
|
|
18
|
+
export interface UpgradeContext {
|
|
19
|
+
pkgRoot: string;
|
|
20
|
+
currentVersion: string;
|
|
21
|
+
installer: Installer;
|
|
22
|
+
/** Re-read after installing: the on-disk copy is what `--version` reports. */
|
|
23
|
+
readVersion: (pkgRoot: string) => string | undefined;
|
|
24
|
+
exec: Exec;
|
|
25
|
+
}
|
|
26
|
+
/** Resolve the running install. `undefined` means it is not an installed copy. */
|
|
27
|
+
export declare function createUpgradeContext(): UpgradeContext | undefined;
|
|
28
|
+
export declare function runUpgrade(ctx: UpgradeContext): Promise<void>;
|
|
29
|
+
export declare function upgradeCommand(): Promise<void>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import { dirname as
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
4
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
5
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/doctor.ts
|
|
10
10
|
import {
|
|
11
11
|
detectProviders,
|
|
12
12
|
presentProviders,
|
|
13
|
+
resolveCommand,
|
|
13
14
|
resolveProvider,
|
|
15
|
+
specForCommand,
|
|
14
16
|
specForProvider
|
|
15
17
|
} from "@multiclawcli/core";
|
|
16
18
|
|
|
@@ -96,6 +98,8 @@ function buildDoctorReport(detected, config) {
|
|
|
96
98
|
label: d.label,
|
|
97
99
|
status: d.status,
|
|
98
100
|
command: d.command,
|
|
101
|
+
source: d.source,
|
|
102
|
+
resolvedPath: d.resolvedPath,
|
|
99
103
|
version: d.version,
|
|
100
104
|
modelCount: spec?.models.length ?? 0,
|
|
101
105
|
modelSelection: spec?.modelSelection ?? "none",
|
|
@@ -111,11 +115,17 @@ function buildDoctorReport(detected, config) {
|
|
|
111
115
|
const agents2 = [...config.agents];
|
|
112
116
|
if (config.leader) agents2.push(config.leader);
|
|
113
117
|
const agentsWithMissingProvider = agents2.map((a) => ({ id: a.id, intended: resolveProvider(a, config) })).filter((a) => !present.includes(a.intended)).map((a) => a.id);
|
|
118
|
+
const agentsWithUnsupportedTools = agents2.map((a) => {
|
|
119
|
+
const command = resolveCommand(a, config);
|
|
120
|
+
const spec = specForCommand(command) ?? specForProvider(resolveProvider(a, config));
|
|
121
|
+
return { id: a.id, spec, tools: a.tools };
|
|
122
|
+
}).filter((a) => a.spec && !a.spec.tools && a.tools !== void 0).map((a) => a.id);
|
|
114
123
|
configReport = {
|
|
115
124
|
name: config.name,
|
|
116
125
|
defaultsProvider,
|
|
117
126
|
defaultsProviderOk,
|
|
118
|
-
agentsWithMissingProvider
|
|
127
|
+
agentsWithMissingProvider,
|
|
128
|
+
agentsWithUnsupportedTools
|
|
119
129
|
};
|
|
120
130
|
}
|
|
121
131
|
return {
|
|
@@ -145,16 +155,23 @@ function formatDoctorReport(report, useColor = true) {
|
|
|
145
155
|
lines.push("");
|
|
146
156
|
lines.push(c(DIM, "multiclaw doctor \u2014 environment check"));
|
|
147
157
|
lines.push("");
|
|
148
|
-
lines.push(
|
|
158
|
+
lines.push(
|
|
159
|
+
`${cell("provider", 12)}${cell("status", 10)}${cell("version", 14)}${cell("source", 9)}models auth`
|
|
160
|
+
);
|
|
149
161
|
for (const p of report.providers) {
|
|
150
162
|
const mark = p.status === "present" ? "\u2713 present" : "\u2717 missing";
|
|
151
163
|
const markCode = p.status === "present" ? GREEN : RED;
|
|
152
164
|
const version = p.version ?? (p.status === "present" ? "" : "\u2014");
|
|
165
|
+
const source = p.source ?? "\u2014";
|
|
166
|
+
const sourceCode = p.source === "bundled" ? YELLOW : void 0;
|
|
153
167
|
const models = p.modelSelection === "none" ? "n/a" : String(p.modelCount);
|
|
154
168
|
const auth = p.authHint ? c(DIM, p.authHint) : "";
|
|
155
169
|
lines.push(
|
|
156
|
-
`${cell(p.label, 12)}${cell(mark, 10, markCode)}${cell(version, 14)}${cell(models, 7)}${auth}`
|
|
170
|
+
`${cell(p.label, 12)}${cell(mark, 10, markCode)}${cell(version, 14)}${cell(source, 9, sourceCode)}${cell(models, 7)}${auth}`
|
|
157
171
|
);
|
|
172
|
+
if (p.status === "present" && p.source === "bundled" && p.resolvedPath) {
|
|
173
|
+
lines.push(c(DIM, ` bundled bridge: ${p.resolvedPath}`));
|
|
174
|
+
}
|
|
158
175
|
if (p.status === "missing" && p.installHint) {
|
|
159
176
|
lines.push(c(YELLOW, ` install: ${p.installHint}`));
|
|
160
177
|
}
|
|
@@ -178,6 +195,14 @@ function formatDoctorReport(report, useColor = true) {
|
|
|
178
195
|
} else {
|
|
179
196
|
lines.push(c(GREEN, " all agents resolve to an installed provider \u2713"));
|
|
180
197
|
}
|
|
198
|
+
if (report.config.agentsWithUnsupportedTools.length > 0) {
|
|
199
|
+
lines.push(
|
|
200
|
+
c(
|
|
201
|
+
YELLOW,
|
|
202
|
+
` ${report.config.agentsWithUnsupportedTools.length} agent(s) use a provider that cannot enforce tool allowlists: ${report.config.agentsWithUnsupportedTools.join(", ")}`
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
181
206
|
}
|
|
182
207
|
lines.push("");
|
|
183
208
|
if (report.runnable) {
|
|
@@ -244,21 +269,75 @@ var PRESET_DESCRIPTIONS = {
|
|
|
244
269
|
frontend: "Architect \u2192 UI Designer \u2192 UI Design Reviewer \u2192 Frontend Developer \u2192 Tester \u2192 Code Reviewer (6 agents)",
|
|
245
270
|
fullstack: "Product Manager \u2192 Architect \u2192 Arch Design Reviewer \u2192 Backend Developer + UI Designer \u2192 UI Design Reviewer \u2192 Frontend Developer \u2192 Tester \u2192 Code Reviewer + DevOps (10 agents)"
|
|
246
271
|
};
|
|
272
|
+
var PRESET_AGENT_IDS = {
|
|
273
|
+
backend: [
|
|
274
|
+
"productManager",
|
|
275
|
+
"architect",
|
|
276
|
+
"archDesignReviewer",
|
|
277
|
+
"backendDeveloper",
|
|
278
|
+
"tester",
|
|
279
|
+
"codeReviewer",
|
|
280
|
+
"devops"
|
|
281
|
+
],
|
|
282
|
+
frontend: [
|
|
283
|
+
"architect",
|
|
284
|
+
"uiDesigner",
|
|
285
|
+
"uiDesignReviewer",
|
|
286
|
+
"frontendDeveloper",
|
|
287
|
+
"tester",
|
|
288
|
+
"codeReviewer"
|
|
289
|
+
],
|
|
290
|
+
fullstack: [
|
|
291
|
+
"productManager",
|
|
292
|
+
"architect",
|
|
293
|
+
"archDesignReviewer",
|
|
294
|
+
"backendDeveloper",
|
|
295
|
+
"uiDesigner",
|
|
296
|
+
"uiDesignReviewer",
|
|
297
|
+
"frontendDeveloper",
|
|
298
|
+
"tester",
|
|
299
|
+
"codeReviewer",
|
|
300
|
+
"devops"
|
|
301
|
+
]
|
|
302
|
+
};
|
|
303
|
+
var OVERRIDE_LABELS = {
|
|
304
|
+
leader: "\u2605 Leader",
|
|
305
|
+
productManager: "Product Manager",
|
|
306
|
+
architect: "Architect",
|
|
307
|
+
archDesignReviewer: "Arch Design Reviewer",
|
|
308
|
+
backendDeveloper: "Backend Developer",
|
|
309
|
+
frontendDeveloper: "Frontend Developer",
|
|
310
|
+
uiDesigner: "UI Designer",
|
|
311
|
+
uiDesignReviewer: "UI Design Reviewer",
|
|
312
|
+
tester: "Tester",
|
|
313
|
+
codeReviewer: "Code Reviewer",
|
|
314
|
+
devops: "DevOps"
|
|
315
|
+
};
|
|
316
|
+
function overrideChoices(preset) {
|
|
317
|
+
const ids = PRESET_AGENT_IDS[preset] ?? [];
|
|
318
|
+
return [
|
|
319
|
+
{ value: "leader", name: OVERRIDE_LABELS.leader },
|
|
320
|
+
...ids.map((id) => ({ value: id, name: OVERRIDE_LABELS[id] ?? id }))
|
|
321
|
+
];
|
|
322
|
+
}
|
|
247
323
|
function generateConfig(opts) {
|
|
248
324
|
const name = JSON.stringify(opts.projectName);
|
|
249
325
|
const workDir = JSON.stringify(opts.workDir);
|
|
250
326
|
const defaultsParts = [`provider: ${JSON.stringify(opts.provider)}`];
|
|
251
327
|
if (opts.model) defaultsParts.push(`model: ${JSON.stringify(opts.model)}`);
|
|
328
|
+
const leaderOverride = opts.overrides?.find((o) => o.id === "leader");
|
|
329
|
+
const leaderLine = leaderOverride ? ` leader: agents.leader({ model: ${JSON.stringify(leaderOverride.model)} }),` : ` leader: agents.leader(),`;
|
|
252
330
|
let agentBlock = AGENT_BLOCKS[opts.preset];
|
|
253
331
|
if (opts.overrides && opts.overrides.length > 0) {
|
|
254
332
|
for (const ov of opts.overrides) {
|
|
255
333
|
const id = ov.id;
|
|
334
|
+
if (id === "leader") continue;
|
|
256
335
|
const model = JSON.stringify(ov.model);
|
|
257
336
|
const re = new RegExp(`(agents\\.${id}\\([^)]*\\))`, "g");
|
|
258
337
|
agentBlock = agentBlock.replace(re, (match) => {
|
|
259
|
-
const
|
|
260
|
-
const sep =
|
|
261
|
-
return `agents.${id}({ ${
|
|
338
|
+
const body = match.slice(id.length + 8, -1).trim().replace(/^\{/, "").replace(/\}$/, "").trim();
|
|
339
|
+
const sep = body ? ", " : "";
|
|
340
|
+
return `agents.${id}({ ${body}${sep}model: ${model} })`;
|
|
262
341
|
});
|
|
263
342
|
}
|
|
264
343
|
}
|
|
@@ -267,6 +346,7 @@ function generateConfig(opts) {
|
|
|
267
346
|
export default defineConfig({
|
|
268
347
|
name: ${name},
|
|
269
348
|
workDir: ${workDir},
|
|
349
|
+
${leaderLine}
|
|
270
350
|
context: {
|
|
271
351
|
projectName: ${name},
|
|
272
352
|
},
|
|
@@ -331,19 +411,7 @@ async function initCommand() {
|
|
|
331
411
|
const modelDefault = modelAnswer || void 0;
|
|
332
412
|
const overrideIds = await checkbox({
|
|
333
413
|
message: "Override model for specific agents? (space to select, enter to confirm)",
|
|
334
|
-
choices:
|
|
335
|
-
{ value: "leader", name: "\u2605 Leader" },
|
|
336
|
-
{ value: "productManager", name: "Product Manager" },
|
|
337
|
-
{ value: "architect", name: "Architect" },
|
|
338
|
-
{ value: "archDesignReviewer", name: "Arch Design Reviewer" },
|
|
339
|
-
{ value: "backendDeveloper", name: "Backend Developer" },
|
|
340
|
-
{ value: "frontendDeveloper", name: "Frontend Developer" },
|
|
341
|
-
{ value: "uiDesigner", name: "UI Designer" },
|
|
342
|
-
{ value: "uiDesignReviewer", name: "UI Design Reviewer" },
|
|
343
|
-
{ value: "tester", name: "Tester" },
|
|
344
|
-
{ value: "codeReviewer", name: "Code Reviewer" },
|
|
345
|
-
{ value: "devops", name: "DevOps" }
|
|
346
|
-
]
|
|
414
|
+
choices: overrideChoices(preset)
|
|
347
415
|
});
|
|
348
416
|
const overrides = [];
|
|
349
417
|
for (const id of overrideIds) {
|
|
@@ -406,14 +474,18 @@ async function initCommand() {
|
|
|
406
474
|
}
|
|
407
475
|
|
|
408
476
|
// src/commands/run.ts
|
|
409
|
-
import * as
|
|
410
|
-
import
|
|
477
|
+
import * as fs5 from "fs";
|
|
478
|
+
import * as path5 from "path";
|
|
479
|
+
import {
|
|
480
|
+
describeResumePlan,
|
|
481
|
+
Orchestrator as OrchestratorImpl
|
|
482
|
+
} from "@multiclawcli/core";
|
|
411
483
|
|
|
412
484
|
// src/lib.ts
|
|
413
485
|
import {
|
|
414
486
|
detectProviders as detectProviders3,
|
|
415
487
|
presentProviders as presentProviders2,
|
|
416
|
-
resolveCommand,
|
|
488
|
+
resolveCommand as resolveCommand2,
|
|
417
489
|
runtimeRegistry
|
|
418
490
|
} from "@multiclawcli/core";
|
|
419
491
|
import { agents, defineConfig } from "@multiclawcli/core";
|
|
@@ -426,7 +498,7 @@ async function assertRuntimesAvailable(config) {
|
|
|
426
498
|
console.error(`\x1B[31mError: Unknown runtime "${name}". Supported: ${supported}\x1B[0m`);
|
|
427
499
|
process.exit(1);
|
|
428
500
|
}
|
|
429
|
-
const command =
|
|
501
|
+
const command = resolveCommand2(agent, config);
|
|
430
502
|
const key = `${name}\0${command}`;
|
|
431
503
|
if (checked.has(key)) continue;
|
|
432
504
|
checked.add(key);
|
|
@@ -463,6 +535,11 @@ var C = isFancy ? {
|
|
|
463
535
|
red: "",
|
|
464
536
|
purple: ""
|
|
465
537
|
};
|
|
538
|
+
function markFor(status) {
|
|
539
|
+
if (status === "success") return { icon: S.ok, color: C.green };
|
|
540
|
+
if (status === "cancelled") return { icon: S.skip, color: C.yellow };
|
|
541
|
+
return { icon: S.fail, color: C.red };
|
|
542
|
+
}
|
|
466
543
|
var ConsoleReporter = class {
|
|
467
544
|
attach(eventBus) {
|
|
468
545
|
return eventBus.subscribe((e) => this.handle(e));
|
|
@@ -480,6 +557,10 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
480
557
|
`${C.cyan}${S.bullet} Agents: ${e.payload.totalAgents} Stages: ${e.payload.stages.length}${C.reset}
|
|
481
558
|
`
|
|
482
559
|
);
|
|
560
|
+
if (e.payload.selectionReason) {
|
|
561
|
+
console.log(`${C.dim}${S.bullet} Why: ${e.payload.selectionReason}${C.reset}
|
|
562
|
+
`);
|
|
563
|
+
}
|
|
483
564
|
break;
|
|
484
565
|
}
|
|
485
566
|
case "orchestration:stages": {
|
|
@@ -498,6 +579,7 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
498
579
|
break;
|
|
499
580
|
}
|
|
500
581
|
case "agent:output": {
|
|
582
|
+
if (e.payload.kind && e.payload.kind !== "text") break;
|
|
501
583
|
process.stdout.write(` ${e.payload.chunk}`);
|
|
502
584
|
break;
|
|
503
585
|
}
|
|
@@ -507,6 +589,10 @@ ${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
|
507
589
|
break;
|
|
508
590
|
}
|
|
509
591
|
case "agent:failed": {
|
|
592
|
+
if (e.payload.status === "cancelled") {
|
|
593
|
+
console.log(`${C.yellow}${S.skip} ${e.payload.agentName} cancelled${C.reset}`);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
510
596
|
console.log(`${C.red}${S.fail} ${e.payload.agentName} failed: ${e.payload.error}${C.reset}`);
|
|
511
597
|
break;
|
|
512
598
|
}
|
|
@@ -533,22 +619,33 @@ ${C.cyan}${mark} ${reply.message}${C.reset}`);
|
|
|
533
619
|
}
|
|
534
620
|
const line = (isFancy ? "\u2550" : "=").repeat(50);
|
|
535
621
|
const edge = isFancy ? ["\u2554", "\u2551", "\u255A", "\u2557", "\u255D"] : ["+", "|", "+", "+", "+"];
|
|
536
|
-
const title = reply ? reply.mode === "ask" ? `${S.warn} Needs your input` : `${S.ok} Answered` : success ? `${S.ok} All done` : `${S.warn} Done (with failures)`;
|
|
622
|
+
const title = reply ? reply.mode === "ask" ? `${S.warn} Needs your input` : `${S.ok} Answered` : e.payload.cancelled ? `${S.skip} Cancelled` : success ? `${S.ok} All done` : `${S.warn} Done (with failures)`;
|
|
537
623
|
console.log(`
|
|
538
624
|
${C.bold}${C.purple}${edge[0]}${line}${edge[3]}`);
|
|
539
625
|
console.log(`${edge[1]} ${title.padEnd(48)}${edge[1]}`);
|
|
540
626
|
console.log(`${edge[2]}${line}${edge[4]}${C.reset}`);
|
|
541
627
|
const nameWidth = Math.max(8, ...agentResults.map((r) => r.agentName.length));
|
|
542
628
|
const totalStr = `${(totalDuration / 1e3).toFixed(1)}s`;
|
|
543
|
-
|
|
629
|
+
const cost = e.payload.totalCostUsd;
|
|
630
|
+
const costStr = cost === void 0 ? "" : ` $${cost.toFixed(2)}`;
|
|
631
|
+
console.log(` ~ ${"Total".padEnd(nameWidth)} ${totalStr.padStart(7)}${costStr}`);
|
|
544
632
|
for (const r of agentResults) {
|
|
545
|
-
const icon = r.status
|
|
546
|
-
const color2 = r.status === "success" ? C.green : C.red;
|
|
633
|
+
const { icon, color: color2 } = markFor(r.status);
|
|
547
634
|
const dur = `${(r.duration / 1e3).toFixed(1)}s`;
|
|
548
635
|
console.log(
|
|
549
636
|
` ${color2}${icon}${C.reset} ${r.agentName.padEnd(nameWidth)} ${dur.padStart(7)}`
|
|
550
637
|
);
|
|
551
638
|
}
|
|
639
|
+
if (e.payload.deliverables && e.payload.deliverables.length > 0) {
|
|
640
|
+
console.log(`
|
|
641
|
+
Deliverables:`);
|
|
642
|
+
for (const file of e.payload.deliverables.slice(0, 5)) {
|
|
643
|
+
console.log(` ${S.bullet} ${file.path} (${file.status}, ${file.size} bytes)`);
|
|
644
|
+
}
|
|
645
|
+
if (e.payload.deliverables.length > 5) {
|
|
646
|
+
console.log(` ${S.bullet} \u2026 ${e.payload.deliverables.length - 5} more`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
552
649
|
console.log();
|
|
553
650
|
break;
|
|
554
651
|
}
|
|
@@ -587,7 +684,9 @@ var FileLogWriter = class {
|
|
|
587
684
|
}
|
|
588
685
|
handle(e) {
|
|
589
686
|
if (e.type === "agent:output") {
|
|
590
|
-
|
|
687
|
+
const { agentId, chunk, kind } = e.payload;
|
|
688
|
+
if (kind && kind !== "text") return;
|
|
689
|
+
this.getStream(agentId)?.write(chunk);
|
|
591
690
|
}
|
|
592
691
|
if (e.type === "orchestration:complete") {
|
|
593
692
|
try {
|
|
@@ -617,12 +716,12 @@ var FileLogWriter = class {
|
|
|
617
716
|
this.streams.clear();
|
|
618
717
|
return Promise.all(
|
|
619
718
|
streams.map(
|
|
620
|
-
(s) => new Promise((
|
|
719
|
+
(s) => new Promise((resolve5) => {
|
|
621
720
|
if (s.destroyed || s.writableEnded) {
|
|
622
|
-
|
|
721
|
+
resolve5();
|
|
623
722
|
return;
|
|
624
723
|
}
|
|
625
|
-
s.end(() =>
|
|
724
|
+
s.end(() => resolve5());
|
|
626
725
|
})
|
|
627
726
|
)
|
|
628
727
|
).then(() => void 0);
|
|
@@ -632,19 +731,108 @@ function errorMessage(err) {
|
|
|
632
731
|
return err instanceof Error ? err.message : String(err);
|
|
633
732
|
}
|
|
634
733
|
|
|
734
|
+
// src/commands/resume.ts
|
|
735
|
+
import * as fs4 from "fs";
|
|
736
|
+
import * as path4 from "path";
|
|
737
|
+
function resolveRunDir(runsRoot, target) {
|
|
738
|
+
if (typeof target !== "string" || target === "latest") {
|
|
739
|
+
return latestRunDir(runsRoot);
|
|
740
|
+
}
|
|
741
|
+
const resolved = path4.resolve(target);
|
|
742
|
+
const runDir = path4.basename(resolved) === "logs" ? path4.dirname(resolved) : resolved;
|
|
743
|
+
if (!fs4.existsSync(runDir)) {
|
|
744
|
+
throw new Error(`Run directory not found: ${runDir}`);
|
|
745
|
+
}
|
|
746
|
+
return runDir;
|
|
747
|
+
}
|
|
748
|
+
function latestRunDir(runsRoot) {
|
|
749
|
+
const names = fs4.existsSync(runsRoot) ? fs4.readdirSync(runsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("run-")).map((entry) => entry.name).sort() : [];
|
|
750
|
+
const latest = names[names.length - 1];
|
|
751
|
+
if (!latest) {
|
|
752
|
+
throw new Error(`No runs found to resume \u2014 looked in ${runsRoot}`);
|
|
753
|
+
}
|
|
754
|
+
return path4.join(runsRoot, latest);
|
|
755
|
+
}
|
|
756
|
+
function readReport(runDir) {
|
|
757
|
+
const reportPath = path4.join(runDir, "logs", "report.json");
|
|
758
|
+
let raw;
|
|
759
|
+
try {
|
|
760
|
+
raw = fs4.readFileSync(reportPath, "utf-8");
|
|
761
|
+
} catch {
|
|
762
|
+
throw new Error(`No report.json in ${runDir} \u2014 it may not be a complete run`);
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
return JSON.parse(raw);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
768
|
+
throw new Error(`Could not parse ${reportPath}: ${detail}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function resumeOptionsFromReport(report, requirementOverride) {
|
|
772
|
+
const agents2 = [...new Set(report.agentResults.map((result) => result.agentId))];
|
|
773
|
+
if (agents2.length === 0) {
|
|
774
|
+
throw new Error("The previous run has no agent results \u2014 nothing to resume");
|
|
775
|
+
}
|
|
776
|
+
const sessions = {};
|
|
777
|
+
for (const result of report.agentResults) {
|
|
778
|
+
if (result.sessionId) sessions[result.agentId] = result.sessionId;
|
|
779
|
+
}
|
|
780
|
+
const requirement = requirementOverride ?? report.requirement;
|
|
781
|
+
if (requirement === void 0) {
|
|
782
|
+
throw new Error("The previous run recorded no requirement \u2014 pass one explicitly to resume");
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
requirement,
|
|
786
|
+
resume: {
|
|
787
|
+
agents: agents2,
|
|
788
|
+
// Absent, not `{}`: core reads a missing `sessions` as "start every agent
|
|
789
|
+
// fresh", which is exactly the single-agent retry of a failed agent.
|
|
790
|
+
...Object.keys(sessions).length > 0 ? { sessions } : {}
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
635
795
|
// src/commands/run.ts
|
|
636
796
|
var EXIT_CANCELLED = 130;
|
|
637
|
-
async function runCommand(
|
|
638
|
-
const config = await loadConfig(options.config,
|
|
797
|
+
async function runCommand(requirementArg, options) {
|
|
798
|
+
const config = await loadConfig(options.config, requirementArg);
|
|
639
799
|
if (options.noLeader) {
|
|
640
800
|
config.useLeader = false;
|
|
641
801
|
}
|
|
642
|
-
const
|
|
643
|
-
|
|
802
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
803
|
+
if (options.resume !== void 0) {
|
|
804
|
+
const runsRoot = path5.join(config.workDir, ".multiclaw", "runs");
|
|
805
|
+
const runDir = resolveRunDir(runsRoot, options.resume);
|
|
806
|
+
const report = readReport(runDir);
|
|
807
|
+
const { requirement, resume } = resumeOptionsFromReport(report, requirementArg);
|
|
808
|
+
config.workDir = runDir;
|
|
809
|
+
config.resume = resume;
|
|
810
|
+
config.context = { ...config.context, requirement };
|
|
811
|
+
config.logDir = path5.join(runDir, `logs-resume-${stamp}`);
|
|
812
|
+
const plan = describeResumePlan({
|
|
813
|
+
agents: resume.agents,
|
|
814
|
+
sessions: resume.sessions,
|
|
815
|
+
team: config.agents
|
|
816
|
+
});
|
|
817
|
+
console.log(`\x1B[2mResuming ${runDir} (${resume.agents.length} agents) \u2014 ${plan}\x1B[0m`);
|
|
818
|
+
} else {
|
|
819
|
+
config.workDir = path5.join(config.workDir, ".multiclaw", "runs", `run-${stamp}`);
|
|
820
|
+
}
|
|
821
|
+
if (options.dryRun) {
|
|
822
|
+
const preview = await new OrchestratorImpl(config).preview();
|
|
823
|
+
printDryRun(preview, config.workDir);
|
|
824
|
+
process.exitCode = 0;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
644
827
|
await assertRuntimesAvailable(config);
|
|
828
|
+
const before = snapshotFiles(config.workDir);
|
|
645
829
|
const orchestrator = new OrchestratorImpl(config);
|
|
830
|
+
orchestrator.eventBus.subscribe((event) => {
|
|
831
|
+
if (event.type !== "orchestration:complete") return;
|
|
832
|
+
event.payload.deliverables = collectDeliverables(config.workDir, before);
|
|
833
|
+
});
|
|
646
834
|
new ConsoleReporter().attach(orchestrator.eventBus);
|
|
647
|
-
const logDir = config.logDir ??
|
|
835
|
+
const logDir = config.logDir ?? path5.join(config.workDir, "logs");
|
|
648
836
|
const logs = new FileLogWriter(logDir);
|
|
649
837
|
logs.attach(orchestrator.eventBus);
|
|
650
838
|
console.log(`\x1B[2mLogs: ${logDir}\x1B[0m`);
|
|
@@ -662,6 +850,59 @@ async function runCommand(requirement, options) {
|
|
|
662
850
|
process.off("SIGINT", onSigint);
|
|
663
851
|
}
|
|
664
852
|
}
|
|
853
|
+
function printDryRun(preview, workDir) {
|
|
854
|
+
console.log("Dry run \u2014 no pipeline agents will execute.");
|
|
855
|
+
console.log(`Project: ${preview.name}`);
|
|
856
|
+
console.log(`Work dir: ${workDir}`);
|
|
857
|
+
if (preview.requirement) console.log(`Requirement: ${preview.requirement}`);
|
|
858
|
+
if (preview.reply) {
|
|
859
|
+
console.log(`${preview.reply.mode === "ask" ? "Question" : "Reply"}: ${preview.reply.message}`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
console.log(`Agents: ${preview.totalAgents}`);
|
|
863
|
+
if (preview.selectionReason) console.log(`Why: ${preview.selectionReason}`);
|
|
864
|
+
for (const stage of preview.stages) {
|
|
865
|
+
const agents2 = stage.agents.map(
|
|
866
|
+
(agent) => `${agent.id}${agent.model ? ` (${agent.model})` : ""}`
|
|
867
|
+
);
|
|
868
|
+
console.log(`Stage ${stage.stageIndex + 1}: ${agents2.join(", ") || "\u2014"}`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
function snapshotFiles(root) {
|
|
872
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
873
|
+
walkFiles(root, (_absolute, relative2, stat) => {
|
|
874
|
+
snapshot.set(relative2, { mtimeMs: stat.mtimeMs, size: stat.size });
|
|
875
|
+
});
|
|
876
|
+
return snapshot;
|
|
877
|
+
}
|
|
878
|
+
function collectDeliverables(root, before) {
|
|
879
|
+
const deliverables = [];
|
|
880
|
+
walkFiles(root, (_absolute, relative2, stat) => {
|
|
881
|
+
const previous = before.get(relative2);
|
|
882
|
+
if (previous && previous.mtimeMs === stat.mtimeMs && previous.size === stat.size) return;
|
|
883
|
+
deliverables.push({
|
|
884
|
+
path: relative2,
|
|
885
|
+
status: previous ? "modified" : "created",
|
|
886
|
+
size: stat.size,
|
|
887
|
+
modifiedAt: stat.mtime.toISOString()
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
return deliverables.sort((a, b) => a.path.localeCompare(b.path)).slice(0, 20);
|
|
891
|
+
}
|
|
892
|
+
function walkFiles(root, visit) {
|
|
893
|
+
if (!fs5.existsSync(root)) return;
|
|
894
|
+
const stack = [root];
|
|
895
|
+
while (stack.length > 0) {
|
|
896
|
+
const dir = stack.pop();
|
|
897
|
+
for (const name of fs5.readdirSync(dir)) {
|
|
898
|
+
if (name === ".multiclaw") continue;
|
|
899
|
+
const absolute = path5.join(dir, name);
|
|
900
|
+
const stat = fs5.statSync(absolute);
|
|
901
|
+
if (stat.isDirectory()) stack.push(absolute);
|
|
902
|
+
else if (stat.isFile()) visit(absolute, path5.relative(root, absolute), stat);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
665
906
|
|
|
666
907
|
// src/commands/tui.ts
|
|
667
908
|
async function tuiCommand(options) {
|
|
@@ -678,44 +919,139 @@ async function tuiCommand(options) {
|
|
|
678
919
|
|
|
679
920
|
// src/commands/upgrade.ts
|
|
680
921
|
import { execSync } from "child_process";
|
|
681
|
-
import {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
922
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
923
|
+
import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
|
|
924
|
+
import { fileURLToPath } from "url";
|
|
925
|
+
var PACKAGE_NAME = "multiclaw";
|
|
926
|
+
var RED2 = "\x1B[31m";
|
|
927
|
+
var GREEN2 = "\x1B[32m";
|
|
928
|
+
var RESET2 = "\x1B[0m";
|
|
929
|
+
function readManifest(dir) {
|
|
930
|
+
const manifest = join4(dir, "package.json");
|
|
931
|
+
if (!existsSync5(manifest)) return void 0;
|
|
932
|
+
try {
|
|
933
|
+
return JSON.parse(readFileSync2(manifest, "utf8"));
|
|
934
|
+
} catch {
|
|
935
|
+
return void 0;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function findPackageRoot(start) {
|
|
939
|
+
let dir = resolve4(start);
|
|
940
|
+
for (; ; ) {
|
|
941
|
+
if (readManifest(dir)?.name === PACKAGE_NAME) return dir;
|
|
942
|
+
const parent = dirname3(dir);
|
|
943
|
+
if (parent === dir) return void 0;
|
|
944
|
+
dir = parent;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function readInstalledVersion(pkgRoot) {
|
|
948
|
+
return readManifest(pkgRoot)?.version;
|
|
949
|
+
}
|
|
950
|
+
function detectInstaller(pkgRoot) {
|
|
951
|
+
const path6 = pkgRoot.replaceAll("\\", "/");
|
|
952
|
+
if (path6.includes("/pnpm/global/") || path6.includes("/.pnpm/")) return "pnpm";
|
|
953
|
+
if (path6.includes("/install/global/") || path6.includes("/.bun/")) return "bun";
|
|
954
|
+
if (path6.includes("/yarn/global/")) return "yarn";
|
|
955
|
+
if (path6.includes("/node_modules/")) return "npm";
|
|
956
|
+
return "unknown";
|
|
957
|
+
}
|
|
958
|
+
var COMMANDS = {
|
|
959
|
+
npm: { latest: "npm show multiclaw version", install: "npm install -g multiclaw@latest" },
|
|
960
|
+
pnpm: { latest: "pnpm view multiclaw version", install: "pnpm add -g multiclaw@latest" },
|
|
961
|
+
bun: void 0,
|
|
962
|
+
yarn: void 0,
|
|
963
|
+
unknown: void 0
|
|
964
|
+
};
|
|
965
|
+
var MANUAL = {
|
|
966
|
+
npm: "npm install -g multiclaw@latest",
|
|
967
|
+
pnpm: "pnpm add -g multiclaw@latest",
|
|
968
|
+
bun: "bun add -g multiclaw@latest",
|
|
969
|
+
yarn: "yarn global add multiclaw@latest",
|
|
970
|
+
unknown: "npm install -g multiclaw@latest"
|
|
971
|
+
};
|
|
972
|
+
function createUpgradeContext() {
|
|
973
|
+
const pkgRoot = findPackageRoot(dirname3(fileURLToPath(import.meta.url)));
|
|
974
|
+
if (!pkgRoot) return void 0;
|
|
975
|
+
const currentVersion = readInstalledVersion(pkgRoot);
|
|
976
|
+
if (!currentVersion) return void 0;
|
|
977
|
+
return {
|
|
978
|
+
pkgRoot,
|
|
979
|
+
currentVersion,
|
|
980
|
+
installer: detectInstaller(pkgRoot),
|
|
981
|
+
readVersion: readInstalledVersion,
|
|
982
|
+
exec: (command, mode) => {
|
|
983
|
+
if (mode === "inherit") {
|
|
984
|
+
execSync(command, { stdio: "inherit" });
|
|
985
|
+
return "";
|
|
986
|
+
}
|
|
987
|
+
return execSync(command, { encoding: "utf8" });
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
async function runUpgrade(ctx) {
|
|
992
|
+
const { pkgRoot, currentVersion, installer, exec, readVersion } = ctx;
|
|
993
|
+
console.log(`Current version: v${currentVersion} (${installer} install)`);
|
|
994
|
+
const commands = COMMANDS[installer];
|
|
995
|
+
if (!commands) {
|
|
996
|
+
const reason = installer === "unknown" ? `this copy is not a global install (${pkgRoot})` : `the ${installer} install at ${pkgRoot} cannot be upgraded automatically`;
|
|
997
|
+
console.error(`${RED2}Error: ${reason}. Run it manually: ${MANUAL[installer]}${RESET2}`);
|
|
998
|
+
process.exit(1);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
686
1001
|
console.log("Checking for updates...");
|
|
687
1002
|
let latestVersion;
|
|
688
1003
|
try {
|
|
689
|
-
latestVersion =
|
|
1004
|
+
latestVersion = exec(commands.latest, "capture").trim();
|
|
690
1005
|
} catch {
|
|
691
|
-
console.error(
|
|
1006
|
+
console.error(`${RED2}Error: Failed to fetch latest version from npm registry${RESET2}`);
|
|
692
1007
|
process.exit(1);
|
|
693
1008
|
return;
|
|
694
1009
|
}
|
|
695
1010
|
if (latestVersion === currentVersion) {
|
|
696
|
-
console.log(
|
|
1011
|
+
console.log(`${GREEN2}\u2713${RESET2} Already on the latest version (v${currentVersion})`);
|
|
697
1012
|
return;
|
|
698
1013
|
}
|
|
699
1014
|
console.log(`Upgrading multiclaw v${currentVersion} \u2192 v${latestVersion}...`);
|
|
700
1015
|
try {
|
|
701
|
-
|
|
702
|
-
console.log(`\x1B[32m\u2713\x1B[0m Successfully upgraded to v${latestVersion}`);
|
|
1016
|
+
exec(commands.install, "inherit");
|
|
703
1017
|
} catch {
|
|
1018
|
+
console.error(`${RED2}Error: Upgrade failed. Try running manually: ${commands.install}${RESET2}`);
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const installedVersion = readVersion(pkgRoot);
|
|
1023
|
+
if (installedVersion !== latestVersion) {
|
|
1024
|
+
console.error(
|
|
1025
|
+
`${RED2}Error: ${commands.install} finished, but ${pkgRoot} still reports v${installedVersion ?? "unknown"}. Check for a second multiclaw install, then retry.${RESET2}`
|
|
1026
|
+
);
|
|
1027
|
+
process.exit(1);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
console.log(`${GREEN2}\u2713${RESET2} Successfully upgraded to v${latestVersion} (${pkgRoot})`);
|
|
1031
|
+
}
|
|
1032
|
+
async function upgradeCommand() {
|
|
1033
|
+
const ctx = createUpgradeContext();
|
|
1034
|
+
if (!ctx) {
|
|
704
1035
|
console.error(
|
|
705
|
-
|
|
1036
|
+
`${RED2}Error: could not locate the multiclaw package this process is running from.${RESET2}`
|
|
706
1037
|
);
|
|
707
1038
|
process.exit(1);
|
|
1039
|
+
return;
|
|
708
1040
|
}
|
|
1041
|
+
await runUpgrade(ctx);
|
|
709
1042
|
}
|
|
710
1043
|
|
|
711
1044
|
// src/index.ts
|
|
712
|
-
var __filename2 =
|
|
713
|
-
var __dirname2 =
|
|
714
|
-
var pkg = JSON.parse(
|
|
1045
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1046
|
+
var __dirname2 = dirname4(__filename2);
|
|
1047
|
+
var pkg = JSON.parse(readFileSync3(join5(__dirname2, "../package.json"), "utf-8"));
|
|
715
1048
|
var program = new Command();
|
|
716
1049
|
program.name("multiclaw").description("Multi-agent orchestration CLI").version(pkg.version, "-v, --version", "Display version number").action((options) => tuiCommand(options));
|
|
717
1050
|
program.command("init").description("Scaffold a config file (interactive)").action(() => initCommand());
|
|
718
|
-
program.command("run
|
|
1051
|
+
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(
|
|
1052
|
+
"--resume [path]",
|
|
1053
|
+
"Resume a previous run, continuing agent sessions (default: the latest)"
|
|
1054
|
+
).action((requirement, opts) => runCommand(requirement, opts));
|
|
719
1055
|
program.command("upgrade").description("Upgrade multiclaw to the latest version").action(() => upgradeCommand());
|
|
720
1056
|
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));
|
|
721
1057
|
program.parseAsync(process.argv).catch((err) => {
|
package/dist/lib.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MultiClawConfig } from
|
|
2
|
-
export { AgentDefinition, MultiClawConfig
|
|
3
|
-
|
|
1
|
+
import { detectProviders, type MultiClawConfig, presentProviders } from "@multiclawcli/core";
|
|
2
|
+
export type { AgentDefinition, MultiClawConfig } from "@multiclawcli/core";
|
|
3
|
+
export { agents, defineConfig } from "@multiclawcli/core";
|
|
4
4
|
/**
|
|
5
5
|
* Hard gate for `run`/`tui`: every runtime the config names must exist and
|
|
6
6
|
* report itself as installed, or the process exits before any work starts.
|
|
@@ -10,12 +10,11 @@ export { AgentDefinition, MultiClawConfig, agents, defineConfig, detectProviders
|
|
|
10
10
|
* receives the agent's configured command because an external-process runtime
|
|
11
11
|
* (e.g. ACP) is only as available as the binary it was told to launch.
|
|
12
12
|
*/
|
|
13
|
-
declare function assertRuntimesAvailable(config: MultiClawConfig): Promise<void>;
|
|
14
|
-
|
|
13
|
+
export declare function assertRuntimesAvailable(config: MultiClawConfig): Promise<void>;
|
|
14
|
+
/** Re-export for consumers that need full detection details. */
|
|
15
|
+
export { detectProviders, presentProviders };
|
|
15
16
|
/**
|
|
16
17
|
* Legacy alias: returns runtime names that have at least one provider present.
|
|
17
18
|
* Prefer `detectProviders()` for new code.
|
|
18
19
|
*/
|
|
19
|
-
declare function availableRuntimes(): Promise<string[]>;
|
|
20
|
-
|
|
21
|
-
export { assertRuntimesAvailable, availableRuntimes };
|
|
20
|
+
export declare function availableRuntimes(): Promise<string[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EventBus } from "@multiclawcli/core";
|
|
2
|
+
/**
|
|
3
|
+
* Streams each agent's output to its own log file, plus a `report.json` per turn.
|
|
4
|
+
*
|
|
5
|
+
* Logging is diagnostics, so nothing in here is allowed to take the run down: a
|
|
6
|
+
* failed log write is reported and then ignored.
|
|
7
|
+
*/
|
|
8
|
+
export declare class FileLogWriter {
|
|
9
|
+
private logDir;
|
|
10
|
+
private onError;
|
|
11
|
+
private streams;
|
|
12
|
+
/** Agent ids whose log could not be opened — never retried, so a broken
|
|
13
|
+
* directory does not produce one failing stream per output chunk. */
|
|
14
|
+
private failed;
|
|
15
|
+
private closing;
|
|
16
|
+
constructor(logDir: string, onError?: (message: string) => void);
|
|
17
|
+
attach(eventBus: EventBus): () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Flush every open stream.
|
|
20
|
+
*
|
|
21
|
+
* `stream.end()` only *starts* the flush, so a caller that exits the moment
|
|
22
|
+
* `orchestration:complete` arrives can lose the tail of the last agent's log.
|
|
23
|
+
* Awaiting this is what makes that tail durable.
|
|
24
|
+
*/
|
|
25
|
+
drain(): Promise<void>;
|
|
26
|
+
private handle;
|
|
27
|
+
private getStream;
|
|
28
|
+
private flush;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multiclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"multiclaw": "dist/index.js"
|
|
@@ -27,22 +27,22 @@
|
|
|
27
27
|
"url": "https://github.com/shipengqi/multiclaw/issues"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@agentclientprotocol/claude-agent-acp": "^0.
|
|
30
|
+
"@agentclientprotocol/claude-agent-acp": "^0.93.0",
|
|
31
31
|
"@agentclientprotocol/codex-acp": "^2.2.2",
|
|
32
32
|
"@inquirer/prompts": "^8.7.3",
|
|
33
|
-
"@multiclawcli/core": "0.
|
|
34
|
-
"@multiclawcli/dashboard": "0.
|
|
33
|
+
"@multiclawcli/core": "0.5.1",
|
|
34
|
+
"@multiclawcli/dashboard": "0.5.1",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
36
|
"jiti": "^2.7.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^26.6.
|
|
39
|
+
"@types/node": "^26.6.5",
|
|
40
40
|
"tsx": "^4.7.0",
|
|
41
|
-
"typescript": "^
|
|
42
|
-
"vitest": "^
|
|
41
|
+
"typescript": "^7.0.2",
|
|
42
|
+
"vitest": "^5.0.4"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"build": "tsup",
|
|
45
|
+
"build": "tsup && tsc -p tsconfig.json --emitDeclarationOnly --outDir dist --composite false --incremental false",
|
|
46
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
47
|
"test": "vitest run",
|
|
48
48
|
"test:coverage": "vitest run --coverage",
|