@task0/cli 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/main.js +1033 -426
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -58,14 +58,14 @@ function generateObjectId(type) {
|
|
|
58
58
|
function isTaskObjectId(id) {
|
|
59
59
|
return TASK_ID_RE.test(id);
|
|
60
60
|
}
|
|
61
|
-
var RESOURCE_PREFIXES, PREFIX_TO_TYPE, BASE62, ID_LEN, BASE, MOD, TASK_ID_RE, PREFIXES_PATTERN, OBJECT_ID_RE;
|
|
61
|
+
var RESOURCE_PREFIXES, LEGACY_PREFIX_TO_TYPE, PREFIX_TO_TYPE, BASE62, ID_LEN, BASE, MOD, TASK_ID_RE, PREFIXES_PATTERN, OBJECT_ID_RE;
|
|
62
62
|
var init_object_id = __esm({
|
|
63
63
|
"../../packages/shared/dist/object-id.js"() {
|
|
64
64
|
"use strict";
|
|
65
65
|
RESOURCE_PREFIXES = {
|
|
66
66
|
task: "tsk",
|
|
67
67
|
project: "prj",
|
|
68
|
-
|
|
68
|
+
agent_run: "run",
|
|
69
69
|
issue: "iss",
|
|
70
70
|
inbox: "ibx",
|
|
71
71
|
inbox_note: "note",
|
|
@@ -81,7 +81,15 @@ var init_object_id = __esm({
|
|
|
81
81
|
task_kr_link: "krl",
|
|
82
82
|
task_comment: "cmt",
|
|
83
83
|
agent: "agt",
|
|
84
|
-
daemon: "dmn"
|
|
84
|
+
daemon: "dmn",
|
|
85
|
+
user: "usr",
|
|
86
|
+
runtime_profile: "rtp",
|
|
87
|
+
api_token: "apit",
|
|
88
|
+
automation: "aut",
|
|
89
|
+
automation_run: "autr"
|
|
90
|
+
};
|
|
91
|
+
LEGACY_PREFIX_TO_TYPE = {
|
|
92
|
+
rt: "agent_run"
|
|
85
93
|
};
|
|
86
94
|
PREFIX_TO_TYPE = Object.fromEntries(Object.entries(RESOURCE_PREFIXES).map(([k, v]) => [v, k]));
|
|
87
95
|
BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
@@ -89,7 +97,10 @@ var init_object_id = __esm({
|
|
|
89
97
|
BASE = 62n;
|
|
90
98
|
MOD = BASE ** BigInt(ID_LEN);
|
|
91
99
|
TASK_ID_RE = /^tsk_[A-Za-z0-9]{5,12}$/;
|
|
92
|
-
PREFIXES_PATTERN =
|
|
100
|
+
PREFIXES_PATTERN = [
|
|
101
|
+
...Object.values(RESOURCE_PREFIXES),
|
|
102
|
+
...Object.keys(LEGACY_PREFIX_TO_TYPE)
|
|
103
|
+
].join("|");
|
|
93
104
|
OBJECT_ID_RE = new RegExp(`\\b(?:${PREFIXES_PATTERN})_[A-Za-z0-9]{5,12}\\b`, "g");
|
|
94
105
|
}
|
|
95
106
|
});
|
|
@@ -444,8 +455,14 @@ async function updateTaskWorkflow(taskYml, patch) {
|
|
|
444
455
|
const raw = readTaskYaml(taskYml);
|
|
445
456
|
const current = raw.workflow || {};
|
|
446
457
|
const merged = { ...current, ...patch, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
447
|
-
if (patch.runtimes) {
|
|
448
|
-
merged.
|
|
458
|
+
if (patch.runtimes || patch.agent_runs || current.runtimes || current.agent_runs) {
|
|
459
|
+
merged.agent_runs = {
|
|
460
|
+
...current.runtimes || {},
|
|
461
|
+
...current.agent_runs || {},
|
|
462
|
+
...patch.runtimes || {},
|
|
463
|
+
...patch.agent_runs || {}
|
|
464
|
+
};
|
|
465
|
+
delete merged.runtimes;
|
|
449
466
|
}
|
|
450
467
|
if (patch.decisions) {
|
|
451
468
|
const next = { ...current.decisions || {} };
|
|
@@ -901,7 +918,10 @@ var init_task_state2 = __esm({
|
|
|
901
918
|
});
|
|
902
919
|
|
|
903
920
|
// src/main.ts
|
|
904
|
-
import {
|
|
921
|
+
import { readFileSync } from "fs";
|
|
922
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
923
|
+
import path25 from "path";
|
|
924
|
+
import { Command as Command23 } from "commander";
|
|
905
925
|
|
|
906
926
|
// src/commands/source.ts
|
|
907
927
|
import { Command } from "commander";
|
|
@@ -1119,7 +1139,7 @@ async function request(method, pathname, body) {
|
|
|
1119
1139
|
});
|
|
1120
1140
|
} catch (error2) {
|
|
1121
1141
|
const err = new Error(
|
|
1122
|
-
`Cannot reach task0 API at ${apiBaseUrl()}. Start
|
|
1142
|
+
`Cannot reach task0 API at ${apiBaseUrl()}. Start the task0-server binary (download from GitHub Releases) or repoint TASK0_API_URL.`
|
|
1123
1143
|
);
|
|
1124
1144
|
err.cause = error2;
|
|
1125
1145
|
throw err;
|
|
@@ -1141,11 +1161,11 @@ async function request(method, pathname, body) {
|
|
|
1141
1161
|
return parsed;
|
|
1142
1162
|
}
|
|
1143
1163
|
var api = {
|
|
1144
|
-
get: (
|
|
1145
|
-
post: (
|
|
1146
|
-
put: (
|
|
1147
|
-
patch: (
|
|
1148
|
-
del: (
|
|
1164
|
+
get: (path26) => request("GET", path26),
|
|
1165
|
+
post: (path26, body) => request("POST", path26, body ?? {}),
|
|
1166
|
+
put: (path26, body) => request("PUT", path26, body ?? {}),
|
|
1167
|
+
patch: (path26, body) => request("PATCH", path26, body ?? {}),
|
|
1168
|
+
del: (path26) => request("DELETE", path26)
|
|
1149
1169
|
};
|
|
1150
1170
|
|
|
1151
1171
|
// src/commands/task/triage.ts
|
|
@@ -1154,23 +1174,23 @@ import chalk3 from "chalk";
|
|
|
1154
1174
|
import fs10 from "fs";
|
|
1155
1175
|
import path10 from "path";
|
|
1156
1176
|
|
|
1157
|
-
// src/core/
|
|
1158
|
-
async function
|
|
1159
|
-
return api.get(`/api/
|
|
1177
|
+
// src/core/agent-run-wait.ts
|
|
1178
|
+
async function getAgentRun(id) {
|
|
1179
|
+
return api.get(`/api/agent-runs/${encodeURIComponent(id)}`);
|
|
1160
1180
|
}
|
|
1161
|
-
async function
|
|
1181
|
+
async function waitForAgentRun(id, opts = {}) {
|
|
1162
1182
|
const interval = opts.intervalMs ?? 2e3;
|
|
1163
1183
|
const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : null;
|
|
1164
1184
|
let lastStatus = "";
|
|
1165
1185
|
while (true) {
|
|
1166
|
-
const snap = await
|
|
1186
|
+
const snap = await getAgentRun(id);
|
|
1167
1187
|
if (snap.status !== lastStatus) {
|
|
1168
1188
|
lastStatus = snap.status;
|
|
1169
1189
|
opts.onTick?.(snap);
|
|
1170
1190
|
}
|
|
1171
1191
|
if (snap.status === "done" || snap.status === "error") return snap;
|
|
1172
1192
|
if (deadline && Date.now() > deadline) {
|
|
1173
|
-
throw new Error(`Timeout waiting for
|
|
1193
|
+
throw new Error(`Timeout waiting for agent-run ${id} (last status: ${snap.status})`);
|
|
1174
1194
|
}
|
|
1175
1195
|
await new Promise((r) => setTimeout(r, interval));
|
|
1176
1196
|
}
|
|
@@ -1248,11 +1268,11 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1248
1268
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
1249
1269
|
body
|
|
1250
1270
|
);
|
|
1251
|
-
const
|
|
1252
|
-
if (!opts.json) console.log(chalk3.green(`triage runtime ${
|
|
1253
|
-
await updateWorkflow(loc.taskYml, {
|
|
1271
|
+
const agentRunId = resp.runtime.id;
|
|
1272
|
+
if (!opts.json) console.log(chalk3.green(`triage runtime ${agentRunId}`));
|
|
1273
|
+
await updateWorkflow(loc.taskYml, { agent_runs: { triage: agentRunId } });
|
|
1254
1274
|
if (opts.wait) {
|
|
1255
|
-
const final = await
|
|
1275
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
1256
1276
|
onTick: (s) => {
|
|
1257
1277
|
if (!opts.json) console.log(chalk3.dim(`[triage] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
1258
1278
|
}
|
|
@@ -1285,7 +1305,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1285
1305
|
console.log(chalk3.dim(`blockingQuestionCount: ${blockingQuestionCount}`));
|
|
1286
1306
|
} else {
|
|
1287
1307
|
console.log(JSON.stringify({
|
|
1288
|
-
|
|
1308
|
+
agentRunId,
|
|
1289
1309
|
issueOverview: "ISSUE.md",
|
|
1290
1310
|
issueFiles,
|
|
1291
1311
|
blockingQuestionCount
|
|
@@ -1293,7 +1313,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1293
1313
|
}
|
|
1294
1314
|
process.exit(blockingQuestionCount > 0 ? 2 : 0);
|
|
1295
1315
|
}
|
|
1296
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1316
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId }, null, 2));
|
|
1297
1317
|
} catch (err) {
|
|
1298
1318
|
console.error(chalk3.red(err.message));
|
|
1299
1319
|
process.exit(1);
|
|
@@ -1358,24 +1378,24 @@ var exec = new Command4("exec").description("Execute a plan against the task (cw
|
|
|
1358
1378
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
1359
1379
|
body
|
|
1360
1380
|
);
|
|
1361
|
-
const
|
|
1362
|
-
if (!opts.json) console.log(chalk4.green(`exec runtime ${
|
|
1363
|
-
await updateWorkflow(loc.taskYml, { phase: "executing",
|
|
1381
|
+
const agentRunId = resp.runtime.id;
|
|
1382
|
+
if (!opts.json) console.log(chalk4.green(`exec runtime ${agentRunId} (plan: ${planFile})`));
|
|
1383
|
+
await updateWorkflow(loc.taskYml, { phase: "executing", agent_runs: { exec: agentRunId } });
|
|
1364
1384
|
if (opts.wait) {
|
|
1365
|
-
const final = await
|
|
1385
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
1366
1386
|
onTick: (s) => {
|
|
1367
1387
|
if (!opts.json) console.log(chalk4.dim(`[exec] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
1368
1388
|
}
|
|
1369
1389
|
});
|
|
1370
1390
|
if (final.status === "done") {
|
|
1371
1391
|
if (!opts.json) console.log(chalk4.green("exec completed"));
|
|
1372
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1392
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, planFile, result: final.result }, null, 2));
|
|
1373
1393
|
return;
|
|
1374
1394
|
}
|
|
1375
1395
|
console.error(chalk4.red(`exec failed: ${final.error || "unknown"}`));
|
|
1376
1396
|
process.exit(1);
|
|
1377
1397
|
}
|
|
1378
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1398
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, planFile }, null, 2));
|
|
1379
1399
|
} catch (err) {
|
|
1380
1400
|
console.error(chalk4.red(err.message));
|
|
1381
1401
|
process.exit(1);
|
|
@@ -1432,13 +1452,16 @@ function preview(body, width = 60) {
|
|
|
1432
1452
|
const single = body.replace(/\s+/g, " ").trim();
|
|
1433
1453
|
return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
|
|
1434
1454
|
}
|
|
1435
|
-
function printRow(c) {
|
|
1455
|
+
function printRow(c, indent = "") {
|
|
1436
1456
|
const id = chalk6.cyan(c.id.padEnd(14));
|
|
1437
1457
|
const author = chalk6.magenta(c.author.padEnd(12));
|
|
1438
|
-
console.log(`${id} ${author} ${preview(c.body)} ${chalk6.dim(c.updated_at)}`);
|
|
1458
|
+
console.log(`${indent}${id} ${author} ${preview(c.body)} ${chalk6.dim(c.updated_at)}`);
|
|
1439
1459
|
}
|
|
1440
1460
|
function printDetail(c) {
|
|
1441
1461
|
console.log(`${chalk6.bold("id:")} ${c.id}`);
|
|
1462
|
+
if ("parent_comment_id" in c) {
|
|
1463
|
+
console.log(`${chalk6.bold("parent:")} ${c.parent_comment_id}`);
|
|
1464
|
+
}
|
|
1442
1465
|
console.log(`${chalk6.bold("author:")} ${c.author}`);
|
|
1443
1466
|
console.log(`${chalk6.bold("created:")} ${c.created_at}`);
|
|
1444
1467
|
console.log(`${chalk6.bold("updated:")} ${c.updated_at}`);
|
|
@@ -1466,17 +1489,22 @@ comment.command("list <taskId>").description("List comments on a task (taskId is
|
|
|
1466
1489
|
console.log(chalk6.dim("No comments."));
|
|
1467
1490
|
return;
|
|
1468
1491
|
}
|
|
1469
|
-
for (const c of comments)
|
|
1492
|
+
for (const c of comments) {
|
|
1493
|
+
printRow(c);
|
|
1494
|
+
for (const reply of c.replies ?? []) {
|
|
1495
|
+
printRow(reply, chalk6.dim(" \u21B3 "));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1470
1498
|
} catch (err) {
|
|
1471
1499
|
fail(err);
|
|
1472
1500
|
}
|
|
1473
1501
|
});
|
|
1474
|
-
comment.command("add <taskId>").description("Add a comment to a task").option("--body <text>", "Comment body (markdown)").option("--file <path>", "Read body from file (- for stdin)").option("--author <name>", "Author label (default: user)", "user").option("--json", "Output JSON").action(async (taskId, opts) => {
|
|
1502
|
+
comment.command("add <taskId>").description("Add a comment to a task").option("--body <text>", "Comment body (markdown)").option("--file <path>", "Read body from file (- for stdin)").option("--author <name>", "Author label (default: user)", "user").option("--reply-to <cmtId>", "Create a second-level reply under a top-level comment").option("--json", "Output JSON").action(async (taskId, opts) => {
|
|
1475
1503
|
try {
|
|
1476
1504
|
const body = readBodyFromOpts(opts);
|
|
1477
1505
|
const { comment: created } = await api.post(
|
|
1478
1506
|
`/api/tasks/${encodeURIComponent(taskId)}/comments`,
|
|
1479
|
-
{ body, author: opts.author }
|
|
1507
|
+
{ body, author: opts.author, parent_comment_id: opts.replyTo }
|
|
1480
1508
|
);
|
|
1481
1509
|
if (opts.json) {
|
|
1482
1510
|
console.log(JSON.stringify({ comment: created }, null, 2));
|
|
@@ -1811,61 +1839,9 @@ var ui = new Command9("ui").description("Launch the dashboard").option("-p, --po
|
|
|
1811
1839
|
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1812
1840
|
});
|
|
1813
1841
|
|
|
1814
|
-
// src/commands/
|
|
1842
|
+
// src/commands/models.ts
|
|
1815
1843
|
import { Command as Command10 } from "commander";
|
|
1816
|
-
import { spawn as spawn2 } from "child_process";
|
|
1817
|
-
import fs15 from "fs";
|
|
1818
|
-
import path14 from "path";
|
|
1819
1844
|
import chalk10 from "chalk";
|
|
1820
|
-
var SERVER_DIR = path14.resolve(
|
|
1821
|
-
import.meta.dirname,
|
|
1822
|
-
"..",
|
|
1823
|
-
"..",
|
|
1824
|
-
"..",
|
|
1825
|
-
"server"
|
|
1826
|
-
);
|
|
1827
|
-
var HTTP_ENTRY = path14.join(SERVER_DIR, "src", "main.ts");
|
|
1828
|
-
var CLI_TSX = path14.resolve(
|
|
1829
|
-
import.meta.dirname,
|
|
1830
|
-
"..",
|
|
1831
|
-
"..",
|
|
1832
|
-
"node_modules",
|
|
1833
|
-
".bin",
|
|
1834
|
-
"tsx"
|
|
1835
|
-
);
|
|
1836
|
-
var serve = new Command10("serve").description("Run the headless task0 API server").option("-p, --port <port>", "Port number", "4318").option("-H, --host <address>", "Bind address", "127.0.0.1").action((opts) => {
|
|
1837
|
-
if (!fs15.existsSync(HTTP_ENTRY)) {
|
|
1838
|
-
console.error(chalk10.red(`Server entry not found: ${HTTP_ENTRY}`));
|
|
1839
|
-
process.exit(1);
|
|
1840
|
-
}
|
|
1841
|
-
if (!fs15.existsSync(CLI_TSX)) {
|
|
1842
|
-
console.error(chalk10.red(`tsx not found at ${CLI_TSX}. Run \`pnpm install\` in the CLI package.`));
|
|
1843
|
-
process.exit(1);
|
|
1844
|
-
}
|
|
1845
|
-
console.log(chalk10.green(`Starting task0 API on http://${opts.host}:${opts.port}`));
|
|
1846
|
-
const child = spawn2(CLI_TSX, [HTTP_ENTRY], {
|
|
1847
|
-
cwd: SERVER_DIR,
|
|
1848
|
-
stdio: "inherit",
|
|
1849
|
-
env: {
|
|
1850
|
-
...process.env,
|
|
1851
|
-
TASK0_API_PORT: opts.port,
|
|
1852
|
-
TASK0_API_HOST: opts.host
|
|
1853
|
-
}
|
|
1854
|
-
});
|
|
1855
|
-
child.on("error", (err) => {
|
|
1856
|
-
console.error(chalk10.red(`Failed to start server: ${err.message}`));
|
|
1857
|
-
process.exit(1);
|
|
1858
|
-
});
|
|
1859
|
-
child.on("exit", (code) => {
|
|
1860
|
-
process.exit(code ?? 0);
|
|
1861
|
-
});
|
|
1862
|
-
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
1863
|
-
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1864
|
-
});
|
|
1865
|
-
|
|
1866
|
-
// src/commands/models.ts
|
|
1867
|
-
import { Command as Command11 } from "commander";
|
|
1868
|
-
import chalk11 from "chalk";
|
|
1869
1845
|
|
|
1870
1846
|
// ../../packages/shared/dist/types/agent.js
|
|
1871
1847
|
var AGENT_KINDS = ["coding", "llm_api", "workflow"];
|
|
@@ -1877,8 +1853,8 @@ function isCodingRuntimeAgent(value) {
|
|
|
1877
1853
|
}
|
|
1878
1854
|
|
|
1879
1855
|
// ../../packages/shared/dist/types/runtime.js
|
|
1880
|
-
var
|
|
1881
|
-
var
|
|
1856
|
+
var AGENT_RUN_STATUS_VALUES = ["starting", "running", "done", "error"];
|
|
1857
|
+
var AGENT_RUN_STATUS_SET = new Set(AGENT_RUN_STATUS_VALUES);
|
|
1882
1858
|
var RUNTIME_AGENTS = CODING_RUNTIME_AGENTS;
|
|
1883
1859
|
var isRuntimeAgent = isCodingRuntimeAgent;
|
|
1884
1860
|
var AGENT_MODEL_DEFAULTS = {
|
|
@@ -2063,31 +2039,31 @@ function refreshAgentModels(agent2) {
|
|
|
2063
2039
|
function parseAgent(raw) {
|
|
2064
2040
|
const trimmed = raw.trim();
|
|
2065
2041
|
if (!isRuntimeAgent(trimmed)) {
|
|
2066
|
-
console.error(
|
|
2042
|
+
console.error(chalk10.red(`Invalid agent "${raw}" (expected one of ${RUNTIME_AGENTS.join(", ")})`));
|
|
2067
2043
|
process.exit(1);
|
|
2068
2044
|
}
|
|
2069
2045
|
return trimmed;
|
|
2070
2046
|
}
|
|
2071
|
-
var models = new
|
|
2047
|
+
var models = new Command10("models").description("Manage cached agent model lists");
|
|
2072
2048
|
models.command("refresh").description("Refresh cached agent model lists into local config").option("-a, --agent <agent>", "Refresh a single agent only (claude-code, codex, cursor)").action((opts) => {
|
|
2073
2049
|
const agent2 = opts.agent ? parseAgent(opts.agent) : void 0;
|
|
2074
2050
|
try {
|
|
2075
2051
|
const summary = refreshAgentModels(agent2);
|
|
2076
2052
|
for (const result of summary.results) {
|
|
2077
|
-
const source2 = result.source === "command" ?
|
|
2078
|
-
console.log(
|
|
2053
|
+
const source2 = result.source === "command" ? chalk10.cyan("command") : chalk10.dim("builtin");
|
|
2054
|
+
console.log(chalk10.green(`Refreshed ${result.agent}`) + ` ${source2} ${result.modelCount} models`);
|
|
2079
2055
|
if (result.fetchCommand) {
|
|
2080
|
-
console.log(
|
|
2056
|
+
console.log(chalk10.dim(` ${result.fetchCommand}`));
|
|
2081
2057
|
}
|
|
2082
2058
|
}
|
|
2083
2059
|
for (const failure of summary.failures) {
|
|
2084
|
-
console.error(
|
|
2060
|
+
console.error(chalk10.red(`Failed ${failure.agent}: ${failure.error}`));
|
|
2085
2061
|
}
|
|
2086
2062
|
if (summary.failures.length > 0) {
|
|
2087
2063
|
process.exit(1);
|
|
2088
2064
|
}
|
|
2089
2065
|
} catch (error2) {
|
|
2090
|
-
console.error(
|
|
2066
|
+
console.error(chalk10.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
2091
2067
|
process.exit(1);
|
|
2092
2068
|
}
|
|
2093
2069
|
});
|
|
@@ -2111,70 +2087,73 @@ models.command("default <agent>").description("Get or set default model / effort
|
|
|
2111
2087
|
if (wroteModel || wroteEffort) {
|
|
2112
2088
|
saveConfig(config);
|
|
2113
2089
|
}
|
|
2114
|
-
console.log(
|
|
2115
|
-
console.log(` model: ${entry.defaultModel ??
|
|
2116
|
-
console.log(` effort: ${entry.defaultEffort ??
|
|
2090
|
+
console.log(chalk10.green(agent2));
|
|
2091
|
+
console.log(` model: ${entry.defaultModel ?? chalk10.dim("(unset)")}`);
|
|
2092
|
+
console.log(` effort: ${entry.defaultEffort ?? chalk10.dim("(unset)")}`);
|
|
2117
2093
|
});
|
|
2118
2094
|
|
|
2119
|
-
// src/commands/
|
|
2120
|
-
import
|
|
2121
|
-
import
|
|
2122
|
-
import {
|
|
2123
|
-
|
|
2124
|
-
|
|
2095
|
+
// src/commands/agent-run.ts
|
|
2096
|
+
import fs15 from "fs";
|
|
2097
|
+
import path14 from "path";
|
|
2098
|
+
import { Command as Command11 } from "commander";
|
|
2099
|
+
import chalk11 from "chalk";
|
|
2100
|
+
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
2101
|
+
import yaml6 from "js-yaml";
|
|
2102
|
+
var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
|
|
2103
|
+
agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
|
|
2125
2104
|
try {
|
|
2126
|
-
const { runtimes } = await api.get("/api/
|
|
2105
|
+
const { agent_runs: runtimes } = await api.get("/api/agent-runs");
|
|
2127
2106
|
const filtered = opts.task ? runtimes.filter((r) => r.taskId === opts.task) : runtimes;
|
|
2128
2107
|
if (opts.json) {
|
|
2129
2108
|
console.log(JSON.stringify(filtered, null, 2));
|
|
2130
2109
|
return;
|
|
2131
2110
|
}
|
|
2132
2111
|
if (filtered.length === 0) {
|
|
2133
|
-
console.log(
|
|
2112
|
+
console.log(chalk11.dim("No agent runs."));
|
|
2134
2113
|
return;
|
|
2135
2114
|
}
|
|
2136
2115
|
const objectIdWidth = Math.max(...filtered.map((r) => (r.objectId || "").length), 8);
|
|
2137
2116
|
for (const r of filtered) {
|
|
2138
|
-
const statusColor2 = r.status === "done" ?
|
|
2139
|
-
const objectId =
|
|
2140
|
-
console.log(`${statusColor2(r.status.padEnd(8))} ${objectId} ${
|
|
2117
|
+
const statusColor2 = r.status === "done" ? chalk11.green : r.status === "error" ? chalk11.red : r.status === "running" ? chalk11.cyan : chalk11.dim;
|
|
2118
|
+
const objectId = chalk11.cyan((r.objectId || "-").padEnd(objectIdWidth));
|
|
2119
|
+
console.log(`${statusColor2(r.status.padEnd(8))} ${objectId} ${chalk11.dim(r.type.padEnd(12))} ${r.taskId.padEnd(30)} ${chalk11.dim(r.id)}`);
|
|
2141
2120
|
}
|
|
2142
2121
|
} catch (err) {
|
|
2143
|
-
console.error(
|
|
2122
|
+
console.error(chalk11.red(err.message));
|
|
2144
2123
|
process.exit(1);
|
|
2145
2124
|
}
|
|
2146
2125
|
});
|
|
2147
|
-
|
|
2126
|
+
agentRun.command("show <id>").description("Show agent run details").option("--json", "Output JSON").action(async (id, opts) => {
|
|
2148
2127
|
try {
|
|
2149
|
-
const r = await
|
|
2128
|
+
const r = await getAgentRun(id);
|
|
2150
2129
|
if (opts.json) {
|
|
2151
2130
|
console.log(JSON.stringify(r, null, 2));
|
|
2152
2131
|
return;
|
|
2153
2132
|
}
|
|
2154
|
-
console.log(`${
|
|
2155
|
-
if (r.objectId) console.log(`${
|
|
2156
|
-
console.log(`${
|
|
2157
|
-
console.log(`${
|
|
2158
|
-
console.log(`${
|
|
2159
|
-
console.log(`${
|
|
2160
|
-
if (r.phase) console.log(`${
|
|
2161
|
-
if (r.error) console.log(`${
|
|
2162
|
-
console.log(`${
|
|
2133
|
+
console.log(`${chalk11.bold("id:")} ${r.id}`);
|
|
2134
|
+
if (r.objectId) console.log(`${chalk11.bold("object_id:")} ${r.objectId}`);
|
|
2135
|
+
console.log(`${chalk11.bold("type:")} ${r.type}`);
|
|
2136
|
+
console.log(`${chalk11.bold("agent:")} ${r.agent}`);
|
|
2137
|
+
console.log(`${chalk11.bold("task:")} ${r.taskId}`);
|
|
2138
|
+
console.log(`${chalk11.bold("status:")} ${r.status}`);
|
|
2139
|
+
if (r.phase) console.log(`${chalk11.bold("phase:")} ${r.phase}`);
|
|
2140
|
+
if (r.error) console.log(`${chalk11.red("error:")} ${r.error}`);
|
|
2141
|
+
console.log(`${chalk11.bold("tmux:")} ${r.tmuxSession} (alive: ${r.sessionAlive ?? "unknown"})`);
|
|
2163
2142
|
if (r.result) {
|
|
2164
|
-
console.log(
|
|
2143
|
+
console.log(chalk11.bold("result:"));
|
|
2165
2144
|
console.log(" " + JSON.stringify(r.result, null, 2).split("\n").join("\n "));
|
|
2166
2145
|
}
|
|
2167
2146
|
} catch (err) {
|
|
2168
|
-
console.error(
|
|
2147
|
+
console.error(chalk11.red(err.message));
|
|
2169
2148
|
process.exit(1);
|
|
2170
2149
|
}
|
|
2171
2150
|
});
|
|
2172
|
-
|
|
2151
|
+
agentRun.command("wait <id>").description("Block until agent run reaches done or error").option("--timeout <seconds>", "Max seconds to wait", "1800").option("--json", "Output final JSON").action(async (id, opts) => {
|
|
2173
2152
|
try {
|
|
2174
|
-
const final = await
|
|
2153
|
+
const final = await waitForAgentRun(id, {
|
|
2175
2154
|
timeoutMs: Number(opts.timeout) * 1e3,
|
|
2176
2155
|
onTick: (s) => {
|
|
2177
|
-
if (!opts.json) console.log(
|
|
2156
|
+
if (!opts.json) console.log(chalk11.dim(`[${s.status}] ${s.phase ?? ""}`));
|
|
2178
2157
|
}
|
|
2179
2158
|
});
|
|
2180
2159
|
if (opts.json) {
|
|
@@ -2182,41 +2161,120 @@ runtime.command("wait <id>").description("Block until runtime reaches done or er
|
|
|
2182
2161
|
}
|
|
2183
2162
|
process.exit(final.status === "done" ? 0 : 1);
|
|
2184
2163
|
} catch (err) {
|
|
2185
|
-
console.error(
|
|
2164
|
+
console.error(chalk11.red(err.message));
|
|
2186
2165
|
process.exit(1);
|
|
2187
2166
|
}
|
|
2188
2167
|
});
|
|
2189
|
-
|
|
2168
|
+
agentRun.command("cancel <id>").description("Cancel an agent run").action(async (id) => {
|
|
2190
2169
|
try {
|
|
2191
|
-
await api.post(`/api/
|
|
2192
|
-
console.log(
|
|
2170
|
+
await api.post(`/api/agent-runs/${encodeURIComponent(id)}/cancel`);
|
|
2171
|
+
console.log(chalk11.green(`Canceled ${id}`));
|
|
2193
2172
|
} catch (err) {
|
|
2194
|
-
console.error(
|
|
2173
|
+
console.error(chalk11.red(err.message));
|
|
2195
2174
|
process.exit(1);
|
|
2196
2175
|
}
|
|
2197
2176
|
});
|
|
2198
|
-
|
|
2177
|
+
agentRun.command("attach <id>").description("Attach to the tmux session backing an agent run").action(async (id) => {
|
|
2199
2178
|
try {
|
|
2200
|
-
const r = await
|
|
2179
|
+
const r = await getAgentRun(id);
|
|
2201
2180
|
if (!r.tmuxSession) {
|
|
2202
|
-
console.error(
|
|
2181
|
+
console.error(chalk11.red("No tmux session recorded for this agent run."));
|
|
2203
2182
|
process.exit(1);
|
|
2204
2183
|
}
|
|
2205
|
-
const child =
|
|
2184
|
+
const child = spawn2("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
|
|
2206
2185
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2207
2186
|
child.on("error", (err) => {
|
|
2208
|
-
console.error(
|
|
2187
|
+
console.error(chalk11.red(`Failed to attach: ${err.message}`));
|
|
2209
2188
|
process.exit(1);
|
|
2210
2189
|
});
|
|
2211
2190
|
} catch (err) {
|
|
2212
|
-
console.error(
|
|
2191
|
+
console.error(chalk11.red(err.message));
|
|
2213
2192
|
process.exit(1);
|
|
2214
2193
|
}
|
|
2215
2194
|
});
|
|
2195
|
+
function failProfile(message) {
|
|
2196
|
+
console.error(chalk11.red(message));
|
|
2197
|
+
process.exit(1);
|
|
2198
|
+
}
|
|
2199
|
+
function formatProfile(r) {
|
|
2200
|
+
const tag = r.system ? chalk11.dim("(system)") : r.scope ? chalk11.dim(`(${r.scope})`) : "";
|
|
2201
|
+
return `${chalk11.bold(r.slug.padEnd(20))} ${chalk11.dim(r.object_id.padEnd(14))} ${r.kind.padEnd(5)} ${chalk11.dim(r.exec.command.padEnd(20))} ${tag}`;
|
|
2202
|
+
}
|
|
2203
|
+
var profile = new Command11("profile").description("Manage RuntimeProfile templates (the executables / endpoints agents bind to)");
|
|
2204
|
+
profile.command("list").description("List visible runtime profiles (project + user + built-in cascade)").option("--include-system", "Include built-in system profiles (default true)").option("--json", "Output JSON").action(async (opts) => {
|
|
2205
|
+
try {
|
|
2206
|
+
const qs = opts.includeSystem === false ? "?include_system=false" : "";
|
|
2207
|
+
const result = await api.get(`/api/runtime-profiles${qs}`);
|
|
2208
|
+
if (opts.json) return console.log(JSON.stringify(result.runtimes, null, 2));
|
|
2209
|
+
if (!result.runtimes.length) {
|
|
2210
|
+
console.log(chalk11.dim("(no runtime profiles)"));
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
for (const r of result.runtimes) console.log(formatProfile(r));
|
|
2214
|
+
} catch (err) {
|
|
2215
|
+
failProfile(err.message);
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
profile.command("get <ref>").description("Show one runtime profile by slug or object_id").option("--json", "Output JSON").action(async (ref, opts) => {
|
|
2219
|
+
try {
|
|
2220
|
+
const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
|
|
2221
|
+
if (opts.json) return console.log(JSON.stringify(result.runtime, null, 2));
|
|
2222
|
+
console.log(formatProfile(result.runtime));
|
|
2223
|
+
console.log();
|
|
2224
|
+
console.log(chalk11.dim("--- exec ---"));
|
|
2225
|
+
console.log(yaml6.dump(result.runtime.exec, { lineWidth: 100 }));
|
|
2226
|
+
} catch (err) {
|
|
2227
|
+
const apiErr = err;
|
|
2228
|
+
if (apiErr.status === 404) failProfile(`not found: ${ref}`);
|
|
2229
|
+
failProfile(err.message);
|
|
2230
|
+
}
|
|
2231
|
+
});
|
|
2232
|
+
profile.command("create").description("Create a runtime profile from a YAML spec file").requiredOption("--from-file <file>", "YAML spec file with kind, slug, exec").option("--scope <scope>", "Storage scope: user (default) or project", "user").option("--project-root <path>", "Project root (required for --scope project)").action(async (opts) => {
|
|
2233
|
+
let parsed;
|
|
2234
|
+
try {
|
|
2235
|
+
parsed = yaml6.load(fs15.readFileSync(path14.resolve(opts.fromFile), "utf-8"));
|
|
2236
|
+
} catch (err) {
|
|
2237
|
+
failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
2238
|
+
}
|
|
2239
|
+
try {
|
|
2240
|
+
const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
|
|
2241
|
+
const result = await api.post("/api/runtime-profiles", body);
|
|
2242
|
+
console.log(chalk11.green(`created ${result.runtime.slug} (${result.runtime.object_id})`));
|
|
2243
|
+
} catch (err) {
|
|
2244
|
+
failProfile(err.message);
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
profile.command("edit <ref>").description("Open the runtime profile YAML in $EDITOR and save changes").action(async (ref) => {
|
|
2248
|
+
try {
|
|
2249
|
+
const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
|
|
2250
|
+
if (result.runtime.system) failProfile("cannot edit a system runtime profile");
|
|
2251
|
+
const tmp = path14.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
|
|
2252
|
+
fs15.writeFileSync(tmp, yaml6.dump(result.runtime, { lineWidth: 100 }), "utf-8");
|
|
2253
|
+
const editor = process.env.EDITOR || "vi";
|
|
2254
|
+
const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
|
|
2255
|
+
if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
|
|
2256
|
+
const updated = yaml6.load(fs15.readFileSync(tmp, "utf-8"));
|
|
2257
|
+
await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
|
|
2258
|
+
fs15.unlinkSync(tmp);
|
|
2259
|
+
console.log(chalk11.green(`updated ${ref}`));
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
failProfile(err.message);
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
profile.command("delete <ref>").description("Delete a runtime profile").action(async (ref) => {
|
|
2265
|
+
try {
|
|
2266
|
+
const result = await api.del(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
|
|
2267
|
+
if (!result.deleted) failProfile(`not deleted: ${ref}`);
|
|
2268
|
+
console.log(chalk11.green(`deleted ${ref}`));
|
|
2269
|
+
} catch (err) {
|
|
2270
|
+
failProfile(err.message);
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
agentRun.addCommand(profile);
|
|
2216
2274
|
|
|
2217
2275
|
// src/commands/plan.ts
|
|
2218
|
-
import { Command as
|
|
2219
|
-
import
|
|
2276
|
+
import { Command as Command12 } from "commander";
|
|
2277
|
+
import chalk12 from "chalk";
|
|
2220
2278
|
import fs16 from "fs";
|
|
2221
2279
|
import path15 from "path";
|
|
2222
2280
|
init_task_state2();
|
|
@@ -2225,27 +2283,27 @@ function resolveSkillFilePath3(projectRoot, skillName) {
|
|
|
2225
2283
|
const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
2226
2284
|
return fs16.existsSync(p) ? p : null;
|
|
2227
2285
|
}
|
|
2228
|
-
var plan = new
|
|
2286
|
+
var plan = new Command12("plan").description("Generate and refine plans");
|
|
2229
2287
|
plan.command("generate <objectId>").description("Generate plan(s) from an IDEA file \u2014 supports agent fan-out").option("-a, --agents <list>", "Comma-separated agents (claude-code,codex,cursor)", "codex,claude-code").option("--model <id>", "Model id or alias \u2014 only with a single agent; use `task0 models default` for fan-out").option("--effort <level>", "Reasoning effort \u2014 only with a single agent; use `task0 models default` for fan-out").option("--idea <file>", "IDEA file name (default: latest IDEA-NN.md)").option("--additional-prompt <text>", "Extra prompt content").option("--wait", "Wait for completion").option("--force", "Overwrite existing plan files").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
2230
2288
|
try {
|
|
2231
2289
|
const loc = resolveTaskByObjectId(objectId);
|
|
2232
2290
|
const ideaFile = opts.idea || latestArtifact(loc.taskDir, /^IDEA-\d+\.md$/);
|
|
2233
2291
|
if (!ideaFile) {
|
|
2234
|
-
console.error(
|
|
2292
|
+
console.error(chalk12.red(`No IDEA-NN.md found in ${loc.taskDir}. Pass --idea.`));
|
|
2235
2293
|
process.exit(1);
|
|
2236
2294
|
}
|
|
2237
2295
|
const agents = opts.agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
2238
2296
|
if (agents.length === 0) {
|
|
2239
|
-
console.error(
|
|
2297
|
+
console.error(chalk12.red("No agents specified."));
|
|
2240
2298
|
process.exit(1);
|
|
2241
2299
|
}
|
|
2242
2300
|
if ((opts.model || opts.effort) && agents.length > 1) {
|
|
2243
|
-
console.error(
|
|
2301
|
+
console.error(chalk12.red(
|
|
2244
2302
|
`--model/--effort only works with a single agent; got ${agents.length} (${agents.join(", ")}). Use \`task0 models default <agent>\` to set per-agent defaults for fan-out.`
|
|
2245
2303
|
));
|
|
2246
2304
|
process.exit(1);
|
|
2247
2305
|
}
|
|
2248
|
-
const warn = opts.json ? void 0 : (m) => console.error(
|
|
2306
|
+
const warn = opts.json ? void 0 : (m) => console.error(chalk12.dim(m));
|
|
2249
2307
|
const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_GENERATE_SKILL_NAME);
|
|
2250
2308
|
if (opts.model || opts.effort) {
|
|
2251
2309
|
resolveModelOptions(agents[0], { model: opts.model, effort: opts.effort }, warn);
|
|
@@ -2268,43 +2326,56 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2268
2326
|
prompt: promptForAgent(agent2),
|
|
2269
2327
|
...skillFilePath ? { skill_file_path: skillFilePath } : {}
|
|
2270
2328
|
};
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2329
|
+
const maxAttempts = 6;
|
|
2330
|
+
const backoffMs = 2e3;
|
|
2331
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2332
|
+
try {
|
|
2333
|
+
const resp = await api.post(
|
|
2334
|
+
`/api/agents/${encodeURIComponent(agent2)}/run`,
|
|
2335
|
+
body
|
|
2336
|
+
);
|
|
2337
|
+
return {
|
|
2338
|
+
agent: agent2,
|
|
2339
|
+
agentRunId: resp.runtime.id,
|
|
2340
|
+
sessionName: "",
|
|
2341
|
+
error: null
|
|
2342
|
+
};
|
|
2343
|
+
} catch (err) {
|
|
2344
|
+
const e = err;
|
|
2345
|
+
const busy = e.status === 409 && e.body?.error === "agent_busy";
|
|
2346
|
+
if (busy && attempt < maxAttempts) {
|
|
2347
|
+
if (!opts.json) console.error(chalk12.dim(`[${agent2}] busy, retry ${attempt}/${maxAttempts - 1}`));
|
|
2348
|
+
await new Promise((r) => setTimeout(r, backoffMs));
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2351
|
+
const msg = e.body?.message || err.message;
|
|
2352
|
+
return { agent: agent2, agentRunId: "", sessionName: "", error: msg };
|
|
2353
|
+
}
|
|
2284
2354
|
}
|
|
2355
|
+
return { agent: agent2, agentRunId: "", sessionName: "", error: "agent_busy: exhausted retries" };
|
|
2285
2356
|
}));
|
|
2286
2357
|
const launched = kicks.filter((k) => !k.error);
|
|
2287
2358
|
const failed = kicks.filter((k) => k.error);
|
|
2288
2359
|
if (!opts.json) {
|
|
2289
2360
|
for (const k of launched) {
|
|
2290
|
-
console.log(
|
|
2361
|
+
console.log(chalk12.green(`[${k.agent}]`) + ` runtime ${k.agentRunId}`);
|
|
2291
2362
|
}
|
|
2292
2363
|
for (const k of failed) {
|
|
2293
|
-
console.error(
|
|
2364
|
+
console.error(chalk12.red(`[${k.agent}] ${k.error}`));
|
|
2294
2365
|
}
|
|
2295
2366
|
}
|
|
2296
|
-
const
|
|
2297
|
-
for (const k of launched)
|
|
2367
|
+
const agentRunIds = {};
|
|
2368
|
+
for (const k of launched) agentRunIds[k.agent] = k.agentRunId;
|
|
2298
2369
|
await updateWorkflow(loc.taskYml, {
|
|
2299
|
-
|
|
2370
|
+
agent_runs: { plan: agentRunIds }
|
|
2300
2371
|
});
|
|
2301
2372
|
const planFiles = [];
|
|
2302
2373
|
if (opts.wait) {
|
|
2303
2374
|
const results = await Promise.all(launched.map(async (k) => {
|
|
2304
2375
|
try {
|
|
2305
|
-
const final = await
|
|
2376
|
+
const final = await waitForAgentRun(k.agentRunId, {
|
|
2306
2377
|
onTick: (s) => {
|
|
2307
|
-
if (!opts.json) console.log(
|
|
2378
|
+
if (!opts.json) console.log(chalk12.dim(`[${k.agent}] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2308
2379
|
}
|
|
2309
2380
|
});
|
|
2310
2381
|
return { agent: k.agent, final, error: null };
|
|
@@ -2317,10 +2388,10 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2317
2388
|
const planFile = r.final.result?.planFile;
|
|
2318
2389
|
if (planFile) {
|
|
2319
2390
|
planFiles.push(planFile);
|
|
2320
|
-
if (!opts.json) console.log(
|
|
2391
|
+
if (!opts.json) console.log(chalk12.green(`[${r.agent}] ${planFile}`));
|
|
2321
2392
|
}
|
|
2322
2393
|
} else {
|
|
2323
|
-
if (!opts.json) console.error(
|
|
2394
|
+
if (!opts.json) console.error(chalk12.red(`[${r.agent}] failed: ${r.error || r.final?.error || "unknown"}`));
|
|
2324
2395
|
}
|
|
2325
2396
|
}
|
|
2326
2397
|
const existingPlanFiles = readWorkflow(loc.taskYml).plan_files || [];
|
|
@@ -2334,7 +2405,7 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2334
2405
|
}
|
|
2335
2406
|
if (failed.length > 0) process.exit(1);
|
|
2336
2407
|
} catch (err) {
|
|
2337
|
-
console.error(
|
|
2408
|
+
console.error(chalk12.red(err.message));
|
|
2338
2409
|
process.exit(1);
|
|
2339
2410
|
}
|
|
2340
2411
|
});
|
|
@@ -2345,7 +2416,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2345
2416
|
const files = fs16.readdirSync(loc.taskDir);
|
|
2346
2417
|
const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
|
|
2347
2418
|
if (planFiles.length === 0) {
|
|
2348
|
-
console.error(
|
|
2419
|
+
console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
|
|
2349
2420
|
process.exit(1);
|
|
2350
2421
|
}
|
|
2351
2422
|
const idx = planFiles[0].match(/^PLAN-(\d+)/)?.[1] || nextArtifactIndex(loc.taskDir, "PLAN");
|
|
@@ -2354,7 +2425,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2354
2425
|
resolveModelOptions(
|
|
2355
2426
|
opts.agent,
|
|
2356
2427
|
{ model: opts.model, effort: opts.effort },
|
|
2357
|
-
opts.json ? void 0 : (m) => console.error(
|
|
2428
|
+
opts.json ? void 0 : (m) => console.error(chalk12.dim(m))
|
|
2358
2429
|
);
|
|
2359
2430
|
}
|
|
2360
2431
|
const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_REFINE_SKILL_NAME);
|
|
@@ -2373,15 +2444,15 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2373
2444
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
2374
2445
|
body
|
|
2375
2446
|
);
|
|
2376
|
-
const
|
|
2377
|
-
if (!opts.json) console.log(
|
|
2447
|
+
const agentRunId = resp.runtime.id;
|
|
2448
|
+
if (!opts.json) console.log(chalk12.green(`refine runtime ${agentRunId}`));
|
|
2378
2449
|
await updateWorkflow(loc.taskYml, {
|
|
2379
|
-
|
|
2450
|
+
agent_runs: { refine: agentRunId }
|
|
2380
2451
|
});
|
|
2381
2452
|
if (opts.wait) {
|
|
2382
|
-
const final = await
|
|
2453
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
2383
2454
|
onTick: (s) => {
|
|
2384
|
-
if (!opts.json) console.log(
|
|
2455
|
+
if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2385
2456
|
}
|
|
2386
2457
|
});
|
|
2387
2458
|
const refinedPath = path15.join(loc.taskDir, refinedFile);
|
|
@@ -2391,29 +2462,29 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2391
2462
|
phase: "refined",
|
|
2392
2463
|
refined_plan_file: refinedFile
|
|
2393
2464
|
});
|
|
2394
|
-
if (!opts.json) console.log(
|
|
2395
|
-
if (opts.json) console.log(JSON.stringify({
|
|
2465
|
+
if (!opts.json) console.log(chalk12.green(refinedFile));
|
|
2466
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
|
|
2396
2467
|
return;
|
|
2397
2468
|
}
|
|
2398
2469
|
if (final.status !== "done") {
|
|
2399
|
-
console.error(
|
|
2470
|
+
console.error(chalk12.red(`refine failed: ${final.error || "unknown"}`));
|
|
2400
2471
|
} else if (!wrote) {
|
|
2401
|
-
console.error(
|
|
2472
|
+
console.error(chalk12.red(`refine completed but ${refinedFile} was not written`));
|
|
2402
2473
|
}
|
|
2403
2474
|
process.exit(1);
|
|
2404
2475
|
}
|
|
2405
|
-
if (opts.json) console.log(JSON.stringify({
|
|
2476
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
|
|
2406
2477
|
} catch (err) {
|
|
2407
|
-
console.error(
|
|
2478
|
+
console.error(chalk12.red(err.message));
|
|
2408
2479
|
process.exit(1);
|
|
2409
2480
|
}
|
|
2410
2481
|
});
|
|
2411
2482
|
|
|
2412
2483
|
// src/commands/run.ts
|
|
2413
2484
|
init_task_state2();
|
|
2414
|
-
import { Command as
|
|
2415
|
-
import
|
|
2416
|
-
import { spawnSync as
|
|
2485
|
+
import { Command as Command13 } from "commander";
|
|
2486
|
+
import chalk13 from "chalk";
|
|
2487
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2417
2488
|
var PHASES = ["triage", "plan", "refine", "exec"];
|
|
2418
2489
|
var PAUSE_AFTER = /* @__PURE__ */ new Set(["refine"]);
|
|
2419
2490
|
var CHECKPOINT_EXIT = 78;
|
|
@@ -2432,13 +2503,13 @@ function nextPhaseFromWorkflow(wf) {
|
|
|
2432
2503
|
return null;
|
|
2433
2504
|
}
|
|
2434
2505
|
}
|
|
2435
|
-
var run = new
|
|
2506
|
+
var run = new Command13("run").description("Run the full task orchestration (triage \u2192 plan \u2192 refine \u2192 exec)").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--from <phase>", "Start from phase (triage|plan|refine|exec)").option("--to <phase>", "Stop after phase").option("--auto", "Skip pauses (no exit 78 at checkpoints)").option("--planners <list>", "Comma-separated plan agents", "codex,claude-code").option("--executor <agent>", "Executor agent", "claude-code").option("--triage-agent <agent>", "Triage agent", "claude-code").option("--refine-agent <agent>", "Refine agent", "claude-code").option("--json", "Output JSON summary at end").action(async (objectId, opts) => {
|
|
2436
2507
|
try {
|
|
2437
2508
|
const loc = resolveTaskByObjectId(objectId);
|
|
2438
2509
|
const wf = readWorkflow(loc.taskYml);
|
|
2439
2510
|
const fromCandidate = opts.from ?? nextPhaseFromWorkflow(wf);
|
|
2440
2511
|
if (!fromCandidate) {
|
|
2441
|
-
if (!opts.json) console.log(
|
|
2512
|
+
if (!opts.json) console.log(chalk13.dim(`Nothing to do \u2014 workflow phase is "${wf.phase}".`));
|
|
2442
2513
|
return;
|
|
2443
2514
|
}
|
|
2444
2515
|
const from = fromCandidate;
|
|
@@ -2449,23 +2520,23 @@ var run = new Command14("run").description("Run the full task orchestration (tri
|
|
|
2449
2520
|
const toIdx = PHASES.indexOf(to);
|
|
2450
2521
|
if (fromIdx > toIdx) throw new Error(`--from (${from}) is past --to (${to})`);
|
|
2451
2522
|
if (!opts.json) {
|
|
2452
|
-
console.log(
|
|
2523
|
+
console.log(chalk13.bold(`task0 run ${objectId}`) + chalk13.dim(` \u2014 ${from} \u2192 ${to}`));
|
|
2453
2524
|
}
|
|
2454
2525
|
for (let i = fromIdx; i <= toIdx; i++) {
|
|
2455
2526
|
const phase = PHASES[i];
|
|
2456
|
-
if (!opts.json) console.log(
|
|
2527
|
+
if (!opts.json) console.log(chalk13.bold.cyan(`
|
|
2457
2528
|
\u25B6 ${phase}`));
|
|
2458
2529
|
const code = runPhase(phase, objectId, opts);
|
|
2459
2530
|
if (code !== 0) {
|
|
2460
|
-
console.error(
|
|
2531
|
+
console.error(chalk13.red(`Phase "${phase}" failed (exit ${code}). Resume with: task0 run ${objectId} --from ${phase}`));
|
|
2461
2532
|
process.exit(code);
|
|
2462
2533
|
}
|
|
2463
2534
|
if (!opts.auto && PAUSE_AFTER.has(phase) && i < toIdx) {
|
|
2464
2535
|
const next = PHASES[i + 1];
|
|
2465
2536
|
if (!opts.json) {
|
|
2466
|
-
console.log(
|
|
2537
|
+
console.log(chalk13.yellow(`
|
|
2467
2538
|
\u23F8 checkpoint after ${phase}. Review and resume with:`));
|
|
2468
|
-
console.log(
|
|
2539
|
+
console.log(chalk13.yellow(` task0 run ${objectId} --from ${next}`));
|
|
2469
2540
|
}
|
|
2470
2541
|
process.exit(CHECKPOINT_EXIT);
|
|
2471
2542
|
}
|
|
@@ -2474,11 +2545,11 @@ var run = new Command14("run").description("Run the full task orchestration (tri
|
|
|
2474
2545
|
if (opts.json) {
|
|
2475
2546
|
console.log(JSON.stringify({ objectId, from, to, workflow: finalWf }, null, 2));
|
|
2476
2547
|
} else {
|
|
2477
|
-
console.log(
|
|
2548
|
+
console.log(chalk13.green(`
|
|
2478
2549
|
\u2713 done (phase: ${finalWf.phase})`));
|
|
2479
2550
|
}
|
|
2480
2551
|
} catch (err) {
|
|
2481
|
-
console.error(
|
|
2552
|
+
console.error(chalk13.red(err.message));
|
|
2482
2553
|
process.exit(1);
|
|
2483
2554
|
}
|
|
2484
2555
|
});
|
|
@@ -2500,7 +2571,7 @@ function runPhase(phase, objectId, opts) {
|
|
|
2500
2571
|
args = ["task", "exec", objectId, "--wait", "--agent", opts.executor];
|
|
2501
2572
|
break;
|
|
2502
2573
|
}
|
|
2503
|
-
const r =
|
|
2574
|
+
const r = spawnSync4(node, [cli, ...args], { stdio: "inherit" });
|
|
2504
2575
|
return r.status ?? 1;
|
|
2505
2576
|
}
|
|
2506
2577
|
run.command("status <objectId>").description("Show current workflow state").option("--json", "Output JSON").action((objectId, opts) => {
|
|
@@ -2511,29 +2582,30 @@ run.command("status <objectId>").description("Show current workflow state").opti
|
|
|
2511
2582
|
console.log(JSON.stringify(wf, null, 2));
|
|
2512
2583
|
return;
|
|
2513
2584
|
}
|
|
2514
|
-
console.log(`${
|
|
2585
|
+
console.log(`${chalk13.bold("phase:")} ${wf.phase ?? chalk13.dim("(not started)")}`);
|
|
2515
2586
|
if (wf.issue_overview) {
|
|
2516
2587
|
const extra = wf.issue_files?.length ? ` (+${wf.issue_files.length} details)` : "";
|
|
2517
|
-
console.log(`${
|
|
2518
|
-
}
|
|
2519
|
-
if (wf.plan_files?.length) console.log(`${
|
|
2520
|
-
if (wf.refined_plan_file) console.log(`${
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2588
|
+
console.log(`${chalk13.bold("issues:")} ${wf.issue_overview}${extra}`);
|
|
2589
|
+
}
|
|
2590
|
+
if (wf.plan_files?.length) console.log(`${chalk13.bold("plans:")} ${wf.plan_files.join(", ")}`);
|
|
2591
|
+
if (wf.refined_plan_file) console.log(`${chalk13.bold("refined:")} ${wf.refined_plan_file}`);
|
|
2592
|
+
const phaseRuns = wf.agent_runs ?? wf.runtimes;
|
|
2593
|
+
if (phaseRuns) {
|
|
2594
|
+
console.log(chalk13.bold("agent_runs:"));
|
|
2595
|
+
for (const [k, v] of Object.entries(phaseRuns)) {
|
|
2524
2596
|
console.log(` ${k.padEnd(8)} ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
2525
2597
|
}
|
|
2526
2598
|
}
|
|
2527
|
-
if (wf.updated_at) console.log(
|
|
2599
|
+
if (wf.updated_at) console.log(chalk13.dim(`updated_at: ${wf.updated_at}`));
|
|
2528
2600
|
} catch (err) {
|
|
2529
|
-
console.error(
|
|
2601
|
+
console.error(chalk13.red(err.message));
|
|
2530
2602
|
process.exit(1);
|
|
2531
2603
|
}
|
|
2532
2604
|
});
|
|
2533
2605
|
|
|
2534
2606
|
// src/commands/okr.ts
|
|
2535
|
-
import { Command as
|
|
2536
|
-
import
|
|
2607
|
+
import { Command as Command14 } from "commander";
|
|
2608
|
+
import chalk14 from "chalk";
|
|
2537
2609
|
|
|
2538
2610
|
// src/lib/project.ts
|
|
2539
2611
|
import path16 from "path";
|
|
@@ -2566,7 +2638,7 @@ function resolveProjectName(opts) {
|
|
|
2566
2638
|
|
|
2567
2639
|
// src/commands/okr.ts
|
|
2568
2640
|
function fail3(message) {
|
|
2569
|
-
console.error(
|
|
2641
|
+
console.error(chalk14.red(message));
|
|
2570
2642
|
process.exit(1);
|
|
2571
2643
|
}
|
|
2572
2644
|
function parseNumber(value) {
|
|
@@ -2603,11 +2675,11 @@ function countKRs(plan3) {
|
|
|
2603
2675
|
return plan3.objectives.reduce((sum, o) => sum + o.key_results.length, 0);
|
|
2604
2676
|
}
|
|
2605
2677
|
function printPlanSummary(plan3) {
|
|
2606
|
-
const oid =
|
|
2678
|
+
const oid = chalk14.cyan((plan3.object_id || "-").padEnd(10));
|
|
2607
2679
|
const id = plan3.id.padEnd(16);
|
|
2608
|
-
const status =
|
|
2680
|
+
const status = chalk14.yellow(plan3.status.padEnd(8));
|
|
2609
2681
|
const range = `${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`;
|
|
2610
|
-
const counts =
|
|
2682
|
+
const counts = chalk14.dim(
|
|
2611
2683
|
`${plan3.objectives.length} obj \xB7 ${countKRs(plan3)} kr \xB7 ${plan3.milestones.length} ms`
|
|
2612
2684
|
);
|
|
2613
2685
|
console.log(`${oid} ${id} ${status} ${range} ${counts} ${plan3.title}`);
|
|
@@ -2626,37 +2698,37 @@ function krProgress(kr2) {
|
|
|
2626
2698
|
}
|
|
2627
2699
|
function printPlanDetail(plan3) {
|
|
2628
2700
|
console.log(
|
|
2629
|
-
`${
|
|
2701
|
+
`${chalk14.bold(plan3.id)} ${chalk14.dim(plan3.object_id || "")} ${chalk14.yellow(plan3.status)} ${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`
|
|
2630
2702
|
);
|
|
2631
2703
|
if (plan3.title) console.log(` ${plan3.title}`);
|
|
2632
2704
|
for (const obj of plan3.objectives) {
|
|
2633
2705
|
console.log(
|
|
2634
|
-
` ${
|
|
2706
|
+
` ${chalk14.bold(obj.id)} ${obj.title} ${chalk14.dim(`(${obj.status})`)} ${chalk14.dim(obj.object_id || "")}`
|
|
2635
2707
|
);
|
|
2636
2708
|
for (const kr2 of obj.key_results) {
|
|
2637
2709
|
const unit = kr2.unit ? kr2.unit : "";
|
|
2638
2710
|
const vals = kr2.target_value !== null ? `${kr2.start_value ?? 0}${unit}\u2192${kr2.target_value}${unit} now ${kr2.current_value ?? 0}${unit}` : "\u2014";
|
|
2639
2711
|
console.log(
|
|
2640
|
-
` ${kr2.id.padEnd(20)} ${vals} ${krProgress(kr2)} ${
|
|
2712
|
+
` ${kr2.id.padEnd(20)} ${vals} ${krProgress(kr2)} ${chalk14.dim(`(${kr2.status}/${kr2.confidence})`)} ${chalk14.dim(kr2.object_id || "")}`
|
|
2641
2713
|
);
|
|
2642
2714
|
}
|
|
2643
2715
|
}
|
|
2644
2716
|
for (const ms of plan3.milestones) {
|
|
2645
|
-
const links = ms.linked_kr_ids.length ?
|
|
2717
|
+
const links = ms.linked_kr_ids.length ? chalk14.dim(`\u2192 ${ms.linked_kr_ids.join(",")}`) : "";
|
|
2646
2718
|
console.log(
|
|
2647
|
-
` ${
|
|
2719
|
+
` ${chalk14.bold(ms.id)} ${dateShort(ms.due_at)} ${ms.title} ${chalk14.dim(`(${ms.status})`)} ${links} ${chalk14.dim(ms.object_id || "")}`
|
|
2648
2720
|
);
|
|
2649
2721
|
}
|
|
2650
2722
|
if (plan3.task_kr_links.length) {
|
|
2651
|
-
console.log(
|
|
2723
|
+
console.log(chalk14.dim("links:"));
|
|
2652
2724
|
for (const link of plan3.task_kr_links) {
|
|
2653
2725
|
console.log(
|
|
2654
|
-
` ${link.task_id} \u2192 ${link.kr_id} ${
|
|
2726
|
+
` ${link.task_id} \u2192 ${link.kr_id} ${chalk14.dim(`${link.contribution} w=${link.weight}`)}`
|
|
2655
2727
|
);
|
|
2656
2728
|
}
|
|
2657
2729
|
}
|
|
2658
2730
|
}
|
|
2659
|
-
var okr = new
|
|
2731
|
+
var okr = new Command14("okr").description(
|
|
2660
2732
|
"Manage OKR plans, objectives, key results, milestones"
|
|
2661
2733
|
);
|
|
2662
2734
|
var plan2 = okr.command("plan").description("OKR plans");
|
|
@@ -2690,7 +2762,7 @@ plan2.command("create <plan-id>").description("Create a new OKR plan").requiredO
|
|
|
2690
2762
|
};
|
|
2691
2763
|
const { plan: view } = await api.post(planBasePath(name), body);
|
|
2692
2764
|
if (maybeJson(view, opts)) return;
|
|
2693
|
-
console.log(
|
|
2765
|
+
console.log(chalk14.green("ok"), view.id, chalk14.dim(view.object_id || ""));
|
|
2694
2766
|
printPlanSummary(view);
|
|
2695
2767
|
});
|
|
2696
2768
|
}
|
|
@@ -2709,7 +2781,7 @@ plan2.command("update <plan-id>").description("Update plan fields").option("-t,
|
|
|
2709
2781
|
body
|
|
2710
2782
|
);
|
|
2711
2783
|
if (maybeJson(view, opts)) return;
|
|
2712
|
-
console.log(
|
|
2784
|
+
console.log(chalk14.green("updated"), view.id, chalk14.dim(view.object_id || ""));
|
|
2713
2785
|
printPlanSummary(view);
|
|
2714
2786
|
});
|
|
2715
2787
|
}
|
|
@@ -2732,10 +2804,10 @@ objective.command("create <plan-id> <objective-id>").description("Create an obje
|
|
|
2732
2804
|
);
|
|
2733
2805
|
if (maybeJson(view, opts)) return;
|
|
2734
2806
|
console.log(
|
|
2735
|
-
|
|
2807
|
+
chalk14.green("ok"),
|
|
2736
2808
|
view.id,
|
|
2737
|
-
|
|
2738
|
-
|
|
2809
|
+
chalk14.dim(view.object_id || ""),
|
|
2810
|
+
chalk14.dim(`(${view.status})`),
|
|
2739
2811
|
view.title
|
|
2740
2812
|
);
|
|
2741
2813
|
});
|
|
@@ -2756,7 +2828,7 @@ objective.command("update <plan-id> <objective-id>").description("Update objecti
|
|
|
2756
2828
|
body
|
|
2757
2829
|
);
|
|
2758
2830
|
if (maybeJson(view, opts)) return;
|
|
2759
|
-
console.log(
|
|
2831
|
+
console.log(chalk14.green("updated"), view.id, chalk14.dim(`(${view.status})`), view.title);
|
|
2760
2832
|
});
|
|
2761
2833
|
}
|
|
2762
2834
|
);
|
|
@@ -2784,10 +2856,10 @@ kr.command("create <plan-id> <objective-id> <kr-id>").description("Create a key
|
|
|
2784
2856
|
);
|
|
2785
2857
|
if (maybeJson(view, opts)) return;
|
|
2786
2858
|
console.log(
|
|
2787
|
-
|
|
2859
|
+
chalk14.green("ok"),
|
|
2788
2860
|
view.id,
|
|
2789
|
-
|
|
2790
|
-
|
|
2861
|
+
chalk14.dim(view.object_id || ""),
|
|
2862
|
+
chalk14.dim(`(${view.status}/${view.confidence})`),
|
|
2791
2863
|
view.title
|
|
2792
2864
|
);
|
|
2793
2865
|
});
|
|
@@ -2815,9 +2887,9 @@ kr.command("update <plan-id> <kr-id>").description("Update a key result").option
|
|
|
2815
2887
|
);
|
|
2816
2888
|
if (maybeJson(view, opts)) return;
|
|
2817
2889
|
console.log(
|
|
2818
|
-
|
|
2890
|
+
chalk14.green("updated"),
|
|
2819
2891
|
view.id,
|
|
2820
|
-
|
|
2892
|
+
chalk14.dim(`(${view.status}/${view.confidence})`),
|
|
2821
2893
|
view.title
|
|
2822
2894
|
);
|
|
2823
2895
|
});
|
|
@@ -2837,10 +2909,10 @@ kr.command("progress <plan-id> <kr-id>").description("Update key result progress
|
|
|
2837
2909
|
);
|
|
2838
2910
|
if (maybeJson(view, opts)) return;
|
|
2839
2911
|
console.log(
|
|
2840
|
-
|
|
2912
|
+
chalk14.green("progress"),
|
|
2841
2913
|
view.id,
|
|
2842
2914
|
krProgress(view),
|
|
2843
|
-
|
|
2915
|
+
chalk14.dim(`(${view.status}/${view.confidence})`)
|
|
2844
2916
|
);
|
|
2845
2917
|
});
|
|
2846
2918
|
}
|
|
@@ -2864,11 +2936,11 @@ milestone.command("create <plan-id> <milestone-id>").description("Create a miles
|
|
|
2864
2936
|
);
|
|
2865
2937
|
if (maybeJson(view, opts)) return;
|
|
2866
2938
|
console.log(
|
|
2867
|
-
|
|
2939
|
+
chalk14.green("ok"),
|
|
2868
2940
|
view.id,
|
|
2869
|
-
|
|
2941
|
+
chalk14.dim(view.object_id || ""),
|
|
2870
2942
|
dateShort(view.due_at),
|
|
2871
|
-
|
|
2943
|
+
chalk14.dim(`(${view.status})`),
|
|
2872
2944
|
view.title
|
|
2873
2945
|
);
|
|
2874
2946
|
});
|
|
@@ -2891,10 +2963,10 @@ milestone.command("update <plan-id> <milestone-id>").description("Update a miles
|
|
|
2891
2963
|
);
|
|
2892
2964
|
if (maybeJson(view, opts)) return;
|
|
2893
2965
|
console.log(
|
|
2894
|
-
|
|
2966
|
+
chalk14.green("updated"),
|
|
2895
2967
|
view.id,
|
|
2896
2968
|
dateShort(view.due_at),
|
|
2897
|
-
|
|
2969
|
+
chalk14.dim(`(${view.status})`),
|
|
2898
2970
|
view.title
|
|
2899
2971
|
);
|
|
2900
2972
|
});
|
|
@@ -2916,9 +2988,9 @@ okr.command("link <plan-id>").description("Link a task to a key result").require
|
|
|
2916
2988
|
);
|
|
2917
2989
|
if (maybeJson(link, opts)) return;
|
|
2918
2990
|
console.log(
|
|
2919
|
-
|
|
2991
|
+
chalk14.green("linked"),
|
|
2920
2992
|
`${link.task_id} \u2192 ${link.kr_id}`,
|
|
2921
|
-
|
|
2993
|
+
chalk14.dim(`${link.contribution} w=${link.weight}`)
|
|
2922
2994
|
);
|
|
2923
2995
|
});
|
|
2924
2996
|
}
|
|
@@ -2930,15 +3002,15 @@ okr.command("unlink <plan-id>").description("Unlink a task from a key result").r
|
|
|
2930
3002
|
`${planEncodedPath(name, planId)}/task-kr-links/${encodeURIComponent(opts.task)}/${encodeURIComponent(opts.kr)}`
|
|
2931
3003
|
);
|
|
2932
3004
|
if (maybeJson(result, opts)) return;
|
|
2933
|
-
console.log(
|
|
3005
|
+
console.log(chalk14.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
|
|
2934
3006
|
});
|
|
2935
3007
|
}
|
|
2936
3008
|
);
|
|
2937
3009
|
|
|
2938
3010
|
// src/commands/note.ts
|
|
2939
|
-
import { Command as
|
|
2940
|
-
import
|
|
2941
|
-
var note = new
|
|
3011
|
+
import { Command as Command15 } from "commander";
|
|
3012
|
+
import chalk15 from "chalk";
|
|
3013
|
+
var note = new Command15("note").description("Manage inbox notes");
|
|
2942
3014
|
async function fetchAllInboxNotes() {
|
|
2943
3015
|
const { inboxes } = await api.get("/api/inboxes");
|
|
2944
3016
|
const results = [];
|
|
@@ -2980,33 +3052,33 @@ function preview2(body, width = 60) {
|
|
|
2980
3052
|
return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
|
|
2981
3053
|
}
|
|
2982
3054
|
function printNoteRow(n, inboxName) {
|
|
2983
|
-
const oid =
|
|
2984
|
-
const id =
|
|
2985
|
-
const tags = n.tags.length ?
|
|
2986
|
-
const linked = n.linked_task_id ?
|
|
2987
|
-
const inboxPart = inboxName ?
|
|
2988
|
-
console.log(`${oid} ${id} ${inboxPart}${preview2(n.body)} ${tags} ${linked} ${
|
|
3055
|
+
const oid = chalk15.cyan((n.object_id || "-").padEnd(14));
|
|
3056
|
+
const id = chalk15.dim(n.id.padEnd(10));
|
|
3057
|
+
const tags = n.tags.length ? chalk15.yellow(n.tags.join(",")) : chalk15.dim("-");
|
|
3058
|
+
const linked = n.linked_task_id ? chalk15.magenta(n.linked_task_id) : chalk15.dim("-");
|
|
3059
|
+
const inboxPart = inboxName ? chalk15.dim(`[${inboxName}] `) : "";
|
|
3060
|
+
console.log(`${oid} ${id} ${inboxPart}${preview2(n.body)} ${tags} ${linked} ${chalk15.dim(n.updated_at)}`);
|
|
2989
3061
|
}
|
|
2990
3062
|
function printNoteDetail(n, inboxId) {
|
|
2991
|
-
if (n.object_id) console.log(`${
|
|
2992
|
-
console.log(`${
|
|
2993
|
-
if (inboxId) console.log(`${
|
|
2994
|
-
console.log(`${
|
|
2995
|
-
if (n.linked_task_id) console.log(`${
|
|
2996
|
-
if (n.linked_project) console.log(`${
|
|
2997
|
-
console.log(`${
|
|
2998
|
-
console.log(`${
|
|
3063
|
+
if (n.object_id) console.log(`${chalk15.bold("object_id:")} ${n.object_id}`);
|
|
3064
|
+
console.log(`${chalk15.bold("id:")} ${n.id}`);
|
|
3065
|
+
if (inboxId) console.log(`${chalk15.bold("inbox:")} ${inboxId}`);
|
|
3066
|
+
console.log(`${chalk15.bold("tags:")} ${n.tags.length ? n.tags.join(", ") : "-"}`);
|
|
3067
|
+
if (n.linked_task_id) console.log(`${chalk15.bold("linked_task:")} ${n.linked_task_id}`);
|
|
3068
|
+
if (n.linked_project) console.log(`${chalk15.bold("linked_project:")} ${n.linked_project}`);
|
|
3069
|
+
console.log(`${chalk15.bold("created_at:")} ${n.created_at}`);
|
|
3070
|
+
console.log(`${chalk15.bold("updated_at:")} ${n.updated_at}`);
|
|
2999
3071
|
if (n.published_to?.length) {
|
|
3000
|
-
console.log(
|
|
3072
|
+
console.log(chalk15.bold("published_to:"));
|
|
3001
3073
|
for (const p of n.published_to) {
|
|
3002
|
-
console.log(` - ${p.source_type}:${p.source_name} ${p.identifier} ${
|
|
3074
|
+
console.log(` - ${p.source_type}:${p.source_name} ${p.identifier} ${chalk15.dim(p.url)}`);
|
|
3003
3075
|
}
|
|
3004
3076
|
}
|
|
3005
|
-
console.log(
|
|
3077
|
+
console.log(chalk15.bold("body:"));
|
|
3006
3078
|
console.log(n.body.split("\n").map((l) => " " + l).join("\n"));
|
|
3007
3079
|
}
|
|
3008
3080
|
function fail4(err) {
|
|
3009
|
-
console.error(
|
|
3081
|
+
console.error(chalk15.red(err.message));
|
|
3010
3082
|
process.exit(1);
|
|
3011
3083
|
}
|
|
3012
3084
|
note.command("list").description("List inbox notes").option("--inbox <id>", "Restrict to a specific inbox").option("--json", "Output JSON").action(async (opts) => {
|
|
@@ -3020,7 +3092,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
|
|
|
3020
3092
|
return;
|
|
3021
3093
|
}
|
|
3022
3094
|
if (!notes.length) {
|
|
3023
|
-
console.log(
|
|
3095
|
+
console.log(chalk15.dim("No notes."));
|
|
3024
3096
|
return;
|
|
3025
3097
|
}
|
|
3026
3098
|
for (const n of notes) printNoteRow(n);
|
|
@@ -3033,7 +3105,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
|
|
|
3033
3105
|
}
|
|
3034
3106
|
const flat = all.flatMap(({ inbox, notes }) => notes.map((n) => ({ n, inboxName: inbox.name })));
|
|
3035
3107
|
if (!flat.length) {
|
|
3036
|
-
console.log(
|
|
3108
|
+
console.log(chalk15.dim("No notes."));
|
|
3037
3109
|
return;
|
|
3038
3110
|
}
|
|
3039
3111
|
for (const { n, inboxName } of flat) printNoteRow(n, inboxName);
|
|
@@ -3066,7 +3138,7 @@ note.command("create").description("Create a note in an inbox").requiredOption("
|
|
|
3066
3138
|
console.log(JSON.stringify({ note: created }, null, 2));
|
|
3067
3139
|
return;
|
|
3068
3140
|
}
|
|
3069
|
-
console.log(
|
|
3141
|
+
console.log(chalk15.green(`Created ${created.object_id || created.id}`));
|
|
3070
3142
|
printNoteDetail(created, opts.inbox);
|
|
3071
3143
|
} catch (err) {
|
|
3072
3144
|
fail4(err);
|
|
@@ -3082,7 +3154,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
|
|
|
3082
3154
|
if (opts.linkedTask !== void 0) payload.linked_task_id = opts.linkedTask;
|
|
3083
3155
|
if (opts.linkedProject !== void 0) payload.linked_project = opts.linkedProject;
|
|
3084
3156
|
if (!Object.keys(payload).length) {
|
|
3085
|
-
console.error(
|
|
3157
|
+
console.error(chalk15.yellow("Nothing to update. Pass --body, --tag, --clear-tags, --linked-task, or --linked-project."));
|
|
3086
3158
|
process.exit(1);
|
|
3087
3159
|
}
|
|
3088
3160
|
const { note: updated } = await api.patch(
|
|
@@ -3093,7 +3165,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
|
|
|
3093
3165
|
console.log(JSON.stringify({ note: updated }, null, 2));
|
|
3094
3166
|
return;
|
|
3095
3167
|
}
|
|
3096
|
-
console.log(
|
|
3168
|
+
console.log(chalk15.green(`Updated ${updated.object_id || updated.id}`));
|
|
3097
3169
|
printNoteDetail(updated, inboxId);
|
|
3098
3170
|
} catch (err) {
|
|
3099
3171
|
fail4(err);
|
|
@@ -3105,7 +3177,7 @@ note.command("delete <id>").description("Delete a note (accepts short id or note
|
|
|
3105
3177
|
await api.del(
|
|
3106
3178
|
`/api/inboxes/${encodeURIComponent(inboxId)}/notes/${encodeURIComponent(existing.id)}`
|
|
3107
3179
|
);
|
|
3108
|
-
console.log(
|
|
3180
|
+
console.log(chalk15.green(`Deleted ${existing.object_id || existing.id}`));
|
|
3109
3181
|
} catch (err) {
|
|
3110
3182
|
fail4(err);
|
|
3111
3183
|
}
|
|
@@ -3123,8 +3195,8 @@ note.command("publish <id>").description("Publish a note to a github/linear sour
|
|
|
3123
3195
|
console.log(JSON.stringify({ published }, null, 2));
|
|
3124
3196
|
return;
|
|
3125
3197
|
}
|
|
3126
|
-
console.log(
|
|
3127
|
-
console.log(` ${
|
|
3198
|
+
console.log(chalk15.green(`Published to ${published.source_type}:${published.source_name} ${published.identifier}`));
|
|
3199
|
+
console.log(` ${chalk15.dim(published.url)}`);
|
|
3128
3200
|
} catch (err) {
|
|
3129
3201
|
fail4(err);
|
|
3130
3202
|
}
|
|
@@ -3140,16 +3212,16 @@ note.command("convert <id>").description("Convert a note into a task under a pro
|
|
|
3140
3212
|
console.log(JSON.stringify({ task: task2 }, null, 2));
|
|
3141
3213
|
return;
|
|
3142
3214
|
}
|
|
3143
|
-
console.log(
|
|
3215
|
+
console.log(chalk15.green(`Converted to task ${task2.object_id} (${task2.id}) in ${task2.project}`));
|
|
3144
3216
|
} catch (err) {
|
|
3145
3217
|
fail4(err);
|
|
3146
3218
|
}
|
|
3147
3219
|
});
|
|
3148
3220
|
|
|
3149
3221
|
// src/commands/object.ts
|
|
3150
|
-
import { Command as
|
|
3151
|
-
import
|
|
3152
|
-
var object = new
|
|
3222
|
+
import { Command as Command16 } from "commander";
|
|
3223
|
+
import chalk16 from "chalk";
|
|
3224
|
+
var object = new Command16("object").description("Resolve any object by its object_id");
|
|
3153
3225
|
function pickFirst(resource, keys) {
|
|
3154
3226
|
for (const k of keys) {
|
|
3155
3227
|
const value = resource[k];
|
|
@@ -3159,16 +3231,16 @@ function pickFirst(resource, keys) {
|
|
|
3159
3231
|
}
|
|
3160
3232
|
function printSummary(result) {
|
|
3161
3233
|
const { type, resource } = result;
|
|
3162
|
-
console.log(`${
|
|
3234
|
+
console.log(`${chalk16.bold("type:")} ${type}`);
|
|
3163
3235
|
const title = pickFirst(resource, ["title", "name"]);
|
|
3164
3236
|
const id = pickFirst(resource, ["object_id", "objectId", "id"]);
|
|
3165
3237
|
const status = pickFirst(resource, ["status"]);
|
|
3166
|
-
if (id) console.log(`${
|
|
3167
|
-
if (title) console.log(`${
|
|
3168
|
-
if (status) console.log(`${
|
|
3238
|
+
if (id) console.log(`${chalk16.bold("object_id:")} ${id}`);
|
|
3239
|
+
if (title) console.log(`${chalk16.bold("title:")} ${title}`);
|
|
3240
|
+
if (status) console.log(`${chalk16.bold("status:")} ${status}`);
|
|
3169
3241
|
const updatedAt = pickFirst(resource, ["updated_at", "updatedAt"]);
|
|
3170
|
-
if (updatedAt) console.log(`${
|
|
3171
|
-
console.log(
|
|
3242
|
+
if (updatedAt) console.log(`${chalk16.bold("updated_at:")} ${updatedAt}`);
|
|
3243
|
+
console.log(chalk16.dim("(pass --json for full payload)"));
|
|
3172
3244
|
}
|
|
3173
3245
|
object.command("get <object-id>").description("Resolve an object by object_id (tsk_\u2026, note_\u2026, obj_\u2026, rt_\u2026, \u2026)").option("--json", "Output full JSON envelope").action(async (objectId, opts) => {
|
|
3174
3246
|
try {
|
|
@@ -3181,17 +3253,17 @@ object.command("get <object-id>").description("Resolve an object by object_id (t
|
|
|
3181
3253
|
} catch (err) {
|
|
3182
3254
|
const apiErr = err;
|
|
3183
3255
|
if (apiErr.status === 404) {
|
|
3184
|
-
console.error(
|
|
3256
|
+
console.error(chalk16.red(`not found: ${objectId}`));
|
|
3185
3257
|
process.exit(1);
|
|
3186
3258
|
}
|
|
3187
|
-
console.error(
|
|
3259
|
+
console.error(chalk16.red(err.message));
|
|
3188
3260
|
process.exit(1);
|
|
3189
3261
|
}
|
|
3190
3262
|
});
|
|
3191
3263
|
|
|
3192
3264
|
// src/commands/issue.ts
|
|
3193
|
-
import { Command as
|
|
3194
|
-
import
|
|
3265
|
+
import { Command as Command17 } from "commander";
|
|
3266
|
+
import chalk17 from "chalk";
|
|
3195
3267
|
|
|
3196
3268
|
// src/core/issue/decision.ts
|
|
3197
3269
|
init_node();
|
|
@@ -3365,7 +3437,7 @@ async function propose(opts) {
|
|
|
3365
3437
|
kicks.push({
|
|
3366
3438
|
issue: issue2.file,
|
|
3367
3439
|
agent: agent2,
|
|
3368
|
-
|
|
3440
|
+
agentRunId: "",
|
|
3369
3441
|
sessionName: "",
|
|
3370
3442
|
decisionFile,
|
|
3371
3443
|
error: null,
|
|
@@ -3375,7 +3447,7 @@ async function propose(opts) {
|
|
|
3375
3447
|
kicks.push({
|
|
3376
3448
|
issue: issue2.file,
|
|
3377
3449
|
agent: agent2,
|
|
3378
|
-
|
|
3450
|
+
agentRunId: "",
|
|
3379
3451
|
sessionName: "",
|
|
3380
3452
|
decisionFile,
|
|
3381
3453
|
error: `${decisionFile} already exists (pass --force to overwrite)`
|
|
@@ -3399,7 +3471,7 @@ async function propose(opts) {
|
|
|
3399
3471
|
kicks.push({
|
|
3400
3472
|
issue: issue2.file,
|
|
3401
3473
|
agent: agent2,
|
|
3402
|
-
|
|
3474
|
+
agentRunId: resp.runtime.id,
|
|
3403
3475
|
sessionName: "",
|
|
3404
3476
|
decisionFile,
|
|
3405
3477
|
error: null
|
|
@@ -3408,7 +3480,7 @@ async function propose(opts) {
|
|
|
3408
3480
|
kicks.push({
|
|
3409
3481
|
issue: issue2.file,
|
|
3410
3482
|
agent: agent2,
|
|
3411
|
-
|
|
3483
|
+
agentRunId: "",
|
|
3412
3484
|
sessionName: "",
|
|
3413
3485
|
decisionFile,
|
|
3414
3486
|
error: err.message
|
|
@@ -3416,25 +3488,25 @@ async function propose(opts) {
|
|
|
3416
3488
|
}
|
|
3417
3489
|
}
|
|
3418
3490
|
const runtimesByAgent = {};
|
|
3419
|
-
for (const k of kicks) if (!k.error && k.
|
|
3491
|
+
for (const k of kicks) if (!k.error && k.agentRunId) runtimesByAgent[k.agent] = k.agentRunId;
|
|
3420
3492
|
const proposalFiles = kicks.filter((k) => !k.error).map((k) => k.decisionFile);
|
|
3421
3493
|
if (Object.keys(runtimesByAgent).length > 0 || proposalFiles.length > 0) {
|
|
3422
3494
|
await updateWorkflow(loc.taskYml, {
|
|
3423
3495
|
decisions: {
|
|
3424
3496
|
[issue2.file]: {
|
|
3425
3497
|
proposal_files: proposalFiles.length > 0 ? proposalFiles : void 0,
|
|
3426
|
-
|
|
3498
|
+
proposal_agent_runs: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
|
|
3427
3499
|
}
|
|
3428
3500
|
}
|
|
3429
3501
|
});
|
|
3430
3502
|
}
|
|
3431
3503
|
if (opts.wait) {
|
|
3432
3504
|
for (const k of kicks) {
|
|
3433
|
-
if (k.error || !k.
|
|
3505
|
+
if (k.error || !k.agentRunId) continue;
|
|
3434
3506
|
try {
|
|
3435
|
-
const final = await
|
|
3507
|
+
const final = await waitForAgentRun(k.agentRunId);
|
|
3436
3508
|
if (final.status !== "done") {
|
|
3437
|
-
k.error = final.error || `runtime ${k.
|
|
3509
|
+
k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
|
|
3438
3510
|
} else if (!fs18.existsSync(path17.join(loc.taskDir, k.decisionFile))) {
|
|
3439
3511
|
k.error = `runtime completed but ${k.decisionFile} was not written`;
|
|
3440
3512
|
}
|
|
@@ -3464,7 +3536,7 @@ async function consolidate(opts) {
|
|
|
3464
3536
|
if (proposalFiles.length === 0) {
|
|
3465
3537
|
kicks.push({
|
|
3466
3538
|
issue: issue2.file,
|
|
3467
|
-
|
|
3539
|
+
agentRunId: "",
|
|
3468
3540
|
sessionName: "",
|
|
3469
3541
|
consolidatedFile: consolidatedFileName(issue2),
|
|
3470
3542
|
error: `no DECISION-${issue2.index}-<agent>.md proposals found. Run \`task0 issue propose\` first.`
|
|
@@ -3483,28 +3555,28 @@ async function consolidate(opts) {
|
|
|
3483
3555
|
prompt
|
|
3484
3556
|
}
|
|
3485
3557
|
);
|
|
3486
|
-
const
|
|
3558
|
+
const agentRunId = resp.runtime.id;
|
|
3487
3559
|
await updateWorkflow(loc.taskYml, {
|
|
3488
3560
|
decisions: {
|
|
3489
3561
|
[issue2.file]: {
|
|
3490
3562
|
consolidated_file: consolidatedFile,
|
|
3491
|
-
|
|
3563
|
+
consolidate_agent_run: agentRunId
|
|
3492
3564
|
}
|
|
3493
3565
|
}
|
|
3494
3566
|
});
|
|
3495
3567
|
const kick = {
|
|
3496
3568
|
issue: issue2.file,
|
|
3497
|
-
|
|
3569
|
+
agentRunId,
|
|
3498
3570
|
sessionName: "",
|
|
3499
3571
|
consolidatedFile,
|
|
3500
3572
|
error: null
|
|
3501
3573
|
};
|
|
3502
3574
|
if (opts.wait) {
|
|
3503
3575
|
try {
|
|
3504
|
-
const final = await
|
|
3576
|
+
const final = await waitForAgentRun(agentRunId);
|
|
3505
3577
|
const wrote = fs18.existsSync(path17.join(loc.taskDir, consolidatedFile));
|
|
3506
3578
|
if (final.status !== "done") {
|
|
3507
|
-
kick.error = final.error || `runtime ${
|
|
3579
|
+
kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
|
|
3508
3580
|
} else if (!wrote) {
|
|
3509
3581
|
kick.error = `runtime completed but ${consolidatedFile} was not written`;
|
|
3510
3582
|
}
|
|
@@ -3517,7 +3589,7 @@ async function consolidate(opts) {
|
|
|
3517
3589
|
} catch (err) {
|
|
3518
3590
|
kicks.push({
|
|
3519
3591
|
issue: issue2.file,
|
|
3520
|
-
|
|
3592
|
+
agentRunId: "",
|
|
3521
3593
|
sessionName: "",
|
|
3522
3594
|
consolidatedFile,
|
|
3523
3595
|
error: err.message
|
|
@@ -3577,7 +3649,7 @@ function buildReferenceFiles(taskDir, issue2) {
|
|
|
3577
3649
|
}
|
|
3578
3650
|
|
|
3579
3651
|
// src/commands/issue.ts
|
|
3580
|
-
var issue = new
|
|
3652
|
+
var issue = new Command17("issue").description(
|
|
3581
3653
|
"Resolve blocking ISSUE Open Questions via multi-agent decision proposals"
|
|
3582
3654
|
);
|
|
3583
3655
|
issue.command("propose <taskId>").description(`Fan out agents to propose decisions for the task's blocking "## Open Questions"`).option("--issue <file>", "Specific ISSUE file (ISSUE-NN, NN, or ISSUE-NN.md); defaults to all blocking ISSUEs").option("-a, --agents <list>", "Comma-separated agents", "codex,claude-code").option("--additional-prompt <text>", "Extra prompt content appended to the default propose prompt").option("--model <id>", "Model id or alias (only valid with a single agent)").option("--effort <level>", "Reasoning effort (only valid with a single agent)").option("--wait", "Wait for each runtime to finish").option("--force", "Overwrite existing DECISION files").option("--if-needed", "Idempotent: skip issues with no Open Questions and agents whose DECISION file already exists (exit 0)").option("--json", "Output JSON").action(async (taskId, opts) => {
|
|
@@ -3593,7 +3665,7 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
|
|
|
3593
3665
|
wait: opts.wait,
|
|
3594
3666
|
force: opts.force,
|
|
3595
3667
|
ifNeeded: opts.ifNeeded,
|
|
3596
|
-
warn: opts.json ? void 0 : (m) => console.error(
|
|
3668
|
+
warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
|
|
3597
3669
|
});
|
|
3598
3670
|
if (opts.json) {
|
|
3599
3671
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3602,26 +3674,26 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
|
|
|
3602
3674
|
return;
|
|
3603
3675
|
}
|
|
3604
3676
|
if (opts.ifNeeded && result.issues.length === 0) {
|
|
3605
|
-
console.log(
|
|
3677
|
+
console.log(chalk17.dim("no blocking Open Questions; nothing to propose"));
|
|
3606
3678
|
return;
|
|
3607
3679
|
}
|
|
3608
3680
|
let anyFailed = false;
|
|
3609
3681
|
for (const item of result.issues) {
|
|
3610
|
-
console.log(
|
|
3682
|
+
console.log(chalk17.bold(item.issue));
|
|
3611
3683
|
for (const k of item.kicks) {
|
|
3612
3684
|
if (k.error) {
|
|
3613
3685
|
anyFailed = true;
|
|
3614
|
-
console.error(
|
|
3686
|
+
console.error(chalk17.red(` [${k.agent}] ${k.error}`));
|
|
3615
3687
|
} else if (k.skipped === "already-exists") {
|
|
3616
|
-
console.log(
|
|
3688
|
+
console.log(chalk17.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
|
|
3617
3689
|
} else {
|
|
3618
|
-
console.log(
|
|
3690
|
+
console.log(chalk17.green(` [${k.agent}]`) + ` runtime ${k.agentRunId} \u2192 ${k.decisionFile}`);
|
|
3619
3691
|
}
|
|
3620
3692
|
}
|
|
3621
3693
|
}
|
|
3622
3694
|
if (anyFailed) process.exit(1);
|
|
3623
3695
|
} catch (err) {
|
|
3624
|
-
console.error(
|
|
3696
|
+
console.error(chalk17.red(err.message));
|
|
3625
3697
|
process.exit(1);
|
|
3626
3698
|
}
|
|
3627
3699
|
});
|
|
@@ -3634,7 +3706,7 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
|
|
|
3634
3706
|
model: opts.model,
|
|
3635
3707
|
effort: opts.effort,
|
|
3636
3708
|
wait: opts.wait,
|
|
3637
|
-
warn: opts.json ? void 0 : (m) => console.error(
|
|
3709
|
+
warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
|
|
3638
3710
|
});
|
|
3639
3711
|
if (opts.json) {
|
|
3640
3712
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3646,16 +3718,16 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
|
|
|
3646
3718
|
for (const k of result.kicks) {
|
|
3647
3719
|
if (k.error) {
|
|
3648
3720
|
anyFailed = true;
|
|
3649
|
-
console.error(
|
|
3721
|
+
console.error(chalk17.red(`[${k.issue}] ${k.error}`));
|
|
3650
3722
|
} else {
|
|
3651
3723
|
console.log(
|
|
3652
|
-
|
|
3724
|
+
chalk17.green(`[${k.issue}]`) + ` runtime ${k.agentRunId} \u2192 ${k.consolidatedFile}` + (k.wrote === false ? chalk17.yellow(" (pending write)") : "")
|
|
3653
3725
|
);
|
|
3654
3726
|
}
|
|
3655
3727
|
}
|
|
3656
3728
|
if (anyFailed) process.exit(1);
|
|
3657
3729
|
} catch (err) {
|
|
3658
|
-
console.error(
|
|
3730
|
+
console.error(chalk17.red(err.message));
|
|
3659
3731
|
process.exit(1);
|
|
3660
3732
|
}
|
|
3661
3733
|
});
|
|
@@ -3667,10 +3739,10 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
3667
3739
|
return;
|
|
3668
3740
|
}
|
|
3669
3741
|
for (const u of result.updated) {
|
|
3670
|
-
console.log(
|
|
3742
|
+
console.log(chalk17.green(`approved ${u.issueFile}`) + ` (${u.questionsReplaced} decisions \u2190 ${u.consolidatedFile})`);
|
|
3671
3743
|
}
|
|
3672
3744
|
} catch (err) {
|
|
3673
|
-
console.error(
|
|
3745
|
+
console.error(chalk17.red(err.message));
|
|
3674
3746
|
process.exit(1);
|
|
3675
3747
|
}
|
|
3676
3748
|
});
|
|
@@ -3678,23 +3750,37 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
3678
3750
|
// src/commands/agent.ts
|
|
3679
3751
|
import fs19 from "fs";
|
|
3680
3752
|
import path18 from "path";
|
|
3681
|
-
import { spawnSync as
|
|
3682
|
-
import { Command as
|
|
3683
|
-
import
|
|
3684
|
-
import
|
|
3753
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
3754
|
+
import { Command as Command18 } from "commander";
|
|
3755
|
+
import chalk18 from "chalk";
|
|
3756
|
+
import yaml7 from "js-yaml";
|
|
3757
|
+
function statusBadge(s) {
|
|
3758
|
+
if (s === "working") return chalk18.cyan("\u25CF");
|
|
3759
|
+
if (s === "error") return chalk18.red("\u25CF");
|
|
3760
|
+
if (s === "idle") return chalk18.green("\u25CF");
|
|
3761
|
+
return chalk18.dim("\u25CB");
|
|
3762
|
+
}
|
|
3763
|
+
function formatDuration(ms) {
|
|
3764
|
+
if (ms === null || !Number.isFinite(ms)) return "\u2014";
|
|
3765
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
3766
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3767
|
+
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
3768
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
3769
|
+
}
|
|
3685
3770
|
function fail5(message, code = 1) {
|
|
3686
|
-
console.error(
|
|
3771
|
+
console.error(chalk18.red(message));
|
|
3687
3772
|
process.exit(code);
|
|
3688
3773
|
}
|
|
3689
3774
|
function formatAgent(a, withDetails = false) {
|
|
3690
|
-
const tag = a.system ?
|
|
3691
|
-
const
|
|
3775
|
+
const tag = a.system ? chalk18.dim("(system)") : a.scope ? chalk18.dim(`(${a.scope})`) : "";
|
|
3776
|
+
const dot = statusBadge(a.status);
|
|
3777
|
+
const head = `${dot} ${chalk18.bold(a.slug.padEnd(24))} ${chalk18.dim(a.object_id.padEnd(16))} ${a.kind.padEnd(10)} ${tag}`;
|
|
3692
3778
|
if (!withDetails) return head;
|
|
3693
3779
|
const lines = [head];
|
|
3694
|
-
if (a.description) lines.push(
|
|
3780
|
+
if (a.description) lines.push(chalk18.dim(" " + a.description));
|
|
3695
3781
|
return lines.join("\n");
|
|
3696
3782
|
}
|
|
3697
|
-
var agent = new
|
|
3783
|
+
var agent = new Command18("agent").description("Manage agents and run them against tasks");
|
|
3698
3784
|
agent.command("list").description("List visible agents (project + user + built-in cascade)").option("--kind <kind>", "Filter by kind: coding | llm-api | workflow").option("--include-system", "Include built-in system agents (default true)").option("--json", "Output JSON").action(async (opts) => {
|
|
3699
3785
|
try {
|
|
3700
3786
|
const params = new URLSearchParams();
|
|
@@ -3704,7 +3790,7 @@ agent.command("list").description("List visible agents (project + user + built-i
|
|
|
3704
3790
|
const result = await api.get(`/api/agents${qs}`);
|
|
3705
3791
|
if (opts.json) return console.log(JSON.stringify(result.agents, null, 2));
|
|
3706
3792
|
if (!result.agents.length) {
|
|
3707
|
-
console.log(
|
|
3793
|
+
console.log(chalk18.dim("(no agents)"));
|
|
3708
3794
|
return;
|
|
3709
3795
|
}
|
|
3710
3796
|
for (const a of result.agents) console.log(formatAgent(a, true));
|
|
@@ -3715,11 +3801,23 @@ agent.command("list").description("List visible agents (project + user + built-i
|
|
|
3715
3801
|
agent.command("get <ref>").description("Show one agent by object_id or slug").option("--json", "Output JSON").action(async (ref, opts) => {
|
|
3716
3802
|
try {
|
|
3717
3803
|
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
|
|
3718
|
-
if (opts.json) return console.log(JSON.stringify(result
|
|
3804
|
+
if (opts.json) return console.log(JSON.stringify(result, null, 2));
|
|
3719
3805
|
console.log(formatAgent(result.agent, true));
|
|
3806
|
+
const limit = result.agent.max_concurrent_tasks;
|
|
3807
|
+
const inFlight = result.in_flight ?? 0;
|
|
3808
|
+
if (limit !== void 0) {
|
|
3809
|
+
const at = inFlight >= limit ? chalk18.yellow : chalk18.dim;
|
|
3810
|
+
console.log(at(` concurrency: ${inFlight}/${limit}`));
|
|
3811
|
+
} else if (inFlight > 0) {
|
|
3812
|
+
console.log(chalk18.dim(` concurrency: ${inFlight}/\u221E`));
|
|
3813
|
+
}
|
|
3720
3814
|
console.log();
|
|
3721
|
-
console.log(
|
|
3722
|
-
console.log(
|
|
3815
|
+
console.log(chalk18.dim("--- spec ---"));
|
|
3816
|
+
console.log(yaml7.dump(result.agent.spec, { lineWidth: 100 }));
|
|
3817
|
+
if (result.agent.mcp_config) {
|
|
3818
|
+
console.log(chalk18.dim("--- mcp_config ---"));
|
|
3819
|
+
console.log(yaml7.dump(result.agent.mcp_config, { lineWidth: 100 }));
|
|
3820
|
+
}
|
|
3723
3821
|
} catch (err) {
|
|
3724
3822
|
const apiErr = err;
|
|
3725
3823
|
if (apiErr.status === 404) fail5(`not found: ${ref}`);
|
|
@@ -3730,14 +3828,14 @@ agent.command("create").description("Create an agent from a YAML spec file").req
|
|
|
3730
3828
|
let parsed;
|
|
3731
3829
|
try {
|
|
3732
3830
|
const raw = fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8");
|
|
3733
|
-
parsed =
|
|
3831
|
+
parsed = yaml7.load(raw);
|
|
3734
3832
|
} catch (err) {
|
|
3735
3833
|
fail5(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
3736
3834
|
}
|
|
3737
3835
|
try {
|
|
3738
3836
|
const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
|
|
3739
3837
|
const result = await api.post("/api/agents", body);
|
|
3740
|
-
console.log(
|
|
3838
|
+
console.log(chalk18.green(`created ${result.agent.slug} (${result.agent.object_id})`));
|
|
3741
3839
|
} catch (err) {
|
|
3742
3840
|
fail5(err.message);
|
|
3743
3841
|
}
|
|
@@ -3750,14 +3848,14 @@ agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save
|
|
|
3750
3848
|
process.env.TMPDIR || "/tmp",
|
|
3751
3849
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
3752
3850
|
);
|
|
3753
|
-
fs19.writeFileSync(tmp,
|
|
3851
|
+
fs19.writeFileSync(tmp, yaml7.dump(result.agent, { lineWidth: 100 }), "utf-8");
|
|
3754
3852
|
const editor = process.env.EDITOR || "vi";
|
|
3755
|
-
const r =
|
|
3853
|
+
const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
|
|
3756
3854
|
if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
|
|
3757
|
-
const updated =
|
|
3855
|
+
const updated = yaml7.load(fs19.readFileSync(tmp, "utf-8"));
|
|
3758
3856
|
await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
|
|
3759
3857
|
fs19.unlinkSync(tmp);
|
|
3760
|
-
console.log(
|
|
3858
|
+
console.log(chalk18.green(`updated ${ref}`));
|
|
3761
3859
|
} catch (err) {
|
|
3762
3860
|
fail5(err.message);
|
|
3763
3861
|
}
|
|
@@ -3766,38 +3864,99 @@ agent.command("delete <ref>").description("Delete an agent").action(async (ref)
|
|
|
3766
3864
|
try {
|
|
3767
3865
|
const result = await api.del(`/api/agents/${encodeURIComponent(ref)}`);
|
|
3768
3866
|
if (!result.deleted) fail5(`not deleted: ${ref}`);
|
|
3769
|
-
console.log(
|
|
3867
|
+
console.log(chalk18.green(`deleted ${ref}`));
|
|
3770
3868
|
} catch (err) {
|
|
3771
3869
|
fail5(err.message);
|
|
3772
3870
|
}
|
|
3773
3871
|
});
|
|
3872
|
+
agent.command("activity <ref>").description("Show recent activity (runs, status, duration) for an agent").option("--days <n>", "Time window in days (default 30)", "30").option("--limit <n>", "Limit recent rows shown (default 10)", "10").option("--json", "Output JSON").action(async (ref, opts) => {
|
|
3873
|
+
try {
|
|
3874
|
+
const days = encodeURIComponent(opts.days);
|
|
3875
|
+
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}/activity?days=${days}`);
|
|
3876
|
+
if (opts.json) return console.log(JSON.stringify(result, null, 2));
|
|
3877
|
+
const a = result.agent;
|
|
3878
|
+
const limitStr = a.max_concurrent_tasks === null ? "\u221E" : String(a.max_concurrent_tasks);
|
|
3879
|
+
console.log(`${statusBadge(result.status)} ${chalk18.bold(a.slug)} ${chalk18.dim(a.object_id)} ${a.kind}`);
|
|
3880
|
+
console.log(chalk18.dim(` concurrency: ${result.in_flight}/${limitStr} status: ${result.status}`));
|
|
3881
|
+
console.log();
|
|
3882
|
+
console.log(chalk18.dim(`--- last ${result.range_days}d ---`));
|
|
3883
|
+
console.log(
|
|
3884
|
+
` total: ${result.runs_total} ${chalk18.green("ok")}: ${result.runs_success} ${chalk18.red("err")}: ${result.runs_failed} ${chalk18.cyan("running")}: ${result.runs_running} avg: ${formatDuration(result.avg_duration_ms)} last: ${result.last_run_at ?? "\u2014"}`
|
|
3885
|
+
);
|
|
3886
|
+
const limit = Math.max(1, Number(opts.limit) || 10);
|
|
3887
|
+
const recent = result.recent.slice(0, limit);
|
|
3888
|
+
if (recent.length === 0) {
|
|
3889
|
+
console.log(chalk18.dim(" (no recent runs)"));
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3892
|
+
console.log();
|
|
3893
|
+
console.log(chalk18.dim("--- recent ---"));
|
|
3894
|
+
for (const r of recent) {
|
|
3895
|
+
const dotStatus = r.status === "done" ? "idle" : r.status === "error" ? "error" : "working";
|
|
3896
|
+
console.log(
|
|
3897
|
+
` ${statusBadge(dotStatus)} ${r.started_at} ${r.status.padEnd(8)} ${formatDuration(r.duration_ms).padStart(7)} ${chalk18.dim(r.type)} ${chalk18.dim(r.task_id)}`
|
|
3898
|
+
);
|
|
3899
|
+
}
|
|
3900
|
+
} catch (err) {
|
|
3901
|
+
const apiErr = err;
|
|
3902
|
+
if (apiErr.status === 404) fail5(`not found: ${ref}`);
|
|
3903
|
+
fail5(err.message);
|
|
3904
|
+
}
|
|
3905
|
+
});
|
|
3774
3906
|
agent.command("run <ref>").description("Run an agent against a task with a user prompt").requiredOption("--task <task-id>", "Task id (tsk_\u2026 or directory name)").requiredOption("--prompt <text>", "User prompt to send the agent").option("--stream", "Stream output.jsonl to stdout as it arrives").option("--attach", "Open tmux session (coding agents only)").action(async (ref, opts) => {
|
|
3775
3907
|
try {
|
|
3776
3908
|
const result = await api.post(
|
|
3777
3909
|
`/api/agents/${encodeURIComponent(ref)}/run`,
|
|
3778
3910
|
{ task_id: opts.task, prompt: opts.prompt }
|
|
3779
3911
|
);
|
|
3780
|
-
console.log(
|
|
3781
|
-
if (result.runtime.objectId) console.log(
|
|
3912
|
+
console.log(chalk18.green(`runtime ${result.runtime.id} started`));
|
|
3913
|
+
if (result.runtime.objectId) console.log(chalk18.dim(`object_id: ${result.runtime.objectId}`));
|
|
3782
3914
|
if (opts.stream) {
|
|
3783
3915
|
await streamOutput(ref, result.runtime.id);
|
|
3784
3916
|
}
|
|
3785
3917
|
if (opts.attach) {
|
|
3786
|
-
console.log(
|
|
3918
|
+
console.log(chalk18.dim("--attach not yet wired; runtime is visible in dashboard"));
|
|
3787
3919
|
}
|
|
3788
3920
|
} catch (err) {
|
|
3789
3921
|
fail5(err.message);
|
|
3790
3922
|
}
|
|
3791
3923
|
});
|
|
3792
|
-
|
|
3924
|
+
var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_config field)");
|
|
3925
|
+
mcp.command("set <ref>").description("Set mcp_config from a JSON file (replaces existing config)").requiredOption("--from-file <file>", "JSON file containing the mcp_config object").action(async (ref, opts) => {
|
|
3926
|
+
let parsed;
|
|
3927
|
+
try {
|
|
3928
|
+
parsed = JSON.parse(fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8"));
|
|
3929
|
+
} catch (err) {
|
|
3930
|
+
fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
|
|
3931
|
+
}
|
|
3932
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3933
|
+
fail5("mcp_config must be a JSON object");
|
|
3934
|
+
}
|
|
3935
|
+
try {
|
|
3936
|
+
await api.put(`/api/agents/${encodeURIComponent(ref)}`, { mcp_config: parsed });
|
|
3937
|
+
console.log(chalk18.green(`mcp_config set on ${ref}`));
|
|
3938
|
+
} catch (err) {
|
|
3939
|
+
fail5(err.message);
|
|
3940
|
+
}
|
|
3941
|
+
});
|
|
3942
|
+
mcp.command("clear <ref>").description("Remove mcp_config from an agent").action(async (ref) => {
|
|
3943
|
+
try {
|
|
3944
|
+
await api.put(`/api/agents/${encodeURIComponent(ref)}`, { mcp_config: null });
|
|
3945
|
+
console.log(chalk18.green(`mcp_config cleared on ${ref}`));
|
|
3946
|
+
} catch (err) {
|
|
3947
|
+
fail5(err.message);
|
|
3948
|
+
}
|
|
3949
|
+
});
|
|
3950
|
+
agent.addCommand(mcp);
|
|
3951
|
+
async function streamOutput(ref, agentRunId) {
|
|
3793
3952
|
let lastCount = 0;
|
|
3794
3953
|
for (let i = 0; i < 600; i++) {
|
|
3795
3954
|
const result = await api.get(
|
|
3796
|
-
`/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(
|
|
3955
|
+
`/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(agentRunId)}/output`
|
|
3797
3956
|
);
|
|
3798
3957
|
for (const line of result.lines.slice(lastCount)) {
|
|
3799
3958
|
if (line.error?.message) {
|
|
3800
|
-
console.error(
|
|
3959
|
+
console.error(chalk18.red(`[error] ${line.error.message}`));
|
|
3801
3960
|
return;
|
|
3802
3961
|
}
|
|
3803
3962
|
const text = line.message?.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
@@ -3809,47 +3968,115 @@ async function streamOutput(ref, runtimeId) {
|
|
|
3809
3968
|
}
|
|
3810
3969
|
|
|
3811
3970
|
// src/commands/daemon.ts
|
|
3812
|
-
import
|
|
3813
|
-
import { Command as
|
|
3814
|
-
import
|
|
3971
|
+
import os7 from "os";
|
|
3972
|
+
import { Command as Command19 } from "commander";
|
|
3973
|
+
import chalk19 from "chalk";
|
|
3815
3974
|
import WebSocket from "ws";
|
|
3816
3975
|
|
|
3817
|
-
// src/core/
|
|
3976
|
+
// src/core/admin-token.ts
|
|
3818
3977
|
import fs20 from "fs";
|
|
3819
3978
|
import os4 from "os";
|
|
3820
3979
|
import path19 from "path";
|
|
3821
3980
|
var CONFIG_DIR2 = path19.join(os4.homedir(), ".config", "task0");
|
|
3822
|
-
var
|
|
3981
|
+
var TOKEN_FILE = path19.join(CONFIG_DIR2, "admin.token");
|
|
3982
|
+
var cached = null;
|
|
3983
|
+
var AdminTokenUnavailableError = class extends Error {
|
|
3984
|
+
constructor() {
|
|
3985
|
+
super(
|
|
3986
|
+
`Admin token not found.
|
|
3987
|
+
\u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${TOKEN_FILE}.
|
|
3988
|
+
\u2022 Otherwise, copy the token from the server host and set TASK0_ADMIN_TOKEN.`
|
|
3989
|
+
);
|
|
3990
|
+
this.name = "AdminTokenUnavailableError";
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
function readAdminToken() {
|
|
3994
|
+
if (cached) return cached;
|
|
3995
|
+
const fromEnv = process.env.TASK0_ADMIN_TOKEN?.trim();
|
|
3996
|
+
if (fromEnv) {
|
|
3997
|
+
cached = fromEnv;
|
|
3998
|
+
return cached;
|
|
3999
|
+
}
|
|
4000
|
+
if (fs20.existsSync(TOKEN_FILE)) {
|
|
4001
|
+
const v = fs20.readFileSync(TOKEN_FILE, "utf-8").trim();
|
|
4002
|
+
if (v) {
|
|
4003
|
+
cached = v;
|
|
4004
|
+
return cached;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
throw new AdminTokenUnavailableError();
|
|
4008
|
+
}
|
|
4009
|
+
function adminAuthHeader() {
|
|
4010
|
+
return { authorization: `Bearer ${readAdminToken()}` };
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// src/core/daemon-config.ts
|
|
4014
|
+
import fs21 from "fs";
|
|
4015
|
+
import os5 from "os";
|
|
4016
|
+
import path20 from "path";
|
|
4017
|
+
var CONFIG_DIR3 = path20.join(os5.homedir(), ".config", "task0");
|
|
4018
|
+
var CONFIG_FILE2 = path20.join(CONFIG_DIR3, "daemon.json");
|
|
3823
4019
|
function daemonConfigPath() {
|
|
3824
4020
|
return CONFIG_FILE2;
|
|
3825
4021
|
}
|
|
3826
4022
|
function readDaemonIdentity() {
|
|
3827
|
-
if (!
|
|
4023
|
+
if (!fs21.existsSync(CONFIG_FILE2)) return null;
|
|
3828
4024
|
try {
|
|
3829
|
-
const raw =
|
|
4025
|
+
const raw = fs21.readFileSync(CONFIG_FILE2, "utf-8");
|
|
3830
4026
|
return JSON.parse(raw);
|
|
3831
4027
|
} catch {
|
|
3832
4028
|
return null;
|
|
3833
4029
|
}
|
|
3834
4030
|
}
|
|
3835
4031
|
function writeDaemonIdentity(identity) {
|
|
3836
|
-
|
|
3837
|
-
|
|
4032
|
+
fs21.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
4033
|
+
fs21.writeFileSync(CONFIG_FILE2, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
3838
4034
|
try {
|
|
3839
|
-
|
|
4035
|
+
fs21.chmodSync(CONFIG_FILE2, 384);
|
|
3840
4036
|
} catch {
|
|
3841
4037
|
}
|
|
3842
4038
|
}
|
|
3843
4039
|
function clearDaemonIdentity() {
|
|
3844
|
-
if (!
|
|
3845
|
-
|
|
4040
|
+
if (!fs21.existsSync(CONFIG_FILE2)) return false;
|
|
4041
|
+
fs21.unlinkSync(CONFIG_FILE2);
|
|
3846
4042
|
return true;
|
|
3847
4043
|
}
|
|
3848
4044
|
|
|
4045
|
+
// src/core/register-auth.ts
|
|
4046
|
+
var RegisterAuthUnavailableError = class extends Error {
|
|
4047
|
+
constructor() {
|
|
4048
|
+
super(
|
|
4049
|
+
`No registration credential available.
|
|
4050
|
+
\u2022 Pass --token <apit_...> with an API token created in the dashboard, or
|
|
4051
|
+
\u2022 Set TASK0_API_TOKEN in the environment, or
|
|
4052
|
+
\u2022 Fall back to the server's admin token (TASK0_ADMIN_TOKEN or ~/.config/task0/admin.token).`
|
|
4053
|
+
);
|
|
4054
|
+
this.name = "RegisterAuthUnavailableError";
|
|
4055
|
+
}
|
|
4056
|
+
};
|
|
4057
|
+
function pickRegisterAuth(flagToken) {
|
|
4058
|
+
const fromFlag = flagToken?.trim();
|
|
4059
|
+
if (fromFlag) {
|
|
4060
|
+
return { header: { authorization: `Bearer ${fromFlag}` }, source: "flag" };
|
|
4061
|
+
}
|
|
4062
|
+
const fromEnv = process.env.TASK0_API_TOKEN?.trim();
|
|
4063
|
+
if (fromEnv) {
|
|
4064
|
+
return { header: { authorization: `Bearer ${fromEnv}` }, source: "env" };
|
|
4065
|
+
}
|
|
4066
|
+
try {
|
|
4067
|
+
return { header: { authorization: `Bearer ${readAdminToken()}` }, source: "admin" };
|
|
4068
|
+
} catch (error2) {
|
|
4069
|
+
if (error2 instanceof AdminTokenUnavailableError) {
|
|
4070
|
+
throw new RegisterAuthUnavailableError();
|
|
4071
|
+
}
|
|
4072
|
+
throw error2;
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
|
|
3849
4076
|
// src/core/daemon-rpc-handlers.ts
|
|
3850
4077
|
init_node();
|
|
3851
|
-
import
|
|
3852
|
-
import
|
|
4078
|
+
import fs22 from "fs";
|
|
4079
|
+
import path21 from "path";
|
|
3853
4080
|
var MAX_FILE_BYTES = 1 * 1024 * 1024;
|
|
3854
4081
|
function ensureString(value, name) {
|
|
3855
4082
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -3862,7 +4089,7 @@ var rpcHandlers = {
|
|
|
3862
4089
|
// in-process server's /api/tasks scanProject() returns.
|
|
3863
4090
|
async scan_project(params) {
|
|
3864
4091
|
const rootPath = ensureString(params.rootPath, "rootPath");
|
|
3865
|
-
const name = typeof params.name === "string" && params.name ? params.name :
|
|
4092
|
+
const name = typeof params.name === "string" && params.name ? params.name : path21.basename(rootPath);
|
|
3866
4093
|
return scanProject(rootPath, name);
|
|
3867
4094
|
},
|
|
3868
4095
|
// Read a file from disk on the daemon's host. The dashboard uses this to
|
|
@@ -3872,7 +4099,7 @@ var rpcHandlers = {
|
|
|
3872
4099
|
const filePath = ensureString(params.path, "path");
|
|
3873
4100
|
let stat;
|
|
3874
4101
|
try {
|
|
3875
|
-
stat =
|
|
4102
|
+
stat = fs22.statSync(filePath);
|
|
3876
4103
|
} catch {
|
|
3877
4104
|
throw Object.assign(new Error("file not found"), { code: "not_found" });
|
|
3878
4105
|
}
|
|
@@ -3882,23 +4109,23 @@ var rpcHandlers = {
|
|
|
3882
4109
|
if (stat.size > MAX_FILE_BYTES) {
|
|
3883
4110
|
throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
|
|
3884
4111
|
}
|
|
3885
|
-
const content =
|
|
4112
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
3886
4113
|
return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
|
|
3887
4114
|
}
|
|
3888
4115
|
};
|
|
3889
4116
|
|
|
3890
4117
|
// src/core/daemon-service/launchd.ts
|
|
3891
|
-
import { spawnSync as
|
|
3892
|
-
import
|
|
3893
|
-
import
|
|
4118
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
4119
|
+
import fs24 from "fs";
|
|
4120
|
+
import path23 from "path";
|
|
3894
4121
|
|
|
3895
4122
|
// src/core/daemon-service/binary.ts
|
|
3896
|
-
import
|
|
4123
|
+
import fs23 from "fs";
|
|
3897
4124
|
import { fileURLToPath } from "url";
|
|
3898
4125
|
function resolveTask0Invocation() {
|
|
3899
4126
|
const node = process.execPath;
|
|
3900
4127
|
const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
3901
|
-
const main2 =
|
|
4128
|
+
const main2 = fs23.realpathSync(argv1);
|
|
3902
4129
|
if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
|
|
3903
4130
|
throw new Error(
|
|
3904
4131
|
`Refusing to install autostart service pointing at ${main2}.
|
|
@@ -3914,8 +4141,8 @@ function isInstalledBuild(p) {
|
|
|
3914
4141
|
}
|
|
3915
4142
|
|
|
3916
4143
|
// src/core/daemon-service/paths.ts
|
|
3917
|
-
import
|
|
3918
|
-
import
|
|
4144
|
+
import os6 from "os";
|
|
4145
|
+
import path22 from "path";
|
|
3919
4146
|
|
|
3920
4147
|
// src/core/daemon-service/types.ts
|
|
3921
4148
|
var SERVICE_LABEL = "cc.cy0.task0";
|
|
@@ -3924,21 +4151,21 @@ var SERVICE_LABEL = "cc.cy0.task0";
|
|
|
3924
4151
|
function unitPath(scope) {
|
|
3925
4152
|
const platform = process.platform;
|
|
3926
4153
|
if (platform === "darwin") {
|
|
3927
|
-
return scope === "user" ?
|
|
4154
|
+
return scope === "user" ? path22.join(os6.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path22.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
|
|
3928
4155
|
}
|
|
3929
4156
|
if (platform === "linux") {
|
|
3930
|
-
return scope === "user" ?
|
|
4157
|
+
return scope === "user" ? path22.join(os6.homedir(), ".config", "systemd", "user", `${SERVICE_LABEL}.service`) : path22.join("/", "etc", "systemd", "system", `${SERVICE_LABEL}.service`);
|
|
3931
4158
|
}
|
|
3932
4159
|
throw new Error(`Unsupported platform for service install: ${platform}`);
|
|
3933
4160
|
}
|
|
3934
4161
|
function logDir() {
|
|
3935
|
-
return
|
|
4162
|
+
return path22.join(os6.homedir(), ".task0", "logs");
|
|
3936
4163
|
}
|
|
3937
4164
|
function logPaths() {
|
|
3938
4165
|
const dir = logDir();
|
|
3939
4166
|
return {
|
|
3940
|
-
out:
|
|
3941
|
-
err:
|
|
4167
|
+
out: path22.join(dir, "daemon.out.log"),
|
|
4168
|
+
err: path22.join(dir, "daemon.err.log")
|
|
3942
4169
|
};
|
|
3943
4170
|
}
|
|
3944
4171
|
|
|
@@ -3991,7 +4218,7 @@ function serviceTarget(scope) {
|
|
|
3991
4218
|
return `${domainTarget(scope)}/${SERVICE_LABEL}`;
|
|
3992
4219
|
}
|
|
3993
4220
|
function run2(cmd, args) {
|
|
3994
|
-
const res =
|
|
4221
|
+
const res = spawnSync6(cmd, args, { encoding: "utf-8" });
|
|
3995
4222
|
return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
3996
4223
|
}
|
|
3997
4224
|
function createLaunchdManager(scope) {
|
|
@@ -3999,8 +4226,8 @@ function createLaunchdManager(scope) {
|
|
|
3999
4226
|
const logs = logPaths();
|
|
4000
4227
|
async function install() {
|
|
4001
4228
|
const inv = resolveTask0Invocation();
|
|
4002
|
-
|
|
4003
|
-
|
|
4229
|
+
fs24.mkdirSync(logDir(), { recursive: true });
|
|
4230
|
+
fs24.mkdirSync(path23.dirname(file), { recursive: true });
|
|
4004
4231
|
const body = renderPlist({
|
|
4005
4232
|
node: inv.node,
|
|
4006
4233
|
main: inv.main,
|
|
@@ -4009,7 +4236,7 @@ function createLaunchdManager(scope) {
|
|
|
4009
4236
|
out: logs.out,
|
|
4010
4237
|
err: logs.err
|
|
4011
4238
|
});
|
|
4012
|
-
|
|
4239
|
+
fs24.writeFileSync(file, body, { mode: 420 });
|
|
4013
4240
|
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
4014
4241
|
if (bootstrap.code !== 0) {
|
|
4015
4242
|
const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
|
|
@@ -4035,9 +4262,9 @@ function createLaunchdManager(scope) {
|
|
|
4035
4262
|
}
|
|
4036
4263
|
async function uninstall() {
|
|
4037
4264
|
run2("launchctl", ["bootout", serviceTarget(scope)]);
|
|
4038
|
-
if (
|
|
4265
|
+
if (fs24.existsSync(file)) {
|
|
4039
4266
|
run2("launchctl", ["unload", file]);
|
|
4040
|
-
|
|
4267
|
+
fs24.unlinkSync(file);
|
|
4041
4268
|
}
|
|
4042
4269
|
}
|
|
4043
4270
|
async function start() {
|
|
@@ -4056,7 +4283,7 @@ function createLaunchdManager(scope) {
|
|
|
4056
4283
|
}
|
|
4057
4284
|
}
|
|
4058
4285
|
async function status() {
|
|
4059
|
-
if (!
|
|
4286
|
+
if (!fs24.existsSync(file)) return "absent";
|
|
4060
4287
|
const printed = run2("launchctl", ["print", serviceTarget(scope)]);
|
|
4061
4288
|
if (printed.code !== 0) return "installed";
|
|
4062
4289
|
const out = printed.stdout;
|
|
@@ -4078,9 +4305,9 @@ function createLaunchdManager(scope) {
|
|
|
4078
4305
|
}
|
|
4079
4306
|
|
|
4080
4307
|
// src/core/daemon-service/systemd.ts
|
|
4081
|
-
import { spawnSync as
|
|
4082
|
-
import
|
|
4083
|
-
import
|
|
4308
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
4309
|
+
import fs25 from "fs";
|
|
4310
|
+
import path24 from "path";
|
|
4084
4311
|
function shellEscape(s) {
|
|
4085
4312
|
if (!/[\s"\\$]/.test(s)) return s;
|
|
4086
4313
|
return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
|
|
@@ -4110,7 +4337,7 @@ function scopeFlag(scope) {
|
|
|
4110
4337
|
return scope === "user" ? ["--user"] : [];
|
|
4111
4338
|
}
|
|
4112
4339
|
function run3(cmd, args) {
|
|
4113
|
-
const res =
|
|
4340
|
+
const res = spawnSync7(cmd, args, { encoding: "utf-8" });
|
|
4114
4341
|
return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
4115
4342
|
}
|
|
4116
4343
|
function createSystemdManager(scope) {
|
|
@@ -4119,8 +4346,8 @@ function createSystemdManager(scope) {
|
|
|
4119
4346
|
const unitName = `${SERVICE_LABEL}.service`;
|
|
4120
4347
|
async function install() {
|
|
4121
4348
|
const inv = resolveTask0Invocation();
|
|
4122
|
-
|
|
4123
|
-
|
|
4349
|
+
fs25.mkdirSync(logDir(), { recursive: true });
|
|
4350
|
+
fs25.mkdirSync(path24.dirname(file), { recursive: true });
|
|
4124
4351
|
const body = renderUnit({
|
|
4125
4352
|
node: inv.node,
|
|
4126
4353
|
main: inv.main,
|
|
@@ -4129,7 +4356,7 @@ function createSystemdManager(scope) {
|
|
|
4129
4356
|
err: logs.err,
|
|
4130
4357
|
scope
|
|
4131
4358
|
});
|
|
4132
|
-
|
|
4359
|
+
fs25.writeFileSync(file, body, { mode: 420 });
|
|
4133
4360
|
const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4134
4361
|
if (reload.code !== 0) {
|
|
4135
4362
|
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
@@ -4138,8 +4365,8 @@ function createSystemdManager(scope) {
|
|
|
4138
4365
|
}
|
|
4139
4366
|
async function uninstall() {
|
|
4140
4367
|
run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
|
|
4141
|
-
if (
|
|
4142
|
-
|
|
4368
|
+
if (fs25.existsSync(file)) {
|
|
4369
|
+
fs25.unlinkSync(file);
|
|
4143
4370
|
}
|
|
4144
4371
|
run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4145
4372
|
}
|
|
@@ -4156,7 +4383,7 @@ function createSystemdManager(scope) {
|
|
|
4156
4383
|
}
|
|
4157
4384
|
}
|
|
4158
4385
|
async function status() {
|
|
4159
|
-
if (!
|
|
4386
|
+
if (!fs25.existsSync(file)) return "absent";
|
|
4160
4387
|
const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
|
|
4161
4388
|
const out = res.stdout.trim();
|
|
4162
4389
|
if (out === "active") return "running";
|
|
@@ -4212,7 +4439,7 @@ function sendRpc(ws, payload) {
|
|
|
4212
4439
|
}
|
|
4213
4440
|
}
|
|
4214
4441
|
function fail6(message, code = 1) {
|
|
4215
|
-
console.error(
|
|
4442
|
+
console.error(chalk19.red(message));
|
|
4216
4443
|
process.exit(code);
|
|
4217
4444
|
}
|
|
4218
4445
|
function loadRequiredIdentity() {
|
|
@@ -4226,10 +4453,18 @@ function serverBase(identity) {
|
|
|
4226
4453
|
if (identity) return identity.server_url.replace(/\/$/, "");
|
|
4227
4454
|
return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
4228
4455
|
}
|
|
4456
|
+
function adminHeaders() {
|
|
4457
|
+
try {
|
|
4458
|
+
return adminAuthHeader();
|
|
4459
|
+
} catch (error2) {
|
|
4460
|
+
if (error2 instanceof AdminTokenUnavailableError) fail6(error2.message);
|
|
4461
|
+
throw error2;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4229
4464
|
async function jsonGet(url) {
|
|
4230
4465
|
let res;
|
|
4231
4466
|
try {
|
|
4232
|
-
res = await fetch(url);
|
|
4467
|
+
res = await fetch(url, { headers: adminHeaders() });
|
|
4233
4468
|
} catch (error2) {
|
|
4234
4469
|
fail6(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
4235
4470
|
}
|
|
@@ -4242,16 +4477,16 @@ function requireRootIfSystem(scope, rerunHint) {
|
|
|
4242
4477
|
if (scope !== "system") return;
|
|
4243
4478
|
const uid = process.getuid?.();
|
|
4244
4479
|
if (uid === 0) return;
|
|
4245
|
-
console.error(
|
|
4480
|
+
console.error(chalk19.red("--system requires root."));
|
|
4246
4481
|
console.error("Re-run with sudo, preserving env:");
|
|
4247
|
-
console.error(
|
|
4482
|
+
console.error(chalk19.cyan(` sudo -E ${rerunHint}`));
|
|
4248
4483
|
process.exit(1);
|
|
4249
4484
|
}
|
|
4250
4485
|
function rerunArgv() {
|
|
4251
4486
|
return ["task0", ...process.argv.slice(2)].join(" ");
|
|
4252
4487
|
}
|
|
4253
|
-
var daemonCmd = new
|
|
4254
|
-
daemonCmd.command("register").description("Register this host with a central server, install the autostart service, and start it").requiredOption("-s, --server <url>", "Central server URL (e.g. https://central.example.com:4318)").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
|
|
4488
|
+
var daemonCmd = new Command19("daemon").description("Manage this host as a task0 daemon registered with a central server");
|
|
4489
|
+
daemonCmd.command("register").description("Register this host with a central server, install the autostart service, and start it").requiredOption("-s, --server <url>", "Central server URL (e.g. https://central.example.com:4318)").option("-n, --name <name>", "Display name for this daemon (defaults to hostname)").option("-t, --token <token>", "API token (apit_...) for registration. Falls back to TASK0_API_TOKEN, then admin token.").option("--force", "Overwrite existing identity if present").option("--system", "Install at the system layer (LaunchDaemons / /etc/systemd/system, requires sudo)").option("--no-install", "Skip installing the autostart service unit").option("--no-start", "Install the service unit but do not start it now").action(async (opts) => {
|
|
4255
4490
|
const scope = opts.system ? "system" : "user";
|
|
4256
4491
|
if (opts.install) {
|
|
4257
4492
|
requireRootIfSystem(scope, rerunArgv());
|
|
@@ -4261,8 +4496,19 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4261
4496
|
fail6(`Already registered as ${existing.daemon_id}. Pass --force to re-register.`);
|
|
4262
4497
|
}
|
|
4263
4498
|
const base = opts.server.replace(/\/$/, "");
|
|
4499
|
+
let authHeader;
|
|
4500
|
+
try {
|
|
4501
|
+
const choice = pickRegisterAuth(opts.token);
|
|
4502
|
+
authHeader = choice.header;
|
|
4503
|
+
if (choice.source === "admin") {
|
|
4504
|
+
console.log(chalk19.dim("Registering with admin token (legacy). Prefer an API token from the dashboard."));
|
|
4505
|
+
}
|
|
4506
|
+
} catch (error2) {
|
|
4507
|
+
if (error2 instanceof RegisterAuthUnavailableError) fail6(error2.message);
|
|
4508
|
+
throw error2;
|
|
4509
|
+
}
|
|
4264
4510
|
const body = {
|
|
4265
|
-
hostname:
|
|
4511
|
+
hostname: os7.hostname(),
|
|
4266
4512
|
platform: process.platform,
|
|
4267
4513
|
name: opts.name
|
|
4268
4514
|
};
|
|
@@ -4270,7 +4516,7 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4270
4516
|
try {
|
|
4271
4517
|
res = await fetch(`${base}/api/daemons/register`, {
|
|
4272
4518
|
method: "POST",
|
|
4273
|
-
headers: { "content-type": "application/json" },
|
|
4519
|
+
headers: { "content-type": "application/json", ...authHeader },
|
|
4274
4520
|
body: JSON.stringify(body)
|
|
4275
4521
|
});
|
|
4276
4522
|
} catch (error2) {
|
|
@@ -4290,16 +4536,16 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4290
4536
|
registered_at: data.daemon.registered_at
|
|
4291
4537
|
};
|
|
4292
4538
|
writeDaemonIdentity(identity);
|
|
4293
|
-
console.log(
|
|
4539
|
+
console.log(chalk19.green(`Registered as ${data.daemon.object_id} (${data.daemon.name})`));
|
|
4294
4540
|
console.log(`Identity saved to ${daemonConfigPath()}`);
|
|
4295
4541
|
if (!opts.install) {
|
|
4296
|
-
console.log(
|
|
4297
|
-
console.log(
|
|
4542
|
+
console.log(chalk19.dim("Skipping service install (--no-install)."));
|
|
4543
|
+
console.log(chalk19.dim(`Run \`task0 daemon run\` to start the WebSocket loop in foreground.`));
|
|
4298
4544
|
return;
|
|
4299
4545
|
}
|
|
4300
4546
|
if (!isPlatformSupported()) {
|
|
4301
4547
|
console.log(
|
|
4302
|
-
|
|
4548
|
+
chalk19.yellow(
|
|
4303
4549
|
`Autostart service is not supported on ${process.platform}. Run \`task0 daemon run\` under a supervisor of your choice.`
|
|
4304
4550
|
)
|
|
4305
4551
|
);
|
|
@@ -4309,18 +4555,18 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4309
4555
|
try {
|
|
4310
4556
|
const { unitPath: unitPath2 } = await svc.install({ identity });
|
|
4311
4557
|
const logs = svc.logPaths();
|
|
4312
|
-
console.log(
|
|
4313
|
-
console.log(
|
|
4314
|
-
console.log(
|
|
4558
|
+
console.log(chalk19.green(`Installed service unit at ${unitPath2}`));
|
|
4559
|
+
console.log(chalk19.dim(`Logs: ${logs.out}`));
|
|
4560
|
+
console.log(chalk19.dim(` ${logs.err}`));
|
|
4315
4561
|
if (opts.start) {
|
|
4316
4562
|
await svc.start();
|
|
4317
|
-
console.log(
|
|
4563
|
+
console.log(chalk19.green("Service started."));
|
|
4318
4564
|
} else {
|
|
4319
|
-
console.log(
|
|
4565
|
+
console.log(chalk19.dim("Skipping start (--no-start). Run `task0 daemon start` when ready."));
|
|
4320
4566
|
}
|
|
4321
4567
|
} catch (error2) {
|
|
4322
|
-
console.error(
|
|
4323
|
-
console.error(
|
|
4568
|
+
console.error(chalk19.red(`Service install failed: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
4569
|
+
console.error(chalk19.dim("Identity is saved; rerun `task0 daemon register --force` once the issue is resolved."));
|
|
4324
4570
|
process.exit(1);
|
|
4325
4571
|
}
|
|
4326
4572
|
});
|
|
@@ -4333,7 +4579,7 @@ daemonCmd.command("start").description("Start the installed autostart service vi
|
|
|
4333
4579
|
const svc = getServiceManager(scope);
|
|
4334
4580
|
try {
|
|
4335
4581
|
await svc.start();
|
|
4336
|
-
console.log(
|
|
4582
|
+
console.log(chalk19.green("Service started."));
|
|
4337
4583
|
} catch (error2) {
|
|
4338
4584
|
fail6(error2 instanceof Error ? error2.message : String(error2));
|
|
4339
4585
|
}
|
|
@@ -4347,7 +4593,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
|
|
|
4347
4593
|
const svc = getServiceManager(scope);
|
|
4348
4594
|
try {
|
|
4349
4595
|
await svc.stop();
|
|
4350
|
-
console.log(
|
|
4596
|
+
console.log(chalk19.green("Service stopped."));
|
|
4351
4597
|
} catch (error2) {
|
|
4352
4598
|
fail6(error2 instanceof Error ? error2.message : String(error2));
|
|
4353
4599
|
}
|
|
@@ -4355,7 +4601,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
|
|
|
4355
4601
|
daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
|
|
4356
4602
|
const identity = loadRequiredIdentity();
|
|
4357
4603
|
const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
|
|
4358
|
-
console.log(
|
|
4604
|
+
console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
|
|
4359
4605
|
let reconnectDelay = 1e3;
|
|
4360
4606
|
let shouldRun = true;
|
|
4361
4607
|
let activeWs = null;
|
|
@@ -4366,7 +4612,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4366
4612
|
activeWs = ws;
|
|
4367
4613
|
ws.on("open", () => {
|
|
4368
4614
|
reconnectDelay = 1e3;
|
|
4369
|
-
console.log(
|
|
4615
|
+
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
4370
4616
|
const hello = {
|
|
4371
4617
|
type: "hello",
|
|
4372
4618
|
daemon_id: identity.daemon_id,
|
|
@@ -4378,7 +4624,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4378
4624
|
const projects = loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
|
|
4379
4625
|
const manifest = { type: "manifest", projects };
|
|
4380
4626
|
ws.send(JSON.stringify(manifest));
|
|
4381
|
-
console.log(
|
|
4627
|
+
console.log(chalk19.dim(`pushed manifest: ${projects.length} project(s)`));
|
|
4382
4628
|
});
|
|
4383
4629
|
ws.on("message", (raw) => {
|
|
4384
4630
|
let msg;
|
|
@@ -4388,12 +4634,12 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4388
4634
|
return;
|
|
4389
4635
|
}
|
|
4390
4636
|
if (msg.type === "welcome") {
|
|
4391
|
-
console.log(
|
|
4637
|
+
console.log(chalk19.green(`welcomed ${msg.server_time}`));
|
|
4392
4638
|
} else if (msg.type === "ping") {
|
|
4393
4639
|
const beat = { type: "heartbeat", ts: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4394
4640
|
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(beat));
|
|
4395
4641
|
} else if (msg.type === "error") {
|
|
4396
|
-
console.error(
|
|
4642
|
+
console.error(chalk19.yellow(`server error: ${msg.message}`));
|
|
4397
4643
|
} else if (msg.type === "rpc_request") {
|
|
4398
4644
|
void dispatchRpc(ws, msg.id, msg.method, msg.params);
|
|
4399
4645
|
}
|
|
@@ -4401,9 +4647,9 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4401
4647
|
ws.on("close", (code, reason) => {
|
|
4402
4648
|
activeWs = null;
|
|
4403
4649
|
const reasonText = reason.toString("utf-8") || "no reason";
|
|
4404
|
-
console.log(
|
|
4650
|
+
console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
|
|
4405
4651
|
if (code === 4001) {
|
|
4406
|
-
console.error(
|
|
4652
|
+
console.error(chalk19.red("superseded by another daemon connection; exiting"));
|
|
4407
4653
|
process.exit(0);
|
|
4408
4654
|
}
|
|
4409
4655
|
if (shouldRun) {
|
|
@@ -4412,7 +4658,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4412
4658
|
}
|
|
4413
4659
|
});
|
|
4414
4660
|
ws.on("error", (err) => {
|
|
4415
|
-
console.error(
|
|
4661
|
+
console.error(chalk19.red(`ws error: ${err.message}`));
|
|
4416
4662
|
});
|
|
4417
4663
|
}
|
|
4418
4664
|
const shutdown = () => {
|
|
@@ -4437,9 +4683,9 @@ daemonCmd.command("list").description("List daemons registered on the configured
|
|
|
4437
4683
|
return;
|
|
4438
4684
|
}
|
|
4439
4685
|
for (const d of data.daemons) {
|
|
4440
|
-
const status = d.status === "online" ?
|
|
4686
|
+
const status = d.status === "online" ? chalk19.green(d.status) : chalk19.dim(d.status);
|
|
4441
4687
|
console.log(
|
|
4442
|
-
`${
|
|
4688
|
+
`${chalk19.bold(d.object_id)} ${d.name.padEnd(20)} ${d.hostname.padEnd(24)} ${d.platform.padEnd(8)} ${status}`
|
|
4443
4689
|
);
|
|
4444
4690
|
}
|
|
4445
4691
|
});
|
|
@@ -4452,9 +4698,9 @@ daemonCmd.command("show [daemonId]").description("Show local daemon identity (no
|
|
|
4452
4698
|
try {
|
|
4453
4699
|
const svc = getServiceManager(scope);
|
|
4454
4700
|
const state2 = await svc.status();
|
|
4455
|
-
console.log(
|
|
4701
|
+
console.log(chalk19.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
|
|
4456
4702
|
} catch (error2) {
|
|
4457
|
-
console.log(
|
|
4703
|
+
console.log(chalk19.dim(`service status unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
4458
4704
|
}
|
|
4459
4705
|
}
|
|
4460
4706
|
return;
|
|
@@ -4471,21 +4717,169 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
|
|
|
4471
4717
|
const svc = getServiceManager(scope);
|
|
4472
4718
|
try {
|
|
4473
4719
|
await svc.uninstall();
|
|
4474
|
-
console.log(
|
|
4720
|
+
console.log(chalk19.dim(`Service unit removed (${scope}).`));
|
|
4475
4721
|
} catch (error2) {
|
|
4476
4722
|
console.error(
|
|
4477
|
-
|
|
4723
|
+
chalk19.yellow(`Service uninstall warning: ${error2 instanceof Error ? error2.message : String(error2)}`)
|
|
4478
4724
|
);
|
|
4479
4725
|
}
|
|
4480
4726
|
}
|
|
4481
4727
|
}
|
|
4482
4728
|
if (clearDaemonIdentity()) {
|
|
4483
|
-
console.log(
|
|
4729
|
+
console.log(chalk19.green("Daemon identity cleared."));
|
|
4484
4730
|
} else {
|
|
4485
4731
|
console.log("No daemon identity to clear.");
|
|
4486
4732
|
}
|
|
4487
4733
|
});
|
|
4488
4734
|
|
|
4735
|
+
// src/commands/user.ts
|
|
4736
|
+
import { Command as Command20 } from "commander";
|
|
4737
|
+
import chalk20 from "chalk";
|
|
4738
|
+
var DEFAULT_BASE2 = (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
4739
|
+
function serverBase2() {
|
|
4740
|
+
return DEFAULT_BASE2;
|
|
4741
|
+
}
|
|
4742
|
+
function fail7(message, code = 1) {
|
|
4743
|
+
console.error(chalk20.red(message));
|
|
4744
|
+
process.exit(code);
|
|
4745
|
+
}
|
|
4746
|
+
async function callServer(path26, init = {}) {
|
|
4747
|
+
const url = `${serverBase2()}${path26}`;
|
|
4748
|
+
let auth;
|
|
4749
|
+
try {
|
|
4750
|
+
auth = adminAuthHeader();
|
|
4751
|
+
} catch (error2) {
|
|
4752
|
+
if (error2 instanceof AdminTokenUnavailableError) fail7(error2.message);
|
|
4753
|
+
throw error2;
|
|
4754
|
+
}
|
|
4755
|
+
const headers = { ...auth, ...init.headers };
|
|
4756
|
+
let res;
|
|
4757
|
+
try {
|
|
4758
|
+
res = await fetch(url, { ...init, headers });
|
|
4759
|
+
} catch (error2) {
|
|
4760
|
+
fail7(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
4761
|
+
}
|
|
4762
|
+
if (res.status === 204) return { status: 204, body: null };
|
|
4763
|
+
const text = await res.text();
|
|
4764
|
+
let body = null;
|
|
4765
|
+
if (text.length > 0) {
|
|
4766
|
+
try {
|
|
4767
|
+
body = JSON.parse(text);
|
|
4768
|
+
} catch {
|
|
4769
|
+
body = null;
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
return { status: res.status, body };
|
|
4773
|
+
}
|
|
4774
|
+
function promptHidden(prompt) {
|
|
4775
|
+
return new Promise((resolve, reject) => {
|
|
4776
|
+
const stdin = process.stdin;
|
|
4777
|
+
const stdout = process.stdout;
|
|
4778
|
+
if (!stdin.isTTY) {
|
|
4779
|
+
reject(new Error("stdin is not a TTY; password input requires an interactive terminal"));
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
stdout.write(prompt);
|
|
4783
|
+
stdin.setRawMode(true);
|
|
4784
|
+
stdin.resume();
|
|
4785
|
+
stdin.setEncoding("utf8");
|
|
4786
|
+
let buf = "";
|
|
4787
|
+
const onData = (raw) => {
|
|
4788
|
+
for (const ch of raw) {
|
|
4789
|
+
const code = ch.charCodeAt(0);
|
|
4790
|
+
if (ch === "\n" || ch === "\r" || code === 4) {
|
|
4791
|
+
stdin.removeListener("data", onData);
|
|
4792
|
+
stdin.setRawMode(false);
|
|
4793
|
+
stdin.pause();
|
|
4794
|
+
stdout.write("\n");
|
|
4795
|
+
resolve(buf);
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
4798
|
+
if (code === 3) {
|
|
4799
|
+
stdin.removeListener("data", onData);
|
|
4800
|
+
stdin.setRawMode(false);
|
|
4801
|
+
stdin.pause();
|
|
4802
|
+
stdout.write("\n");
|
|
4803
|
+
process.exit(130);
|
|
4804
|
+
}
|
|
4805
|
+
if (code === 127 || code === 8) {
|
|
4806
|
+
buf = buf.slice(0, -1);
|
|
4807
|
+
continue;
|
|
4808
|
+
}
|
|
4809
|
+
if (code < 32) continue;
|
|
4810
|
+
buf += ch;
|
|
4811
|
+
}
|
|
4812
|
+
};
|
|
4813
|
+
stdin.on("data", onData);
|
|
4814
|
+
});
|
|
4815
|
+
}
|
|
4816
|
+
async function readPasswordWithConfirm() {
|
|
4817
|
+
const a = await promptHidden("Password: ");
|
|
4818
|
+
if (!a) fail7("Password must not be empty.");
|
|
4819
|
+
const b = await promptHidden("Confirm password: ");
|
|
4820
|
+
if (a !== b) fail7("Passwords do not match.");
|
|
4821
|
+
return a;
|
|
4822
|
+
}
|
|
4823
|
+
var userCmd = new Command20("user").description("Manage dashboard login accounts");
|
|
4824
|
+
userCmd.command("create <username>").description("Create a new dashboard login account (prompts for password)").action(async (username) => {
|
|
4825
|
+
const password = await readPasswordWithConfirm();
|
|
4826
|
+
const { status, body } = await callServer(
|
|
4827
|
+
"/api/auth/users",
|
|
4828
|
+
{
|
|
4829
|
+
method: "POST",
|
|
4830
|
+
headers: { "content-type": "application/json" },
|
|
4831
|
+
body: JSON.stringify({ username, password })
|
|
4832
|
+
}
|
|
4833
|
+
);
|
|
4834
|
+
if (status === 201 && body?.user) {
|
|
4835
|
+
console.log(chalk20.green(`Created user ${body.user.username} (${body.user.object_id})`));
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
if (status === 409) fail7(body?.error || "username already exists");
|
|
4839
|
+
fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
|
|
4840
|
+
});
|
|
4841
|
+
userCmd.command("list").description("List dashboard login accounts").action(async () => {
|
|
4842
|
+
const { status, body } = await callServer("/api/auth/users");
|
|
4843
|
+
if (status !== 200 || !body) fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
|
|
4844
|
+
if (body.users.length === 0) {
|
|
4845
|
+
console.log("No users. Use `task0 user create <username>` to create one.");
|
|
4846
|
+
return;
|
|
4847
|
+
}
|
|
4848
|
+
const nameWidth = Math.max(...body.users.map((u) => u.username.length), 4);
|
|
4849
|
+
for (const u of body.users) {
|
|
4850
|
+
console.log(`${chalk20.cyan(u.object_id)} ${u.username.padEnd(nameWidth)} ${chalk20.dim(u.created_at)}`);
|
|
4851
|
+
}
|
|
4852
|
+
});
|
|
4853
|
+
userCmd.command("delete <username>").description("Delete a dashboard login account").action(async (username) => {
|
|
4854
|
+
const { status, body } = await callServer(
|
|
4855
|
+
`/api/auth/users/${encodeURIComponent(username)}`,
|
|
4856
|
+
{ method: "DELETE" }
|
|
4857
|
+
);
|
|
4858
|
+
if (status === 204) {
|
|
4859
|
+
console.log(chalk20.green(`Deleted user ${username}`));
|
|
4860
|
+
return;
|
|
4861
|
+
}
|
|
4862
|
+
if (status === 404) fail7(body?.error || "user not found");
|
|
4863
|
+
fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
|
|
4864
|
+
});
|
|
4865
|
+
userCmd.command("passwd <username>").description("Reset a user's password (prompts for new password)").action(async (username) => {
|
|
4866
|
+
const password = await readPasswordWithConfirm();
|
|
4867
|
+
const { status, body } = await callServer(
|
|
4868
|
+
`/api/auth/users/${encodeURIComponent(username)}/password`,
|
|
4869
|
+
{
|
|
4870
|
+
method: "POST",
|
|
4871
|
+
headers: { "content-type": "application/json" },
|
|
4872
|
+
body: JSON.stringify({ password })
|
|
4873
|
+
}
|
|
4874
|
+
);
|
|
4875
|
+
if (status === 204) {
|
|
4876
|
+
console.log(chalk20.green(`Updated password for ${username}`));
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
if (status === 404) fail7(body?.error || "user not found");
|
|
4880
|
+
fail7(`Server returned ${status}: ${body?.error ?? "unknown error"}`);
|
|
4881
|
+
});
|
|
4882
|
+
|
|
4489
4883
|
// src/commands/error.ts
|
|
4490
4884
|
init_node();
|
|
4491
4885
|
import { Command as Command21 } from "commander";
|
|
@@ -4650,6 +5044,209 @@ error.command("prune").description("Remove captured error reports by age, count,
|
|
|
4650
5044
|
console.log(`Removed ${res.removed.length} report(s); ${res.kept} remaining.`);
|
|
4651
5045
|
});
|
|
4652
5046
|
|
|
5047
|
+
// src/commands/automation.ts
|
|
5048
|
+
import { Command as Command22 } from "commander";
|
|
5049
|
+
import chalk22 from "chalk";
|
|
5050
|
+
function fail8(message) {
|
|
5051
|
+
console.error(chalk22.red(message));
|
|
5052
|
+
process.exit(1);
|
|
5053
|
+
}
|
|
5054
|
+
function shortIso(value) {
|
|
5055
|
+
if (!value) return "\u2014";
|
|
5056
|
+
return value.replace("T", " ").replace(/\.\d{3}Z$/, "Z").replace(/Z$/, " UTC");
|
|
5057
|
+
}
|
|
5058
|
+
function buildPresetRrule(preset, opts) {
|
|
5059
|
+
switch (preset) {
|
|
5060
|
+
case "hourly": {
|
|
5061
|
+
const minute = clampInt(opts.minute ?? "0", 0, 59);
|
|
5062
|
+
return `RRULE:FREQ=HOURLY;BYMINUTE=${minute};BYSECOND=0`;
|
|
5063
|
+
}
|
|
5064
|
+
case "daily": {
|
|
5065
|
+
const { hour, minute } = parseHHMM(opts.time ?? "09:00");
|
|
5066
|
+
return `RRULE:FREQ=DAILY;BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
|
|
5067
|
+
}
|
|
5068
|
+
case "weekdays": {
|
|
5069
|
+
const { hour, minute } = parseHHMM(opts.time ?? "09:00");
|
|
5070
|
+
return `RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
|
|
5071
|
+
}
|
|
5072
|
+
case "weekly": {
|
|
5073
|
+
const { hour, minute } = parseHHMM(opts.time ?? "09:00");
|
|
5074
|
+
const codes = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"];
|
|
5075
|
+
const idx = clampInt(opts.weekday ?? "1", 0, 6);
|
|
5076
|
+
return `RRULE:FREQ=WEEKLY;BYDAY=${codes[idx]};BYHOUR=${hour};BYMINUTE=${minute};BYSECOND=0`;
|
|
5077
|
+
}
|
|
5078
|
+
case "custom": {
|
|
5079
|
+
if (!opts.custom) throw new Error("custom preset requires --rrule");
|
|
5080
|
+
const trimmed = opts.custom.trim();
|
|
5081
|
+
if (!trimmed.startsWith("RRULE:")) throw new Error('custom rrule must start with "RRULE:"');
|
|
5082
|
+
return trimmed;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
function parseHHMM(value) {
|
|
5087
|
+
const m = value.match(/^(\d{1,2}):(\d{2})$/);
|
|
5088
|
+
if (!m) throw new Error(`invalid --time, expected HH:MM, got: ${value}`);
|
|
5089
|
+
return {
|
|
5090
|
+
hour: clampInt(m[1], 0, 23),
|
|
5091
|
+
minute: clampInt(m[2], 0, 59)
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
5094
|
+
function clampInt(value, lo, hi) {
|
|
5095
|
+
const n = Number(value);
|
|
5096
|
+
if (!Number.isFinite(n)) return lo;
|
|
5097
|
+
return Math.max(lo, Math.min(hi, Math.floor(n)));
|
|
5098
|
+
}
|
|
5099
|
+
function printAutomation(a) {
|
|
5100
|
+
const status = a.status === "active" ? chalk22.green("active") : chalk22.yellow("paused");
|
|
5101
|
+
console.log(
|
|
5102
|
+
`${chalk22.cyan(a.object_id.padEnd(10))} ${status.padEnd(15)} ${a.schedule_preset.padEnd(8)} next=${shortIso(a.next_run_at)}`
|
|
5103
|
+
);
|
|
5104
|
+
console.log(` ${chalk22.bold(a.title)}`);
|
|
5105
|
+
console.log(
|
|
5106
|
+
` project=${a.project_name} agent=${a.agent_object_id} skill=${a.skill_file_path}`
|
|
5107
|
+
);
|
|
5108
|
+
console.log(` rrule=${chalk22.dim(a.rrule)} tz=${a.timezone}`);
|
|
5109
|
+
}
|
|
5110
|
+
function printRun(r) {
|
|
5111
|
+
const status = (() => {
|
|
5112
|
+
switch (r.status) {
|
|
5113
|
+
case "succeeded":
|
|
5114
|
+
return chalk22.green(r.status);
|
|
5115
|
+
case "failed":
|
|
5116
|
+
return chalk22.red(r.status);
|
|
5117
|
+
case "running":
|
|
5118
|
+
return chalk22.yellow(r.status);
|
|
5119
|
+
case "pending":
|
|
5120
|
+
return chalk22.dim(r.status);
|
|
5121
|
+
}
|
|
5122
|
+
})();
|
|
5123
|
+
console.log(
|
|
5124
|
+
`${chalk22.cyan(r.object_id.padEnd(10))} ${status.padEnd(18)} ${r.trigger.padEnd(10)} started=${shortIso(r.started_at)} finished=${shortIso(r.finished_at)}`
|
|
5125
|
+
);
|
|
5126
|
+
console.log(` ${chalk22.dim(r.title_snapshot)}`);
|
|
5127
|
+
if (r.agent_run_object_id) console.log(` agent_run=${chalk22.dim(r.agent_run_object_id)}`);
|
|
5128
|
+
if (r.error_message) console.log(` ${chalk22.red("error: " + r.error_message)}`);
|
|
5129
|
+
}
|
|
5130
|
+
function maybeJson2(view, opts) {
|
|
5131
|
+
if (opts.json) {
|
|
5132
|
+
console.log(JSON.stringify(view, null, 2));
|
|
5133
|
+
return true;
|
|
5134
|
+
}
|
|
5135
|
+
return false;
|
|
5136
|
+
}
|
|
5137
|
+
var automation = new Command22("automation").description(
|
|
5138
|
+
"Manage automations \u2014 scheduled or manual AgentRuns"
|
|
5139
|
+
);
|
|
5140
|
+
automation.command("list").description("List automations").option("-p, --project <name>", "Filter by project (defaults to cwd if no other filter)").option("--status <status>", "active|paused").option("--json", "Output JSON").option("--all", "Do not filter by project").action(async (opts) => {
|
|
5141
|
+
const query = new URLSearchParams();
|
|
5142
|
+
if (!opts.all) {
|
|
5143
|
+
const projectName = opts.project ?? safeResolveProject();
|
|
5144
|
+
if (projectName) query.set("project", projectName);
|
|
5145
|
+
}
|
|
5146
|
+
if (opts.status) query.set("status", opts.status);
|
|
5147
|
+
const qs = query.toString();
|
|
5148
|
+
const url = qs ? `/api/automations?${qs}` : "/api/automations";
|
|
5149
|
+
const { automations } = await api.get(url);
|
|
5150
|
+
if (maybeJson2(automations, opts)) return;
|
|
5151
|
+
if (automations.length === 0) {
|
|
5152
|
+
console.log(chalk22.dim("No automations"));
|
|
5153
|
+
return;
|
|
5154
|
+
}
|
|
5155
|
+
for (const a of automations) printAutomation(a);
|
|
5156
|
+
});
|
|
5157
|
+
function safeResolveProject() {
|
|
5158
|
+
try {
|
|
5159
|
+
return resolveProjectName({});
|
|
5160
|
+
} catch {
|
|
5161
|
+
return void 0;
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
automation.command("create").description("Create a new automation").requiredOption("--title <title>", "Display name").option("-p, --project <name>", "Project name (defaults to cwd)").requiredOption("--agent <object_id>", "Agent object id (agt_*)").requiredOption("--skill <path>", "Absolute path to the skill markdown file").requiredOption("--prompt <text>", "Additional prompt sent to the agent").requiredOption("--schedule <preset>", "hourly|daily|weekdays|weekly|custom").option("--time <HH:MM>", "Local time for daily/weekdays/weekly presets", "09:00").option("--weekday <0-6>", "Day of week for weekly preset (Sun=0)", "1").option("--minute <0-59>", "Minute for hourly preset", "0").option("--rrule <rrule>", "Custom RRULE string (required when --schedule=custom)").option("--timezone <tz>", "IANA timezone", "UTC").option("--dtstart <iso>", "Schedule anchor (ISO 8601)").option("--model <name>", "Override agent model").option("--paused", "Create in paused status").option("--json", "Output JSON").action(async (opts) => {
|
|
5165
|
+
const presets = ["hourly", "daily", "weekdays", "weekly", "custom"];
|
|
5166
|
+
if (!presets.includes(opts.schedule)) {
|
|
5167
|
+
fail8(`--schedule must be one of ${presets.join("|")}`);
|
|
5168
|
+
}
|
|
5169
|
+
const preset = opts.schedule;
|
|
5170
|
+
let rrule;
|
|
5171
|
+
try {
|
|
5172
|
+
rrule = buildPresetRrule(preset, {
|
|
5173
|
+
time: opts.time,
|
|
5174
|
+
weekday: opts.weekday,
|
|
5175
|
+
minute: opts.minute,
|
|
5176
|
+
custom: opts.rrule
|
|
5177
|
+
});
|
|
5178
|
+
} catch (err) {
|
|
5179
|
+
fail8(err instanceof Error ? err.message : "failed to build rrule");
|
|
5180
|
+
}
|
|
5181
|
+
const projectName = opts.project ?? safeResolveProject();
|
|
5182
|
+
if (!projectName) fail8("Cannot determine project \u2014 pass --project or run inside a registered project");
|
|
5183
|
+
const { automation: automation2 } = await api.post("/api/automations", {
|
|
5184
|
+
title: opts.title,
|
|
5185
|
+
project_name: projectName,
|
|
5186
|
+
agent_object_id: opts.agent,
|
|
5187
|
+
skill_file_path: opts.skill,
|
|
5188
|
+
prompt: opts.prompt,
|
|
5189
|
+
schedule_preset: preset,
|
|
5190
|
+
rrule,
|
|
5191
|
+
timezone: opts.timezone,
|
|
5192
|
+
dtstart: opts.dtstart,
|
|
5193
|
+
model: opts.model ?? null,
|
|
5194
|
+
status: opts.paused ? "paused" : "active"
|
|
5195
|
+
});
|
|
5196
|
+
if (maybeJson2(automation2, opts)) return;
|
|
5197
|
+
console.log(chalk22.green("created:"));
|
|
5198
|
+
printAutomation(automation2);
|
|
5199
|
+
});
|
|
5200
|
+
automation.command("get <id>").description("Show automation detail including recent runs").option("--json", "Output JSON").action(async (id, opts) => {
|
|
5201
|
+
const { automation: automation2 } = await api.get(`/api/automations/${encodeURIComponent(id)}`);
|
|
5202
|
+
if (maybeJson2(automation2, opts)) return;
|
|
5203
|
+
printAutomation(automation2);
|
|
5204
|
+
console.log("");
|
|
5205
|
+
console.log(chalk22.dim(`recent_runs (${automation2.recent_runs.length}):`));
|
|
5206
|
+
for (const run4 of automation2.recent_runs) printRun(run4);
|
|
5207
|
+
});
|
|
5208
|
+
automation.command("update <id>").description("Patch an automation").option("--title <title>").option("--prompt <text>").option("--status <status>", "active|paused").option("--rrule <rrule>").option("--timezone <tz>").option("--dtstart <iso>").option("--model <name>").option("--json", "Output JSON").action(async (id, opts) => {
|
|
5209
|
+
const patch = {};
|
|
5210
|
+
if (opts.title) patch.title = opts.title;
|
|
5211
|
+
if (opts.prompt) patch.prompt = opts.prompt;
|
|
5212
|
+
if (opts.status) patch.status = opts.status;
|
|
5213
|
+
if (opts.rrule) patch.rrule = opts.rrule;
|
|
5214
|
+
if (opts.timezone) patch.timezone = opts.timezone;
|
|
5215
|
+
if (opts.dtstart) patch.dtstart = opts.dtstart;
|
|
5216
|
+
if (opts.model !== void 0) patch.model = opts.model || null;
|
|
5217
|
+
const { automation: automation2 } = await api.patch(
|
|
5218
|
+
`/api/automations/${encodeURIComponent(id)}`,
|
|
5219
|
+
patch
|
|
5220
|
+
);
|
|
5221
|
+
if (maybeJson2(automation2, opts)) return;
|
|
5222
|
+
console.log(chalk22.green("updated:"));
|
|
5223
|
+
printAutomation(automation2);
|
|
5224
|
+
});
|
|
5225
|
+
automation.command("delete <id>").description("Delete an automation and its run history").action(async (id) => {
|
|
5226
|
+
await api.del(`/api/automations/${encodeURIComponent(id)}`);
|
|
5227
|
+
console.log(chalk22.green(`deleted ${id}`));
|
|
5228
|
+
});
|
|
5229
|
+
automation.command("run <id>").description("Trigger an automation immediately (manual)").option("--json", "Output JSON").action(async (id, opts) => {
|
|
5230
|
+
const { automation_run } = await api.post(
|
|
5231
|
+
`/api/automations/${encodeURIComponent(id)}/run`
|
|
5232
|
+
);
|
|
5233
|
+
if (maybeJson2(automation_run, opts)) return;
|
|
5234
|
+
console.log(chalk22.green("started:"));
|
|
5235
|
+
printRun(automation_run);
|
|
5236
|
+
});
|
|
5237
|
+
automation.command("runs <id>").description("List recent runs for an automation").option("--limit <n>", "Max rows to return", "20").option("--json", "Output JSON").action(async (id, opts) => {
|
|
5238
|
+
const limit = Number(opts.limit ?? "20");
|
|
5239
|
+
const { automation_runs } = await api.get(
|
|
5240
|
+
`/api/automations/${encodeURIComponent(id)}/runs?limit=${limit}`
|
|
5241
|
+
);
|
|
5242
|
+
if (maybeJson2(automation_runs, opts)) return;
|
|
5243
|
+
if (automation_runs.length === 0) {
|
|
5244
|
+
console.log(chalk22.dim("No runs"));
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
for (const run4 of automation_runs) printRun(run4);
|
|
5248
|
+
});
|
|
5249
|
+
|
|
4653
5250
|
// src/core/error-capture.ts
|
|
4654
5251
|
init_node();
|
|
4655
5252
|
var DEFAULT_KEEP = 50;
|
|
@@ -4747,24 +5344,34 @@ function captureTopLevel(err, options) {
|
|
|
4747
5344
|
}
|
|
4748
5345
|
|
|
4749
5346
|
// src/main.ts
|
|
4750
|
-
var TASK0_VERSION =
|
|
4751
|
-
|
|
5347
|
+
var TASK0_VERSION = readVersion();
|
|
5348
|
+
function readVersion() {
|
|
5349
|
+
try {
|
|
5350
|
+
const here = path25.dirname(fileURLToPath2(import.meta.url));
|
|
5351
|
+
const pkg = JSON.parse(readFileSync(path25.resolve(here, "..", "package.json"), "utf-8"));
|
|
5352
|
+
return pkg.version ?? "unknown";
|
|
5353
|
+
} catch {
|
|
5354
|
+
return "unknown";
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
var program = new Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
|
|
4752
5358
|
program.addCommand(source);
|
|
4753
5359
|
program.addCommand(project);
|
|
4754
5360
|
program.addCommand(task);
|
|
4755
|
-
program.addCommand(
|
|
5361
|
+
program.addCommand(agentRun);
|
|
4756
5362
|
program.addCommand(plan);
|
|
4757
5363
|
program.addCommand(okr);
|
|
4758
5364
|
program.addCommand(note);
|
|
4759
5365
|
program.addCommand(run);
|
|
4760
5366
|
program.addCommand(ui);
|
|
4761
|
-
program.addCommand(serve);
|
|
4762
5367
|
program.addCommand(models);
|
|
4763
5368
|
program.addCommand(object);
|
|
4764
5369
|
program.addCommand(issue);
|
|
4765
5370
|
program.addCommand(agent);
|
|
4766
5371
|
program.addCommand(daemonCmd);
|
|
5372
|
+
program.addCommand(userCmd);
|
|
4767
5373
|
program.addCommand(error);
|
|
5374
|
+
program.addCommand(automation);
|
|
4768
5375
|
async function main() {
|
|
4769
5376
|
installErrorCapture({ version: TASK0_VERSION });
|
|
4770
5377
|
try {
|