lazyclaw 3.99.21 → 3.99.22
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 +13 -0
- package/cli.mjs +259 -1
- package/package.json +1 -1
- package/providers/orchestrator.mjs +8 -0
package/README.md
CHANGED
|
@@ -111,6 +111,19 @@ Then `lazyclaw chat` (or any other entry point that ends up calling a provider
|
|
|
111
111
|
|
|
112
112
|
Defaults fall back gracefully: `planner` defaults to `cfg.provider`/`cfg.model`, `workers` defaults to `[planner]` (single-agent chain, still benefits from plan + synthesis structure). Self-recursion (`planner: "orchestrator"`) is rejected up front.
|
|
113
113
|
|
|
114
|
+
You can skip the JSON entirely and configure via `lazyclaw onboard` / `lazyclaw setup` (the picker lands on the orchestrator and walks you through a planner + workers wizard) **or** via the dedicated CLI:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
lazyclaw orchestrator status
|
|
118
|
+
lazyclaw orchestrator set-planner claude-cli:claude-opus-4-7
|
|
119
|
+
lazyclaw orchestrator workers add openai:gpt-4o
|
|
120
|
+
lazyclaw orchestrator workers add gemini:gemini-2.5-pro
|
|
121
|
+
lazyclaw orchestrator workers set claude-cli:claude-sonnet-4-6,nim:meta/llama-3.1-405b-instruct # bulk replace
|
|
122
|
+
lazyclaw orchestrator set-max-subtasks 5
|
|
123
|
+
lazyclaw orchestrator clear # wipe cfg.orchestrator
|
|
124
|
+
lazyclaw config set provider orchestrator # route chats through it
|
|
125
|
+
```
|
|
126
|
+
|
|
114
127
|
## Launcher (no-arg `lazyclaw`)
|
|
115
128
|
|
|
116
129
|
Running `lazyclaw` with no subcommand drops into an arrow-key launcher with every subcommand laid out as a menu. Navigation:
|
package/cli.mjs
CHANGED
|
@@ -914,6 +914,8 @@ const SUBCOMMANDS = [
|
|
|
914
914
|
'auth', 'pairing', 'nodes', 'message', 'workspace', 'browse', 'cron',
|
|
915
915
|
// v3.99.6 — multi-step setup wizard + lazyclaw-only dashboard
|
|
916
916
|
'setup', 'dashboard',
|
|
917
|
+
// v3.99.22 — multi-agent orchestrator config
|
|
918
|
+
'orchestrator',
|
|
917
919
|
];
|
|
918
920
|
|
|
919
921
|
const SUBCOMMAND_SUBS = {
|
|
@@ -929,6 +931,7 @@ const SUBCOMMAND_SUBS = {
|
|
|
929
931
|
message: ['list', 'add', 'remove', 'send'],
|
|
930
932
|
workspace: ['list', 'init', 'show', 'remove', 'path'],
|
|
931
933
|
cron: ['list', 'add', 'remove', 'show', 'sync', 'run'],
|
|
934
|
+
orchestrator: ['status', 'set-planner', 'workers', 'set-max-subtasks', 'clear'],
|
|
932
935
|
};
|
|
933
936
|
|
|
934
937
|
function bashCompletion() {
|
|
@@ -1185,6 +1188,7 @@ const HELP_DETAILS = {
|
|
|
1185
1188
|
cron: 'Usage: lazyclaw cron <list | add <name> "<cron-spec>" -- <cmd> ... | remove <name> | show <name> | sync | run <name>>\n Schedule recurring agent runs. macOS uses launchd (~/Library/LaunchAgents/com.lazyclaw.<name>.plist); Linux / WSL uses the user crontab.\n Cron spec is the standard 5-field form (minute hour dom month dow). Supports *, range a-b, list a,b,c, step */N.\n add: pass the command after `--`. Typical use:\n lazyclaw cron add daily-summary "0 9 * * 1-5" -- lazyclaw agent "Summarise today\'s TODOs"\n list / show: read from cfg.cron[name] (config is the source of truth).\n sync: re-installs every job in cfg.cron into the system scheduler — handy after a reinstall.\n run: one-shot in-process execution of the named job; the OS scheduler does the same thing on its trigger.\n Logs: ~/.lazyclaw/logs/cron-<name>.{out,err}.log (macOS launchd path).',
|
|
1186
1189
|
setup: 'Usage: lazyclaw setup [--skip-test]\n OpenClaw-style multi-step first-run wizard. Walks through:\n 1. Provider + model + api-key (delegates to onboard --pick)\n 2. Optional workspace init (AGENTS.md / SOUL.md / TOOLS.md)\n 3. Optional skill bundle install from GitHub\n 4. Optional outbound webhook (Slack / Discord)\n 5. Reachability test against the picked provider\n Each optional step takes Enter or "skip" to bypass. Re-runnable safely.\n Also fires automatically on first run when `lazyclaw` is invoked with no config.',
|
|
1187
1190
|
dashboard: 'Usage: lazyclaw dashboard [--port <N>] [--no-open]\n Launches the lazyclaw-only web UI on http://127.0.0.1:<port> (default 19600) and opens it in the default browser.\n Wraps `lazyclaw daemon` + a static HTML; no Python / lazyclaude dashboard required.\n Tabs: Chat · Sessions · Skills · Workspace · Providers · Status. Each tab calls existing daemon endpoints.\n --no-open keeps the browser closed (handy for SSH / headless / dev). The bound URL is always printed to stdout.',
|
|
1191
|
+
orchestrator: 'Usage: lazyclaw orchestrator <status | set-planner <provider[:model]> | workers add <spec> | workers remove <spec> | workers set <spec,spec,...> | workers clear | set-max-subtasks <N> | clear>\n Read/write cfg.orchestrator without editing config.json by hand.\n status — print {planner, workers, maxSubtasks} as JSON; lists registered providers for reference.\n set-planner — replace the planner spec ("provider" or "provider:model"). "orchestrator" itself is rejected (self-recursion).\n workers add — append a worker (idempotent — duplicates skipped).\n workers remove — drop a worker by exact match. Idempotent.\n workers set — replace the whole list (comma-separated specs).\n workers clear — empty the workers list.\n set-max-subtasks <N> — cap subtasks per request, clamped 1..10 (default 5).\n clear — delete the cfg.orchestrator block entirely.\n Pair with: `lazyclaw config set provider orchestrator` to route chats through it.',
|
|
1188
1192
|
};
|
|
1189
1193
|
|
|
1190
1194
|
function cmdHelp(name) {
|
|
@@ -1835,7 +1839,17 @@ async function _pickProviderInteractive() {
|
|
|
1835
1839
|
provider = picked;
|
|
1836
1840
|
}
|
|
1837
1841
|
|
|
1838
|
-
// ── Step 3 — model
|
|
1842
|
+
// ── Step 3 — model (or, for composite providers, a config wizard) ───
|
|
1843
|
+
// The orchestrator (and any future composite provider) has no model
|
|
1844
|
+
// of its own — it dispatches to other providers. Step 3 routes
|
|
1845
|
+
// through a custom wizard instead of the standard model picker.
|
|
1846
|
+
const providerMeta = (_registryMod.PROVIDER_INFO || {})[provider.id] || {};
|
|
1847
|
+
if (providerMeta.composite || provider.id === 'orchestrator') {
|
|
1848
|
+
const result = await _setupOrchestratorInteractive();
|
|
1849
|
+
if (result === 'CANCEL') return null;
|
|
1850
|
+
if (result === 'BACK') return _pickProviderInteractive();
|
|
1851
|
+
return { provider: provider.id, model: 'orchestrator' };
|
|
1852
|
+
}
|
|
1839
1853
|
const picked = await _pickModelInteractive(provider.id, {
|
|
1840
1854
|
titlePrefix: 'LazyClaw setup — Step 3 of 3:',
|
|
1841
1855
|
onBack: 'restart',
|
|
@@ -1845,6 +1859,125 @@ async function _pickProviderInteractive() {
|
|
|
1845
1859
|
return { provider: provider.id, model: picked };
|
|
1846
1860
|
}
|
|
1847
1861
|
|
|
1862
|
+
// Step-3 alternative for composite providers (currently only the
|
|
1863
|
+
// orchestrator). Builds `cfg.orchestrator = { planner, workers,
|
|
1864
|
+
// maxSubtasks }` interactively and persists it before returning.
|
|
1865
|
+
//
|
|
1866
|
+
// planner: single picker over registered non-composite providers.
|
|
1867
|
+
// workers: multi-select with a running list + add/remove/done loop.
|
|
1868
|
+
// maxSubtasks: typed integer, default 5.
|
|
1869
|
+
async function _setupOrchestratorInteractive() {
|
|
1870
|
+
const accent = (s) => `\x1b[38;5;208m${s}\x1b[0m`;
|
|
1871
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
1872
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
1873
|
+
const ok = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
1874
|
+
const info = _registryMod.PROVIDER_INFO || {};
|
|
1875
|
+
const eligibleNames = Object.keys(_registryMod.PROVIDERS).filter((n) => n !== 'orchestrator' && n !== 'mock');
|
|
1876
|
+
if (eligibleNames.length === 0) {
|
|
1877
|
+
process.stdout.write('\n' + accent('orchestrator setup') + ': no eligible workers — register a real provider first.\n');
|
|
1878
|
+
await _quickPrompt(' press Enter to continue ');
|
|
1879
|
+
return 'CANCEL';
|
|
1880
|
+
}
|
|
1881
|
+
const cfg = readConfig();
|
|
1882
|
+
const existing = cfg.orchestrator && typeof cfg.orchestrator === 'object' ? cfg.orchestrator : {};
|
|
1883
|
+
|
|
1884
|
+
// ── Pick planner ─────────────────────────────────────────────────
|
|
1885
|
+
const plannerItems = eligibleNames.map((name) => {
|
|
1886
|
+
const m = info[name] || {};
|
|
1887
|
+
const defaultModel = m.defaultModel || '';
|
|
1888
|
+
return {
|
|
1889
|
+
id: `${name}${defaultModel ? ':' + defaultModel : ''}`,
|
|
1890
|
+
label: m.label && m.label !== name ? `${name} — ${m.label}` : name,
|
|
1891
|
+
desc: defaultModel ? `default model: ${defaultModel}` : '',
|
|
1892
|
+
};
|
|
1893
|
+
});
|
|
1894
|
+
const plannerPick = await _arrowMenu({
|
|
1895
|
+
title: 'LazyClaw setup — Step 3 of 3: orchestrator — pick the planner',
|
|
1896
|
+
subtitle: 'The planner decomposes the user request into subtasks and writes the final synthesis. Strong reasoning models work best here.',
|
|
1897
|
+
items: plannerItems,
|
|
1898
|
+
searchable: true,
|
|
1899
|
+
defaultIdx: Math.max(0, plannerItems.findIndex((p) => p.id === existing.planner)),
|
|
1900
|
+
});
|
|
1901
|
+
if (plannerPick === 'CANCEL') return 'CANCEL';
|
|
1902
|
+
if (plannerPick === 'BACK') return 'BACK';
|
|
1903
|
+
const planner = plannerPick.id;
|
|
1904
|
+
|
|
1905
|
+
// ── Pick workers (iterative add/remove) ──────────────────────────
|
|
1906
|
+
const workers = Array.isArray(existing.workers) ? existing.workers.slice() : [];
|
|
1907
|
+
while (true) {
|
|
1908
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
1909
|
+
process.stdout.write(accent('Orchestrator workers') + '\n');
|
|
1910
|
+
process.stdout.write(dim('Subtasks are dispatched round-robin across this list.') + '\n\n');
|
|
1911
|
+
if (workers.length === 0) {
|
|
1912
|
+
process.stdout.write(' ' + dim('(none yet — add at least one)') + '\n\n');
|
|
1913
|
+
} else {
|
|
1914
|
+
workers.forEach((w, i) => {
|
|
1915
|
+
process.stdout.write(` ${i + 1}. ${ok(w)}\n`);
|
|
1916
|
+
});
|
|
1917
|
+
process.stdout.write('\n');
|
|
1918
|
+
}
|
|
1919
|
+
const items = [
|
|
1920
|
+
{ id: '__add__', label: '+ Add a worker', desc: 'pick from registered providers' },
|
|
1921
|
+
{ id: '__remove__', label: '- Remove a worker', desc: workers.length ? 'pick which entry to drop' : '(nothing to remove)' },
|
|
1922
|
+
{ id: '__done__', label: `Done${workers.length ? ` (${workers.length} worker${workers.length === 1 ? '' : 's'})` : ' — at least one worker required'}`, desc: workers.length ? 'save cfg.orchestrator and finish' : 'add one worker first' },
|
|
1923
|
+
];
|
|
1924
|
+
const action = await _arrowMenu({
|
|
1925
|
+
title: 'LazyClaw setup — orchestrator workers',
|
|
1926
|
+
subtitle: `Planner: ${planner}`,
|
|
1927
|
+
items,
|
|
1928
|
+
});
|
|
1929
|
+
if (action === 'CANCEL') return 'CANCEL';
|
|
1930
|
+
if (action === 'BACK') return 'BACK';
|
|
1931
|
+
if (action.id === '__add__') {
|
|
1932
|
+
const wPick = await _arrowMenu({
|
|
1933
|
+
title: 'Add worker',
|
|
1934
|
+
subtitle: 'Picked entries are appended to the workers list.',
|
|
1935
|
+
items: plannerItems.filter((p) => !workers.includes(p.id)),
|
|
1936
|
+
searchable: true,
|
|
1937
|
+
});
|
|
1938
|
+
if (wPick === 'CANCEL' || wPick === 'BACK') continue;
|
|
1939
|
+
workers.push(wPick.id);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (action.id === '__remove__') {
|
|
1943
|
+
if (!workers.length) continue;
|
|
1944
|
+
const rPick = await _arrowMenu({
|
|
1945
|
+
title: 'Remove worker',
|
|
1946
|
+
subtitle: 'Highlighted entry is removed from the list.',
|
|
1947
|
+
items: workers.map((w) => ({ id: w, label: w })),
|
|
1948
|
+
});
|
|
1949
|
+
if (rPick === 'CANCEL' || rPick === 'BACK') continue;
|
|
1950
|
+
const idx = workers.indexOf(rPick.id);
|
|
1951
|
+
if (idx >= 0) workers.splice(idx, 1);
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (action.id === '__done__') {
|
|
1955
|
+
if (workers.length === 0) continue;
|
|
1956
|
+
break;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// ── maxSubtasks ──────────────────────────────────────────────────
|
|
1961
|
+
const defaultMax = Number.isFinite(existing.maxSubtasks) && existing.maxSubtasks > 0
|
|
1962
|
+
? Math.min(10, existing.maxSubtasks)
|
|
1963
|
+
: 5;
|
|
1964
|
+
const rawMax = (await _quickPrompt(` ${bold('maxSubtasks')} ${dim(`(2..10, blank → ${defaultMax}):`)} `)).trim();
|
|
1965
|
+
let maxSubtasks = defaultMax;
|
|
1966
|
+
if (rawMax) {
|
|
1967
|
+
const n = parseInt(rawMax, 10);
|
|
1968
|
+
if (Number.isFinite(n) && n >= 1) maxSubtasks = Math.min(10, Math.max(1, n));
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// ── Persist ──────────────────────────────────────────────────────
|
|
1972
|
+
cfg.orchestrator = { planner, workers, maxSubtasks };
|
|
1973
|
+
writeConfig(cfg);
|
|
1974
|
+
process.stdout.write('\n');
|
|
1975
|
+
process.stdout.write(` ${ok('✓ orchestrator saved')} ${dim('→')} ` +
|
|
1976
|
+
`planner ${ok(planner)} · ${workers.length} worker${workers.length === 1 ? '' : 's'} · maxSubtasks ${maxSubtasks}\n`);
|
|
1977
|
+
await _quickPrompt(' press Enter to continue ');
|
|
1978
|
+
return { ok: true };
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1848
1981
|
// Pause the chat REPL's readline + ghost-autocomplete while a sub-picker
|
|
1849
1982
|
// (provider / model arrow menu) takes over the terminal. The sub-picker
|
|
1850
1983
|
// installs its own `keypress` listener and toggles raw mode; the chat's
|
|
@@ -3722,6 +3855,126 @@ async function cmdProviders(sub, positional, flags = {}) {
|
|
|
3722
3855
|
}
|
|
3723
3856
|
}
|
|
3724
3857
|
|
|
3858
|
+
// `lazyclaw orchestrator` — read/write the cfg.orchestrator section
|
|
3859
|
+
// without editing config.json by hand. Mirrors the shape `lazyclaw
|
|
3860
|
+
// providers` / `lazyclaw rates` already use.
|
|
3861
|
+
//
|
|
3862
|
+
// Subcommands:
|
|
3863
|
+
// status Print current planner / workers / maxSubtasks as JSON.
|
|
3864
|
+
// set-planner <provider[:model]> Replace the planner spec.
|
|
3865
|
+
// workers add <provider[:model]> Append a worker (idempotent — duplicates skipped).
|
|
3866
|
+
// workers remove <provider[:model]> Drop a worker by exact match. Idempotent.
|
|
3867
|
+
// workers clear Empty the workers list.
|
|
3868
|
+
// workers set <provider[:model],...> Replace the whole list (comma-separated).
|
|
3869
|
+
// set-max-subtasks <N> Cap the number of subtasks (clamped 1..10).
|
|
3870
|
+
// clear Delete the entire cfg.orchestrator block.
|
|
3871
|
+
async function cmdOrchestrator(sub, positional, _flags = {}) {
|
|
3872
|
+
await ensureRegistry();
|
|
3873
|
+
const cfg = readConfig();
|
|
3874
|
+
const orch = cfg.orchestrator && typeof cfg.orchestrator === 'object' ? cfg.orchestrator : {};
|
|
3875
|
+
const known = Object.keys(_registryMod.PROVIDERS);
|
|
3876
|
+
const validateSpec = (spec) => {
|
|
3877
|
+
if (!spec) throw new Error('provider spec required (e.g. "claude-cli" or "openai:gpt-4o")');
|
|
3878
|
+
const colon = spec.indexOf(':');
|
|
3879
|
+
const provName = colon > 0 ? spec.slice(0, colon) : spec;
|
|
3880
|
+
if (provName === 'orchestrator') throw new Error('"orchestrator" cannot reference itself — pick a real provider');
|
|
3881
|
+
if (!known.includes(provName)) {
|
|
3882
|
+
throw new Error(`unknown provider "${provName}" — registered: ${known.join(', ')}`);
|
|
3883
|
+
}
|
|
3884
|
+
return spec;
|
|
3885
|
+
};
|
|
3886
|
+
const saveAndPrint = (next) => {
|
|
3887
|
+
if (next === null) delete cfg.orchestrator;
|
|
3888
|
+
else cfg.orchestrator = next;
|
|
3889
|
+
writeConfig(cfg);
|
|
3890
|
+
console.log(JSON.stringify(cfg.orchestrator || null, null, 2));
|
|
3891
|
+
};
|
|
3892
|
+
switch (sub) {
|
|
3893
|
+
case undefined:
|
|
3894
|
+
case 'status': {
|
|
3895
|
+
console.log(JSON.stringify({
|
|
3896
|
+
ok: true,
|
|
3897
|
+
configured: !!cfg.orchestrator,
|
|
3898
|
+
planner: orch.planner || null,
|
|
3899
|
+
workers: Array.isArray(orch.workers) ? orch.workers : [],
|
|
3900
|
+
maxSubtasks: Number.isFinite(orch.maxSubtasks) ? orch.maxSubtasks : null,
|
|
3901
|
+
knownProviders: known,
|
|
3902
|
+
}, null, 2));
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
case 'set-planner': {
|
|
3906
|
+
try {
|
|
3907
|
+
const spec = validateSpec(positional[0]);
|
|
3908
|
+
saveAndPrint({ ...orch, planner: spec });
|
|
3909
|
+
} catch (e) { console.error(`orchestrator: ${e.message}`); process.exit(2); }
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
case 'workers': {
|
|
3913
|
+
const wsub = positional[0];
|
|
3914
|
+
const workers = Array.isArray(orch.workers) ? orch.workers.slice() : [];
|
|
3915
|
+
switch (wsub) {
|
|
3916
|
+
case 'add': {
|
|
3917
|
+
try {
|
|
3918
|
+
const spec = validateSpec(positional[1]);
|
|
3919
|
+
if (!workers.includes(spec)) workers.push(spec);
|
|
3920
|
+
saveAndPrint({ ...orch, workers });
|
|
3921
|
+
} catch (e) { console.error(`orchestrator: ${e.message}`); process.exit(2); }
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
case 'remove': {
|
|
3925
|
+
const spec = positional[1];
|
|
3926
|
+
if (!spec) { console.error('orchestrator: workers remove <provider[:model]>'); process.exit(2); }
|
|
3927
|
+
const idx = workers.indexOf(spec);
|
|
3928
|
+
if (idx >= 0) workers.splice(idx, 1);
|
|
3929
|
+
saveAndPrint({ ...orch, workers });
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
case 'clear': {
|
|
3933
|
+
saveAndPrint({ ...orch, workers: [] });
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
case 'set': {
|
|
3937
|
+
const raw = positional[1] || '';
|
|
3938
|
+
const specs = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
3939
|
+
try {
|
|
3940
|
+
specs.forEach(validateSpec);
|
|
3941
|
+
saveAndPrint({ ...orch, workers: specs });
|
|
3942
|
+
} catch (e) { console.error(`orchestrator: ${e.message}`); process.exit(2); }
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
default: {
|
|
3946
|
+
console.error('Usage: lazyclaw orchestrator workers <add <spec> | remove <spec> | clear | set <spec,spec,...>>');
|
|
3947
|
+
process.exit(2);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
case 'set-max-subtasks': {
|
|
3952
|
+
const n = parseInt(positional[0], 10);
|
|
3953
|
+
if (!Number.isFinite(n) || n < 1) { console.error('orchestrator: set-max-subtasks <N> (1..10)'); process.exit(2); }
|
|
3954
|
+
saveAndPrint({ ...orch, maxSubtasks: Math.min(10, Math.max(1, n)) });
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
case 'clear': {
|
|
3958
|
+
saveAndPrint(null);
|
|
3959
|
+
return;
|
|
3960
|
+
}
|
|
3961
|
+
default: {
|
|
3962
|
+
console.error(
|
|
3963
|
+
'Usage:\n' +
|
|
3964
|
+
' lazyclaw orchestrator status\n' +
|
|
3965
|
+
' lazyclaw orchestrator set-planner <provider[:model]>\n' +
|
|
3966
|
+
' lazyclaw orchestrator workers add <provider[:model]>\n' +
|
|
3967
|
+
' lazyclaw orchestrator workers remove <provider[:model]>\n' +
|
|
3968
|
+
' lazyclaw orchestrator workers set <provider[:model],...>\n' +
|
|
3969
|
+
' lazyclaw orchestrator workers clear\n' +
|
|
3970
|
+
' lazyclaw orchestrator set-max-subtasks <N>\n' +
|
|
3971
|
+
' lazyclaw orchestrator clear'
|
|
3972
|
+
);
|
|
3973
|
+
process.exit(2);
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3725
3978
|
async function cmdSessions(sub, positional, flags = {}) {
|
|
3726
3979
|
const sessionsMod = await import('./sessions.mjs');
|
|
3727
3980
|
const cfgDir = path.dirname(configPath());
|
|
@@ -4614,6 +4867,11 @@ async function main() {
|
|
|
4614
4867
|
await cmdProviders(sub, rest.positional.slice(1), rest.flags);
|
|
4615
4868
|
break;
|
|
4616
4869
|
}
|
|
4870
|
+
case 'orchestrator': {
|
|
4871
|
+
const sub = rest.positional[0];
|
|
4872
|
+
await cmdOrchestrator(sub, rest.positional.slice(1), rest.flags);
|
|
4873
|
+
break;
|
|
4874
|
+
}
|
|
4617
4875
|
case 'skills': {
|
|
4618
4876
|
const sub = rest.positional[0];
|
|
4619
4877
|
await cmdSkills(sub, rest.positional.slice(1), rest.flags);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lazyclaw",
|
|
3
|
-
"version": "3.99.
|
|
3
|
+
"version": "3.99.22",
|
|
4
4
|
"description": "Lazy, elegant terminal CLI for chatting with Claude / OpenAI / Gemini / Ollama and orchestrating multi-step LLM workflows. Banner-on-launch, slash-command ghost autocomplete, persistent sessions, local HTTP gateway.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -124,6 +124,14 @@ export function makeOrchestratorProvider(opts = {}) {
|
|
|
124
124
|
const fallbackSpec = cfg.provider && cfg.provider !== 'orchestrator'
|
|
125
125
|
? `${cfg.provider}${cfg.model ? ':' + cfg.model : ''}`
|
|
126
126
|
: 'claude-cli';
|
|
127
|
+
// First-run hint when cfg.orchestrator is missing entirely. The
|
|
128
|
+
// fallback path below still works (planner = cfg.provider, single
|
|
129
|
+
// worker = same), but the user almost certainly meant to opt in
|
|
130
|
+
// explicitly — surface the shortest valid CLI to set it up.
|
|
131
|
+
if (!cfg.orchestrator) {
|
|
132
|
+
yield `> orchestrator: \`cfg.orchestrator\` is not set. Defaulting to a single-agent chain on \`${fallbackSpec}\`.\n` +
|
|
133
|
+
`> Configure properly: \`lazyclaw orchestrator set-planner ${fallbackSpec}\` then \`lazyclaw orchestrator workers add <provider:model>\` (one per agent).\n\n`;
|
|
134
|
+
}
|
|
127
135
|
const plannerSpec = String(o.planner || fallbackSpec);
|
|
128
136
|
const workerSpecs = Array.isArray(o.workers) && o.workers.length
|
|
129
137
|
? o.workers.map(String)
|