@task0/cli 0.2.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/main.js +1023 -428
- 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 || {} };
|
|
@@ -903,8 +920,8 @@ var init_task_state2 = __esm({
|
|
|
903
920
|
// src/main.ts
|
|
904
921
|
import { readFileSync } from "fs";
|
|
905
922
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
906
|
-
import
|
|
907
|
-
import { Command as
|
|
923
|
+
import path25 from "path";
|
|
924
|
+
import { Command as Command23 } from "commander";
|
|
908
925
|
|
|
909
926
|
// src/commands/source.ts
|
|
910
927
|
import { Command } from "commander";
|
|
@@ -1122,7 +1139,7 @@ async function request(method, pathname, body) {
|
|
|
1122
1139
|
});
|
|
1123
1140
|
} catch (error2) {
|
|
1124
1141
|
const err = new Error(
|
|
1125
|
-
`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.`
|
|
1126
1143
|
);
|
|
1127
1144
|
err.cause = error2;
|
|
1128
1145
|
throw err;
|
|
@@ -1144,11 +1161,11 @@ async function request(method, pathname, body) {
|
|
|
1144
1161
|
return parsed;
|
|
1145
1162
|
}
|
|
1146
1163
|
var api = {
|
|
1147
|
-
get: (
|
|
1148
|
-
post: (
|
|
1149
|
-
put: (
|
|
1150
|
-
patch: (
|
|
1151
|
-
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)
|
|
1152
1169
|
};
|
|
1153
1170
|
|
|
1154
1171
|
// src/commands/task/triage.ts
|
|
@@ -1157,23 +1174,23 @@ import chalk3 from "chalk";
|
|
|
1157
1174
|
import fs10 from "fs";
|
|
1158
1175
|
import path10 from "path";
|
|
1159
1176
|
|
|
1160
|
-
// src/core/
|
|
1161
|
-
async function
|
|
1162
|
-
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)}`);
|
|
1163
1180
|
}
|
|
1164
|
-
async function
|
|
1181
|
+
async function waitForAgentRun(id, opts = {}) {
|
|
1165
1182
|
const interval = opts.intervalMs ?? 2e3;
|
|
1166
1183
|
const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : null;
|
|
1167
1184
|
let lastStatus = "";
|
|
1168
1185
|
while (true) {
|
|
1169
|
-
const snap = await
|
|
1186
|
+
const snap = await getAgentRun(id);
|
|
1170
1187
|
if (snap.status !== lastStatus) {
|
|
1171
1188
|
lastStatus = snap.status;
|
|
1172
1189
|
opts.onTick?.(snap);
|
|
1173
1190
|
}
|
|
1174
1191
|
if (snap.status === "done" || snap.status === "error") return snap;
|
|
1175
1192
|
if (deadline && Date.now() > deadline) {
|
|
1176
|
-
throw new Error(`Timeout waiting for
|
|
1193
|
+
throw new Error(`Timeout waiting for agent-run ${id} (last status: ${snap.status})`);
|
|
1177
1194
|
}
|
|
1178
1195
|
await new Promise((r) => setTimeout(r, interval));
|
|
1179
1196
|
}
|
|
@@ -1251,11 +1268,11 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1251
1268
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
1252
1269
|
body
|
|
1253
1270
|
);
|
|
1254
|
-
const
|
|
1255
|
-
if (!opts.json) console.log(chalk3.green(`triage runtime ${
|
|
1256
|
-
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 } });
|
|
1257
1274
|
if (opts.wait) {
|
|
1258
|
-
const final = await
|
|
1275
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
1259
1276
|
onTick: (s) => {
|
|
1260
1277
|
if (!opts.json) console.log(chalk3.dim(`[triage] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
1261
1278
|
}
|
|
@@ -1288,7 +1305,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1288
1305
|
console.log(chalk3.dim(`blockingQuestionCount: ${blockingQuestionCount}`));
|
|
1289
1306
|
} else {
|
|
1290
1307
|
console.log(JSON.stringify({
|
|
1291
|
-
|
|
1308
|
+
agentRunId,
|
|
1292
1309
|
issueOverview: "ISSUE.md",
|
|
1293
1310
|
issueFiles,
|
|
1294
1311
|
blockingQuestionCount
|
|
@@ -1296,7 +1313,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1296
1313
|
}
|
|
1297
1314
|
process.exit(blockingQuestionCount > 0 ? 2 : 0);
|
|
1298
1315
|
}
|
|
1299
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1316
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId }, null, 2));
|
|
1300
1317
|
} catch (err) {
|
|
1301
1318
|
console.error(chalk3.red(err.message));
|
|
1302
1319
|
process.exit(1);
|
|
@@ -1361,24 +1378,24 @@ var exec = new Command4("exec").description("Execute a plan against the task (cw
|
|
|
1361
1378
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
1362
1379
|
body
|
|
1363
1380
|
);
|
|
1364
|
-
const
|
|
1365
|
-
if (!opts.json) console.log(chalk4.green(`exec runtime ${
|
|
1366
|
-
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 } });
|
|
1367
1384
|
if (opts.wait) {
|
|
1368
|
-
const final = await
|
|
1385
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
1369
1386
|
onTick: (s) => {
|
|
1370
1387
|
if (!opts.json) console.log(chalk4.dim(`[exec] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
1371
1388
|
}
|
|
1372
1389
|
});
|
|
1373
1390
|
if (final.status === "done") {
|
|
1374
1391
|
if (!opts.json) console.log(chalk4.green("exec completed"));
|
|
1375
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1392
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, planFile, result: final.result }, null, 2));
|
|
1376
1393
|
return;
|
|
1377
1394
|
}
|
|
1378
1395
|
console.error(chalk4.red(`exec failed: ${final.error || "unknown"}`));
|
|
1379
1396
|
process.exit(1);
|
|
1380
1397
|
}
|
|
1381
|
-
if (opts.json) console.log(JSON.stringify({
|
|
1398
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, planFile }, null, 2));
|
|
1382
1399
|
} catch (err) {
|
|
1383
1400
|
console.error(chalk4.red(err.message));
|
|
1384
1401
|
process.exit(1);
|
|
@@ -1435,13 +1452,16 @@ function preview(body, width = 60) {
|
|
|
1435
1452
|
const single = body.replace(/\s+/g, " ").trim();
|
|
1436
1453
|
return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
|
|
1437
1454
|
}
|
|
1438
|
-
function printRow(c) {
|
|
1455
|
+
function printRow(c, indent = "") {
|
|
1439
1456
|
const id = chalk6.cyan(c.id.padEnd(14));
|
|
1440
1457
|
const author = chalk6.magenta(c.author.padEnd(12));
|
|
1441
|
-
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)}`);
|
|
1442
1459
|
}
|
|
1443
1460
|
function printDetail(c) {
|
|
1444
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
|
+
}
|
|
1445
1465
|
console.log(`${chalk6.bold("author:")} ${c.author}`);
|
|
1446
1466
|
console.log(`${chalk6.bold("created:")} ${c.created_at}`);
|
|
1447
1467
|
console.log(`${chalk6.bold("updated:")} ${c.updated_at}`);
|
|
@@ -1469,17 +1489,22 @@ comment.command("list <taskId>").description("List comments on a task (taskId is
|
|
|
1469
1489
|
console.log(chalk6.dim("No comments."));
|
|
1470
1490
|
return;
|
|
1471
1491
|
}
|
|
1472
|
-
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
|
+
}
|
|
1473
1498
|
} catch (err) {
|
|
1474
1499
|
fail(err);
|
|
1475
1500
|
}
|
|
1476
1501
|
});
|
|
1477
|
-
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) => {
|
|
1478
1503
|
try {
|
|
1479
1504
|
const body = readBodyFromOpts(opts);
|
|
1480
1505
|
const { comment: created } = await api.post(
|
|
1481
1506
|
`/api/tasks/${encodeURIComponent(taskId)}/comments`,
|
|
1482
|
-
{ body, author: opts.author }
|
|
1507
|
+
{ body, author: opts.author, parent_comment_id: opts.replyTo }
|
|
1483
1508
|
);
|
|
1484
1509
|
if (opts.json) {
|
|
1485
1510
|
console.log(JSON.stringify({ comment: created }, null, 2));
|
|
@@ -1814,61 +1839,9 @@ var ui = new Command9("ui").description("Launch the dashboard").option("-p, --po
|
|
|
1814
1839
|
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1815
1840
|
});
|
|
1816
1841
|
|
|
1817
|
-
// src/commands/
|
|
1842
|
+
// src/commands/models.ts
|
|
1818
1843
|
import { Command as Command10 } from "commander";
|
|
1819
|
-
import { spawn as spawn2 } from "child_process";
|
|
1820
|
-
import fs15 from "fs";
|
|
1821
|
-
import path14 from "path";
|
|
1822
1844
|
import chalk10 from "chalk";
|
|
1823
|
-
var SERVER_DIR = path14.resolve(
|
|
1824
|
-
import.meta.dirname,
|
|
1825
|
-
"..",
|
|
1826
|
-
"..",
|
|
1827
|
-
"..",
|
|
1828
|
-
"server"
|
|
1829
|
-
);
|
|
1830
|
-
var HTTP_ENTRY = path14.join(SERVER_DIR, "src", "main.ts");
|
|
1831
|
-
var CLI_TSX = path14.resolve(
|
|
1832
|
-
import.meta.dirname,
|
|
1833
|
-
"..",
|
|
1834
|
-
"..",
|
|
1835
|
-
"node_modules",
|
|
1836
|
-
".bin",
|
|
1837
|
-
"tsx"
|
|
1838
|
-
);
|
|
1839
|
-
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) => {
|
|
1840
|
-
if (!fs15.existsSync(HTTP_ENTRY)) {
|
|
1841
|
-
console.error(chalk10.red(`Server entry not found: ${HTTP_ENTRY}`));
|
|
1842
|
-
process.exit(1);
|
|
1843
|
-
}
|
|
1844
|
-
if (!fs15.existsSync(CLI_TSX)) {
|
|
1845
|
-
console.error(chalk10.red(`tsx not found at ${CLI_TSX}. Run \`pnpm install\` in the CLI package.`));
|
|
1846
|
-
process.exit(1);
|
|
1847
|
-
}
|
|
1848
|
-
console.log(chalk10.green(`Starting task0 API on http://${opts.host}:${opts.port}`));
|
|
1849
|
-
const child = spawn2(CLI_TSX, [HTTP_ENTRY], {
|
|
1850
|
-
cwd: SERVER_DIR,
|
|
1851
|
-
stdio: "inherit",
|
|
1852
|
-
env: {
|
|
1853
|
-
...process.env,
|
|
1854
|
-
TASK0_API_PORT: opts.port,
|
|
1855
|
-
TASK0_API_HOST: opts.host
|
|
1856
|
-
}
|
|
1857
|
-
});
|
|
1858
|
-
child.on("error", (err) => {
|
|
1859
|
-
console.error(chalk10.red(`Failed to start server: ${err.message}`));
|
|
1860
|
-
process.exit(1);
|
|
1861
|
-
});
|
|
1862
|
-
child.on("exit", (code) => {
|
|
1863
|
-
process.exit(code ?? 0);
|
|
1864
|
-
});
|
|
1865
|
-
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
1866
|
-
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
1867
|
-
});
|
|
1868
|
-
|
|
1869
|
-
// src/commands/models.ts
|
|
1870
|
-
import { Command as Command11 } from "commander";
|
|
1871
|
-
import chalk11 from "chalk";
|
|
1872
1845
|
|
|
1873
1846
|
// ../../packages/shared/dist/types/agent.js
|
|
1874
1847
|
var AGENT_KINDS = ["coding", "llm_api", "workflow"];
|
|
@@ -1880,8 +1853,8 @@ function isCodingRuntimeAgent(value) {
|
|
|
1880
1853
|
}
|
|
1881
1854
|
|
|
1882
1855
|
// ../../packages/shared/dist/types/runtime.js
|
|
1883
|
-
var
|
|
1884
|
-
var
|
|
1856
|
+
var AGENT_RUN_STATUS_VALUES = ["starting", "running", "done", "error"];
|
|
1857
|
+
var AGENT_RUN_STATUS_SET = new Set(AGENT_RUN_STATUS_VALUES);
|
|
1885
1858
|
var RUNTIME_AGENTS = CODING_RUNTIME_AGENTS;
|
|
1886
1859
|
var isRuntimeAgent = isCodingRuntimeAgent;
|
|
1887
1860
|
var AGENT_MODEL_DEFAULTS = {
|
|
@@ -2066,31 +2039,31 @@ function refreshAgentModels(agent2) {
|
|
|
2066
2039
|
function parseAgent(raw) {
|
|
2067
2040
|
const trimmed = raw.trim();
|
|
2068
2041
|
if (!isRuntimeAgent(trimmed)) {
|
|
2069
|
-
console.error(
|
|
2042
|
+
console.error(chalk10.red(`Invalid agent "${raw}" (expected one of ${RUNTIME_AGENTS.join(", ")})`));
|
|
2070
2043
|
process.exit(1);
|
|
2071
2044
|
}
|
|
2072
2045
|
return trimmed;
|
|
2073
2046
|
}
|
|
2074
|
-
var models = new
|
|
2047
|
+
var models = new Command10("models").description("Manage cached agent model lists");
|
|
2075
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) => {
|
|
2076
2049
|
const agent2 = opts.agent ? parseAgent(opts.agent) : void 0;
|
|
2077
2050
|
try {
|
|
2078
2051
|
const summary = refreshAgentModels(agent2);
|
|
2079
2052
|
for (const result of summary.results) {
|
|
2080
|
-
const source2 = result.source === "command" ?
|
|
2081
|
-
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`);
|
|
2082
2055
|
if (result.fetchCommand) {
|
|
2083
|
-
console.log(
|
|
2056
|
+
console.log(chalk10.dim(` ${result.fetchCommand}`));
|
|
2084
2057
|
}
|
|
2085
2058
|
}
|
|
2086
2059
|
for (const failure of summary.failures) {
|
|
2087
|
-
console.error(
|
|
2060
|
+
console.error(chalk10.red(`Failed ${failure.agent}: ${failure.error}`));
|
|
2088
2061
|
}
|
|
2089
2062
|
if (summary.failures.length > 0) {
|
|
2090
2063
|
process.exit(1);
|
|
2091
2064
|
}
|
|
2092
2065
|
} catch (error2) {
|
|
2093
|
-
console.error(
|
|
2066
|
+
console.error(chalk10.red(error2 instanceof Error ? error2.message : String(error2)));
|
|
2094
2067
|
process.exit(1);
|
|
2095
2068
|
}
|
|
2096
2069
|
});
|
|
@@ -2114,70 +2087,73 @@ models.command("default <agent>").description("Get or set default model / effort
|
|
|
2114
2087
|
if (wroteModel || wroteEffort) {
|
|
2115
2088
|
saveConfig(config);
|
|
2116
2089
|
}
|
|
2117
|
-
console.log(
|
|
2118
|
-
console.log(` model: ${entry.defaultModel ??
|
|
2119
|
-
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)")}`);
|
|
2120
2093
|
});
|
|
2121
2094
|
|
|
2122
|
-
// src/commands/
|
|
2123
|
-
import
|
|
2124
|
-
import
|
|
2125
|
-
import {
|
|
2126
|
-
|
|
2127
|
-
|
|
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) => {
|
|
2128
2104
|
try {
|
|
2129
|
-
const { runtimes } = await api.get("/api/
|
|
2105
|
+
const { agent_runs: runtimes } = await api.get("/api/agent-runs");
|
|
2130
2106
|
const filtered = opts.task ? runtimes.filter((r) => r.taskId === opts.task) : runtimes;
|
|
2131
2107
|
if (opts.json) {
|
|
2132
2108
|
console.log(JSON.stringify(filtered, null, 2));
|
|
2133
2109
|
return;
|
|
2134
2110
|
}
|
|
2135
2111
|
if (filtered.length === 0) {
|
|
2136
|
-
console.log(
|
|
2112
|
+
console.log(chalk11.dim("No agent runs."));
|
|
2137
2113
|
return;
|
|
2138
2114
|
}
|
|
2139
2115
|
const objectIdWidth = Math.max(...filtered.map((r) => (r.objectId || "").length), 8);
|
|
2140
2116
|
for (const r of filtered) {
|
|
2141
|
-
const statusColor2 = r.status === "done" ?
|
|
2142
|
-
const objectId =
|
|
2143
|
-
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)}`);
|
|
2144
2120
|
}
|
|
2145
2121
|
} catch (err) {
|
|
2146
|
-
console.error(
|
|
2122
|
+
console.error(chalk11.red(err.message));
|
|
2147
2123
|
process.exit(1);
|
|
2148
2124
|
}
|
|
2149
2125
|
});
|
|
2150
|
-
|
|
2126
|
+
agentRun.command("show <id>").description("Show agent run details").option("--json", "Output JSON").action(async (id, opts) => {
|
|
2151
2127
|
try {
|
|
2152
|
-
const r = await
|
|
2128
|
+
const r = await getAgentRun(id);
|
|
2153
2129
|
if (opts.json) {
|
|
2154
2130
|
console.log(JSON.stringify(r, null, 2));
|
|
2155
2131
|
return;
|
|
2156
2132
|
}
|
|
2157
|
-
console.log(`${
|
|
2158
|
-
if (r.objectId) console.log(`${
|
|
2159
|
-
console.log(`${
|
|
2160
|
-
console.log(`${
|
|
2161
|
-
console.log(`${
|
|
2162
|
-
console.log(`${
|
|
2163
|
-
if (r.phase) console.log(`${
|
|
2164
|
-
if (r.error) console.log(`${
|
|
2165
|
-
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"})`);
|
|
2166
2142
|
if (r.result) {
|
|
2167
|
-
console.log(
|
|
2143
|
+
console.log(chalk11.bold("result:"));
|
|
2168
2144
|
console.log(" " + JSON.stringify(r.result, null, 2).split("\n").join("\n "));
|
|
2169
2145
|
}
|
|
2170
2146
|
} catch (err) {
|
|
2171
|
-
console.error(
|
|
2147
|
+
console.error(chalk11.red(err.message));
|
|
2172
2148
|
process.exit(1);
|
|
2173
2149
|
}
|
|
2174
2150
|
});
|
|
2175
|
-
|
|
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) => {
|
|
2176
2152
|
try {
|
|
2177
|
-
const final = await
|
|
2153
|
+
const final = await waitForAgentRun(id, {
|
|
2178
2154
|
timeoutMs: Number(opts.timeout) * 1e3,
|
|
2179
2155
|
onTick: (s) => {
|
|
2180
|
-
if (!opts.json) console.log(
|
|
2156
|
+
if (!opts.json) console.log(chalk11.dim(`[${s.status}] ${s.phase ?? ""}`));
|
|
2181
2157
|
}
|
|
2182
2158
|
});
|
|
2183
2159
|
if (opts.json) {
|
|
@@ -2185,41 +2161,120 @@ runtime.command("wait <id>").description("Block until runtime reaches done or er
|
|
|
2185
2161
|
}
|
|
2186
2162
|
process.exit(final.status === "done" ? 0 : 1);
|
|
2187
2163
|
} catch (err) {
|
|
2188
|
-
console.error(
|
|
2164
|
+
console.error(chalk11.red(err.message));
|
|
2189
2165
|
process.exit(1);
|
|
2190
2166
|
}
|
|
2191
2167
|
});
|
|
2192
|
-
|
|
2168
|
+
agentRun.command("cancel <id>").description("Cancel an agent run").action(async (id) => {
|
|
2193
2169
|
try {
|
|
2194
|
-
await api.post(`/api/
|
|
2195
|
-
console.log(
|
|
2170
|
+
await api.post(`/api/agent-runs/${encodeURIComponent(id)}/cancel`);
|
|
2171
|
+
console.log(chalk11.green(`Canceled ${id}`));
|
|
2196
2172
|
} catch (err) {
|
|
2197
|
-
console.error(
|
|
2173
|
+
console.error(chalk11.red(err.message));
|
|
2198
2174
|
process.exit(1);
|
|
2199
2175
|
}
|
|
2200
2176
|
});
|
|
2201
|
-
|
|
2177
|
+
agentRun.command("attach <id>").description("Attach to the tmux session backing an agent run").action(async (id) => {
|
|
2202
2178
|
try {
|
|
2203
|
-
const r = await
|
|
2179
|
+
const r = await getAgentRun(id);
|
|
2204
2180
|
if (!r.tmuxSession) {
|
|
2205
|
-
console.error(
|
|
2181
|
+
console.error(chalk11.red("No tmux session recorded for this agent run."));
|
|
2206
2182
|
process.exit(1);
|
|
2207
2183
|
}
|
|
2208
|
-
const child =
|
|
2184
|
+
const child = spawn2("tmux", ["attach", "-t", r.tmuxSession], { stdio: "inherit" });
|
|
2209
2185
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
2210
2186
|
child.on("error", (err) => {
|
|
2211
|
-
console.error(
|
|
2187
|
+
console.error(chalk11.red(`Failed to attach: ${err.message}`));
|
|
2212
2188
|
process.exit(1);
|
|
2213
2189
|
});
|
|
2214
2190
|
} catch (err) {
|
|
2215
|
-
console.error(
|
|
2191
|
+
console.error(chalk11.red(err.message));
|
|
2216
2192
|
process.exit(1);
|
|
2217
2193
|
}
|
|
2218
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);
|
|
2219
2274
|
|
|
2220
2275
|
// src/commands/plan.ts
|
|
2221
|
-
import { Command as
|
|
2222
|
-
import
|
|
2276
|
+
import { Command as Command12 } from "commander";
|
|
2277
|
+
import chalk12 from "chalk";
|
|
2223
2278
|
import fs16 from "fs";
|
|
2224
2279
|
import path15 from "path";
|
|
2225
2280
|
init_task_state2();
|
|
@@ -2228,27 +2283,27 @@ function resolveSkillFilePath3(projectRoot, skillName) {
|
|
|
2228
2283
|
const p = path15.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
2229
2284
|
return fs16.existsSync(p) ? p : null;
|
|
2230
2285
|
}
|
|
2231
|
-
var plan = new
|
|
2286
|
+
var plan = new Command12("plan").description("Generate and refine plans");
|
|
2232
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) => {
|
|
2233
2288
|
try {
|
|
2234
2289
|
const loc = resolveTaskByObjectId(objectId);
|
|
2235
2290
|
const ideaFile = opts.idea || latestArtifact(loc.taskDir, /^IDEA-\d+\.md$/);
|
|
2236
2291
|
if (!ideaFile) {
|
|
2237
|
-
console.error(
|
|
2292
|
+
console.error(chalk12.red(`No IDEA-NN.md found in ${loc.taskDir}. Pass --idea.`));
|
|
2238
2293
|
process.exit(1);
|
|
2239
2294
|
}
|
|
2240
2295
|
const agents = opts.agents.split(",").map((a) => a.trim()).filter(Boolean);
|
|
2241
2296
|
if (agents.length === 0) {
|
|
2242
|
-
console.error(
|
|
2297
|
+
console.error(chalk12.red("No agents specified."));
|
|
2243
2298
|
process.exit(1);
|
|
2244
2299
|
}
|
|
2245
2300
|
if ((opts.model || opts.effort) && agents.length > 1) {
|
|
2246
|
-
console.error(
|
|
2301
|
+
console.error(chalk12.red(
|
|
2247
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.`
|
|
2248
2303
|
));
|
|
2249
2304
|
process.exit(1);
|
|
2250
2305
|
}
|
|
2251
|
-
const warn = opts.json ? void 0 : (m) => console.error(
|
|
2306
|
+
const warn = opts.json ? void 0 : (m) => console.error(chalk12.dim(m));
|
|
2252
2307
|
const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_GENERATE_SKILL_NAME);
|
|
2253
2308
|
if (opts.model || opts.effort) {
|
|
2254
2309
|
resolveModelOptions(agents[0], { model: opts.model, effort: opts.effort }, warn);
|
|
@@ -2271,43 +2326,56 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2271
2326
|
prompt: promptForAgent(agent2),
|
|
2272
2327
|
...skillFilePath ? { skill_file_path: skillFilePath } : {}
|
|
2273
2328
|
};
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
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
|
+
}
|
|
2287
2354
|
}
|
|
2355
|
+
return { agent: agent2, agentRunId: "", sessionName: "", error: "agent_busy: exhausted retries" };
|
|
2288
2356
|
}));
|
|
2289
2357
|
const launched = kicks.filter((k) => !k.error);
|
|
2290
2358
|
const failed = kicks.filter((k) => k.error);
|
|
2291
2359
|
if (!opts.json) {
|
|
2292
2360
|
for (const k of launched) {
|
|
2293
|
-
console.log(
|
|
2361
|
+
console.log(chalk12.green(`[${k.agent}]`) + ` runtime ${k.agentRunId}`);
|
|
2294
2362
|
}
|
|
2295
2363
|
for (const k of failed) {
|
|
2296
|
-
console.error(
|
|
2364
|
+
console.error(chalk12.red(`[${k.agent}] ${k.error}`));
|
|
2297
2365
|
}
|
|
2298
2366
|
}
|
|
2299
|
-
const
|
|
2300
|
-
for (const k of launched)
|
|
2367
|
+
const agentRunIds = {};
|
|
2368
|
+
for (const k of launched) agentRunIds[k.agent] = k.agentRunId;
|
|
2301
2369
|
await updateWorkflow(loc.taskYml, {
|
|
2302
|
-
|
|
2370
|
+
agent_runs: { plan: agentRunIds }
|
|
2303
2371
|
});
|
|
2304
2372
|
const planFiles = [];
|
|
2305
2373
|
if (opts.wait) {
|
|
2306
2374
|
const results = await Promise.all(launched.map(async (k) => {
|
|
2307
2375
|
try {
|
|
2308
|
-
const final = await
|
|
2376
|
+
const final = await waitForAgentRun(k.agentRunId, {
|
|
2309
2377
|
onTick: (s) => {
|
|
2310
|
-
if (!opts.json) console.log(
|
|
2378
|
+
if (!opts.json) console.log(chalk12.dim(`[${k.agent}] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2311
2379
|
}
|
|
2312
2380
|
});
|
|
2313
2381
|
return { agent: k.agent, final, error: null };
|
|
@@ -2320,10 +2388,10 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2320
2388
|
const planFile = r.final.result?.planFile;
|
|
2321
2389
|
if (planFile) {
|
|
2322
2390
|
planFiles.push(planFile);
|
|
2323
|
-
if (!opts.json) console.log(
|
|
2391
|
+
if (!opts.json) console.log(chalk12.green(`[${r.agent}] ${planFile}`));
|
|
2324
2392
|
}
|
|
2325
2393
|
} else {
|
|
2326
|
-
if (!opts.json) console.error(
|
|
2394
|
+
if (!opts.json) console.error(chalk12.red(`[${r.agent}] failed: ${r.error || r.final?.error || "unknown"}`));
|
|
2327
2395
|
}
|
|
2328
2396
|
}
|
|
2329
2397
|
const existingPlanFiles = readWorkflow(loc.taskYml).plan_files || [];
|
|
@@ -2337,7 +2405,7 @@ plan.command("generate <objectId>").description("Generate plan(s) from an IDEA f
|
|
|
2337
2405
|
}
|
|
2338
2406
|
if (failed.length > 0) process.exit(1);
|
|
2339
2407
|
} catch (err) {
|
|
2340
|
-
console.error(
|
|
2408
|
+
console.error(chalk12.red(err.message));
|
|
2341
2409
|
process.exit(1);
|
|
2342
2410
|
}
|
|
2343
2411
|
});
|
|
@@ -2348,7 +2416,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2348
2416
|
const files = fs16.readdirSync(loc.taskDir);
|
|
2349
2417
|
const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
|
|
2350
2418
|
if (planFiles.length === 0) {
|
|
2351
|
-
console.error(
|
|
2419
|
+
console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
|
|
2352
2420
|
process.exit(1);
|
|
2353
2421
|
}
|
|
2354
2422
|
const idx = planFiles[0].match(/^PLAN-(\d+)/)?.[1] || nextArtifactIndex(loc.taskDir, "PLAN");
|
|
@@ -2357,7 +2425,7 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2357
2425
|
resolveModelOptions(
|
|
2358
2426
|
opts.agent,
|
|
2359
2427
|
{ model: opts.model, effort: opts.effort },
|
|
2360
|
-
opts.json ? void 0 : (m) => console.error(
|
|
2428
|
+
opts.json ? void 0 : (m) => console.error(chalk12.dim(m))
|
|
2361
2429
|
);
|
|
2362
2430
|
}
|
|
2363
2431
|
const skillFilePath = resolveSkillFilePath3(loc.projectRoot, PLAN_REFINE_SKILL_NAME);
|
|
@@ -2376,15 +2444,15 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2376
2444
|
`/api/agents/${encodeURIComponent(opts.agent)}/run`,
|
|
2377
2445
|
body
|
|
2378
2446
|
);
|
|
2379
|
-
const
|
|
2380
|
-
if (!opts.json) console.log(
|
|
2447
|
+
const agentRunId = resp.runtime.id;
|
|
2448
|
+
if (!opts.json) console.log(chalk12.green(`refine runtime ${agentRunId}`));
|
|
2381
2449
|
await updateWorkflow(loc.taskYml, {
|
|
2382
|
-
|
|
2450
|
+
agent_runs: { refine: agentRunId }
|
|
2383
2451
|
});
|
|
2384
2452
|
if (opts.wait) {
|
|
2385
|
-
const final = await
|
|
2453
|
+
const final = await waitForAgentRun(agentRunId, {
|
|
2386
2454
|
onTick: (s) => {
|
|
2387
|
-
if (!opts.json) console.log(
|
|
2455
|
+
if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2388
2456
|
}
|
|
2389
2457
|
});
|
|
2390
2458
|
const refinedPath = path15.join(loc.taskDir, refinedFile);
|
|
@@ -2394,29 +2462,29 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2394
2462
|
phase: "refined",
|
|
2395
2463
|
refined_plan_file: refinedFile
|
|
2396
2464
|
});
|
|
2397
|
-
if (!opts.json) console.log(
|
|
2398
|
-
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));
|
|
2399
2467
|
return;
|
|
2400
2468
|
}
|
|
2401
2469
|
if (final.status !== "done") {
|
|
2402
|
-
console.error(
|
|
2470
|
+
console.error(chalk12.red(`refine failed: ${final.error || "unknown"}`));
|
|
2403
2471
|
} else if (!wrote) {
|
|
2404
|
-
console.error(
|
|
2472
|
+
console.error(chalk12.red(`refine completed but ${refinedFile} was not written`));
|
|
2405
2473
|
}
|
|
2406
2474
|
process.exit(1);
|
|
2407
2475
|
}
|
|
2408
|
-
if (opts.json) console.log(JSON.stringify({
|
|
2476
|
+
if (opts.json) console.log(JSON.stringify({ agentRunId, refinedFile }, null, 2));
|
|
2409
2477
|
} catch (err) {
|
|
2410
|
-
console.error(
|
|
2478
|
+
console.error(chalk12.red(err.message));
|
|
2411
2479
|
process.exit(1);
|
|
2412
2480
|
}
|
|
2413
2481
|
});
|
|
2414
2482
|
|
|
2415
2483
|
// src/commands/run.ts
|
|
2416
2484
|
init_task_state2();
|
|
2417
|
-
import { Command as
|
|
2418
|
-
import
|
|
2419
|
-
import { spawnSync as
|
|
2485
|
+
import { Command as Command13 } from "commander";
|
|
2486
|
+
import chalk13 from "chalk";
|
|
2487
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2420
2488
|
var PHASES = ["triage", "plan", "refine", "exec"];
|
|
2421
2489
|
var PAUSE_AFTER = /* @__PURE__ */ new Set(["refine"]);
|
|
2422
2490
|
var CHECKPOINT_EXIT = 78;
|
|
@@ -2435,13 +2503,13 @@ function nextPhaseFromWorkflow(wf) {
|
|
|
2435
2503
|
return null;
|
|
2436
2504
|
}
|
|
2437
2505
|
}
|
|
2438
|
-
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) => {
|
|
2439
2507
|
try {
|
|
2440
2508
|
const loc = resolveTaskByObjectId(objectId);
|
|
2441
2509
|
const wf = readWorkflow(loc.taskYml);
|
|
2442
2510
|
const fromCandidate = opts.from ?? nextPhaseFromWorkflow(wf);
|
|
2443
2511
|
if (!fromCandidate) {
|
|
2444
|
-
if (!opts.json) console.log(
|
|
2512
|
+
if (!opts.json) console.log(chalk13.dim(`Nothing to do \u2014 workflow phase is "${wf.phase}".`));
|
|
2445
2513
|
return;
|
|
2446
2514
|
}
|
|
2447
2515
|
const from = fromCandidate;
|
|
@@ -2452,23 +2520,23 @@ var run = new Command14("run").description("Run the full task orchestration (tri
|
|
|
2452
2520
|
const toIdx = PHASES.indexOf(to);
|
|
2453
2521
|
if (fromIdx > toIdx) throw new Error(`--from (${from}) is past --to (${to})`);
|
|
2454
2522
|
if (!opts.json) {
|
|
2455
|
-
console.log(
|
|
2523
|
+
console.log(chalk13.bold(`task0 run ${objectId}`) + chalk13.dim(` \u2014 ${from} \u2192 ${to}`));
|
|
2456
2524
|
}
|
|
2457
2525
|
for (let i = fromIdx; i <= toIdx; i++) {
|
|
2458
2526
|
const phase = PHASES[i];
|
|
2459
|
-
if (!opts.json) console.log(
|
|
2527
|
+
if (!opts.json) console.log(chalk13.bold.cyan(`
|
|
2460
2528
|
\u25B6 ${phase}`));
|
|
2461
2529
|
const code = runPhase(phase, objectId, opts);
|
|
2462
2530
|
if (code !== 0) {
|
|
2463
|
-
console.error(
|
|
2531
|
+
console.error(chalk13.red(`Phase "${phase}" failed (exit ${code}). Resume with: task0 run ${objectId} --from ${phase}`));
|
|
2464
2532
|
process.exit(code);
|
|
2465
2533
|
}
|
|
2466
2534
|
if (!opts.auto && PAUSE_AFTER.has(phase) && i < toIdx) {
|
|
2467
2535
|
const next = PHASES[i + 1];
|
|
2468
2536
|
if (!opts.json) {
|
|
2469
|
-
console.log(
|
|
2537
|
+
console.log(chalk13.yellow(`
|
|
2470
2538
|
\u23F8 checkpoint after ${phase}. Review and resume with:`));
|
|
2471
|
-
console.log(
|
|
2539
|
+
console.log(chalk13.yellow(` task0 run ${objectId} --from ${next}`));
|
|
2472
2540
|
}
|
|
2473
2541
|
process.exit(CHECKPOINT_EXIT);
|
|
2474
2542
|
}
|
|
@@ -2477,11 +2545,11 @@ var run = new Command14("run").description("Run the full task orchestration (tri
|
|
|
2477
2545
|
if (opts.json) {
|
|
2478
2546
|
console.log(JSON.stringify({ objectId, from, to, workflow: finalWf }, null, 2));
|
|
2479
2547
|
} else {
|
|
2480
|
-
console.log(
|
|
2548
|
+
console.log(chalk13.green(`
|
|
2481
2549
|
\u2713 done (phase: ${finalWf.phase})`));
|
|
2482
2550
|
}
|
|
2483
2551
|
} catch (err) {
|
|
2484
|
-
console.error(
|
|
2552
|
+
console.error(chalk13.red(err.message));
|
|
2485
2553
|
process.exit(1);
|
|
2486
2554
|
}
|
|
2487
2555
|
});
|
|
@@ -2503,7 +2571,7 @@ function runPhase(phase, objectId, opts) {
|
|
|
2503
2571
|
args = ["task", "exec", objectId, "--wait", "--agent", opts.executor];
|
|
2504
2572
|
break;
|
|
2505
2573
|
}
|
|
2506
|
-
const r =
|
|
2574
|
+
const r = spawnSync4(node, [cli, ...args], { stdio: "inherit" });
|
|
2507
2575
|
return r.status ?? 1;
|
|
2508
2576
|
}
|
|
2509
2577
|
run.command("status <objectId>").description("Show current workflow state").option("--json", "Output JSON").action((objectId, opts) => {
|
|
@@ -2514,29 +2582,30 @@ run.command("status <objectId>").description("Show current workflow state").opti
|
|
|
2514
2582
|
console.log(JSON.stringify(wf, null, 2));
|
|
2515
2583
|
return;
|
|
2516
2584
|
}
|
|
2517
|
-
console.log(`${
|
|
2585
|
+
console.log(`${chalk13.bold("phase:")} ${wf.phase ?? chalk13.dim("(not started)")}`);
|
|
2518
2586
|
if (wf.issue_overview) {
|
|
2519
2587
|
const extra = wf.issue_files?.length ? ` (+${wf.issue_files.length} details)` : "";
|
|
2520
|
-
console.log(`${
|
|
2521
|
-
}
|
|
2522
|
-
if (wf.plan_files?.length) console.log(`${
|
|
2523
|
-
if (wf.refined_plan_file) console.log(`${
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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)) {
|
|
2527
2596
|
console.log(` ${k.padEnd(8)} ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
2528
2597
|
}
|
|
2529
2598
|
}
|
|
2530
|
-
if (wf.updated_at) console.log(
|
|
2599
|
+
if (wf.updated_at) console.log(chalk13.dim(`updated_at: ${wf.updated_at}`));
|
|
2531
2600
|
} catch (err) {
|
|
2532
|
-
console.error(
|
|
2601
|
+
console.error(chalk13.red(err.message));
|
|
2533
2602
|
process.exit(1);
|
|
2534
2603
|
}
|
|
2535
2604
|
});
|
|
2536
2605
|
|
|
2537
2606
|
// src/commands/okr.ts
|
|
2538
|
-
import { Command as
|
|
2539
|
-
import
|
|
2607
|
+
import { Command as Command14 } from "commander";
|
|
2608
|
+
import chalk14 from "chalk";
|
|
2540
2609
|
|
|
2541
2610
|
// src/lib/project.ts
|
|
2542
2611
|
import path16 from "path";
|
|
@@ -2569,7 +2638,7 @@ function resolveProjectName(opts) {
|
|
|
2569
2638
|
|
|
2570
2639
|
// src/commands/okr.ts
|
|
2571
2640
|
function fail3(message) {
|
|
2572
|
-
console.error(
|
|
2641
|
+
console.error(chalk14.red(message));
|
|
2573
2642
|
process.exit(1);
|
|
2574
2643
|
}
|
|
2575
2644
|
function parseNumber(value) {
|
|
@@ -2606,11 +2675,11 @@ function countKRs(plan3) {
|
|
|
2606
2675
|
return plan3.objectives.reduce((sum, o) => sum + o.key_results.length, 0);
|
|
2607
2676
|
}
|
|
2608
2677
|
function printPlanSummary(plan3) {
|
|
2609
|
-
const oid =
|
|
2678
|
+
const oid = chalk14.cyan((plan3.object_id || "-").padEnd(10));
|
|
2610
2679
|
const id = plan3.id.padEnd(16);
|
|
2611
|
-
const status =
|
|
2680
|
+
const status = chalk14.yellow(plan3.status.padEnd(8));
|
|
2612
2681
|
const range = `${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`;
|
|
2613
|
-
const counts =
|
|
2682
|
+
const counts = chalk14.dim(
|
|
2614
2683
|
`${plan3.objectives.length} obj \xB7 ${countKRs(plan3)} kr \xB7 ${plan3.milestones.length} ms`
|
|
2615
2684
|
);
|
|
2616
2685
|
console.log(`${oid} ${id} ${status} ${range} ${counts} ${plan3.title}`);
|
|
@@ -2629,37 +2698,37 @@ function krProgress(kr2) {
|
|
|
2629
2698
|
}
|
|
2630
2699
|
function printPlanDetail(plan3) {
|
|
2631
2700
|
console.log(
|
|
2632
|
-
`${
|
|
2701
|
+
`${chalk14.bold(plan3.id)} ${chalk14.dim(plan3.object_id || "")} ${chalk14.yellow(plan3.status)} ${dateShort(plan3.start_at)} \u2192 ${dateShort(plan3.end_at)}`
|
|
2633
2702
|
);
|
|
2634
2703
|
if (plan3.title) console.log(` ${plan3.title}`);
|
|
2635
2704
|
for (const obj of plan3.objectives) {
|
|
2636
2705
|
console.log(
|
|
2637
|
-
` ${
|
|
2706
|
+
` ${chalk14.bold(obj.id)} ${obj.title} ${chalk14.dim(`(${obj.status})`)} ${chalk14.dim(obj.object_id || "")}`
|
|
2638
2707
|
);
|
|
2639
2708
|
for (const kr2 of obj.key_results) {
|
|
2640
2709
|
const unit = kr2.unit ? kr2.unit : "";
|
|
2641
2710
|
const vals = kr2.target_value !== null ? `${kr2.start_value ?? 0}${unit}\u2192${kr2.target_value}${unit} now ${kr2.current_value ?? 0}${unit}` : "\u2014";
|
|
2642
2711
|
console.log(
|
|
2643
|
-
` ${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 || "")}`
|
|
2644
2713
|
);
|
|
2645
2714
|
}
|
|
2646
2715
|
}
|
|
2647
2716
|
for (const ms of plan3.milestones) {
|
|
2648
|
-
const links = ms.linked_kr_ids.length ?
|
|
2717
|
+
const links = ms.linked_kr_ids.length ? chalk14.dim(`\u2192 ${ms.linked_kr_ids.join(",")}`) : "";
|
|
2649
2718
|
console.log(
|
|
2650
|
-
` ${
|
|
2719
|
+
` ${chalk14.bold(ms.id)} ${dateShort(ms.due_at)} ${ms.title} ${chalk14.dim(`(${ms.status})`)} ${links} ${chalk14.dim(ms.object_id || "")}`
|
|
2651
2720
|
);
|
|
2652
2721
|
}
|
|
2653
2722
|
if (plan3.task_kr_links.length) {
|
|
2654
|
-
console.log(
|
|
2723
|
+
console.log(chalk14.dim("links:"));
|
|
2655
2724
|
for (const link of plan3.task_kr_links) {
|
|
2656
2725
|
console.log(
|
|
2657
|
-
` ${link.task_id} \u2192 ${link.kr_id} ${
|
|
2726
|
+
` ${link.task_id} \u2192 ${link.kr_id} ${chalk14.dim(`${link.contribution} w=${link.weight}`)}`
|
|
2658
2727
|
);
|
|
2659
2728
|
}
|
|
2660
2729
|
}
|
|
2661
2730
|
}
|
|
2662
|
-
var okr = new
|
|
2731
|
+
var okr = new Command14("okr").description(
|
|
2663
2732
|
"Manage OKR plans, objectives, key results, milestones"
|
|
2664
2733
|
);
|
|
2665
2734
|
var plan2 = okr.command("plan").description("OKR plans");
|
|
@@ -2693,7 +2762,7 @@ plan2.command("create <plan-id>").description("Create a new OKR plan").requiredO
|
|
|
2693
2762
|
};
|
|
2694
2763
|
const { plan: view } = await api.post(planBasePath(name), body);
|
|
2695
2764
|
if (maybeJson(view, opts)) return;
|
|
2696
|
-
console.log(
|
|
2765
|
+
console.log(chalk14.green("ok"), view.id, chalk14.dim(view.object_id || ""));
|
|
2697
2766
|
printPlanSummary(view);
|
|
2698
2767
|
});
|
|
2699
2768
|
}
|
|
@@ -2712,7 +2781,7 @@ plan2.command("update <plan-id>").description("Update plan fields").option("-t,
|
|
|
2712
2781
|
body
|
|
2713
2782
|
);
|
|
2714
2783
|
if (maybeJson(view, opts)) return;
|
|
2715
|
-
console.log(
|
|
2784
|
+
console.log(chalk14.green("updated"), view.id, chalk14.dim(view.object_id || ""));
|
|
2716
2785
|
printPlanSummary(view);
|
|
2717
2786
|
});
|
|
2718
2787
|
}
|
|
@@ -2735,10 +2804,10 @@ objective.command("create <plan-id> <objective-id>").description("Create an obje
|
|
|
2735
2804
|
);
|
|
2736
2805
|
if (maybeJson(view, opts)) return;
|
|
2737
2806
|
console.log(
|
|
2738
|
-
|
|
2807
|
+
chalk14.green("ok"),
|
|
2739
2808
|
view.id,
|
|
2740
|
-
|
|
2741
|
-
|
|
2809
|
+
chalk14.dim(view.object_id || ""),
|
|
2810
|
+
chalk14.dim(`(${view.status})`),
|
|
2742
2811
|
view.title
|
|
2743
2812
|
);
|
|
2744
2813
|
});
|
|
@@ -2759,7 +2828,7 @@ objective.command("update <plan-id> <objective-id>").description("Update objecti
|
|
|
2759
2828
|
body
|
|
2760
2829
|
);
|
|
2761
2830
|
if (maybeJson(view, opts)) return;
|
|
2762
|
-
console.log(
|
|
2831
|
+
console.log(chalk14.green("updated"), view.id, chalk14.dim(`(${view.status})`), view.title);
|
|
2763
2832
|
});
|
|
2764
2833
|
}
|
|
2765
2834
|
);
|
|
@@ -2787,10 +2856,10 @@ kr.command("create <plan-id> <objective-id> <kr-id>").description("Create a key
|
|
|
2787
2856
|
);
|
|
2788
2857
|
if (maybeJson(view, opts)) return;
|
|
2789
2858
|
console.log(
|
|
2790
|
-
|
|
2859
|
+
chalk14.green("ok"),
|
|
2791
2860
|
view.id,
|
|
2792
|
-
|
|
2793
|
-
|
|
2861
|
+
chalk14.dim(view.object_id || ""),
|
|
2862
|
+
chalk14.dim(`(${view.status}/${view.confidence})`),
|
|
2794
2863
|
view.title
|
|
2795
2864
|
);
|
|
2796
2865
|
});
|
|
@@ -2818,9 +2887,9 @@ kr.command("update <plan-id> <kr-id>").description("Update a key result").option
|
|
|
2818
2887
|
);
|
|
2819
2888
|
if (maybeJson(view, opts)) return;
|
|
2820
2889
|
console.log(
|
|
2821
|
-
|
|
2890
|
+
chalk14.green("updated"),
|
|
2822
2891
|
view.id,
|
|
2823
|
-
|
|
2892
|
+
chalk14.dim(`(${view.status}/${view.confidence})`),
|
|
2824
2893
|
view.title
|
|
2825
2894
|
);
|
|
2826
2895
|
});
|
|
@@ -2840,10 +2909,10 @@ kr.command("progress <plan-id> <kr-id>").description("Update key result progress
|
|
|
2840
2909
|
);
|
|
2841
2910
|
if (maybeJson(view, opts)) return;
|
|
2842
2911
|
console.log(
|
|
2843
|
-
|
|
2912
|
+
chalk14.green("progress"),
|
|
2844
2913
|
view.id,
|
|
2845
2914
|
krProgress(view),
|
|
2846
|
-
|
|
2915
|
+
chalk14.dim(`(${view.status}/${view.confidence})`)
|
|
2847
2916
|
);
|
|
2848
2917
|
});
|
|
2849
2918
|
}
|
|
@@ -2867,11 +2936,11 @@ milestone.command("create <plan-id> <milestone-id>").description("Create a miles
|
|
|
2867
2936
|
);
|
|
2868
2937
|
if (maybeJson(view, opts)) return;
|
|
2869
2938
|
console.log(
|
|
2870
|
-
|
|
2939
|
+
chalk14.green("ok"),
|
|
2871
2940
|
view.id,
|
|
2872
|
-
|
|
2941
|
+
chalk14.dim(view.object_id || ""),
|
|
2873
2942
|
dateShort(view.due_at),
|
|
2874
|
-
|
|
2943
|
+
chalk14.dim(`(${view.status})`),
|
|
2875
2944
|
view.title
|
|
2876
2945
|
);
|
|
2877
2946
|
});
|
|
@@ -2894,10 +2963,10 @@ milestone.command("update <plan-id> <milestone-id>").description("Update a miles
|
|
|
2894
2963
|
);
|
|
2895
2964
|
if (maybeJson(view, opts)) return;
|
|
2896
2965
|
console.log(
|
|
2897
|
-
|
|
2966
|
+
chalk14.green("updated"),
|
|
2898
2967
|
view.id,
|
|
2899
2968
|
dateShort(view.due_at),
|
|
2900
|
-
|
|
2969
|
+
chalk14.dim(`(${view.status})`),
|
|
2901
2970
|
view.title
|
|
2902
2971
|
);
|
|
2903
2972
|
});
|
|
@@ -2919,9 +2988,9 @@ okr.command("link <plan-id>").description("Link a task to a key result").require
|
|
|
2919
2988
|
);
|
|
2920
2989
|
if (maybeJson(link, opts)) return;
|
|
2921
2990
|
console.log(
|
|
2922
|
-
|
|
2991
|
+
chalk14.green("linked"),
|
|
2923
2992
|
`${link.task_id} \u2192 ${link.kr_id}`,
|
|
2924
|
-
|
|
2993
|
+
chalk14.dim(`${link.contribution} w=${link.weight}`)
|
|
2925
2994
|
);
|
|
2926
2995
|
});
|
|
2927
2996
|
}
|
|
@@ -2933,15 +3002,15 @@ okr.command("unlink <plan-id>").description("Unlink a task from a key result").r
|
|
|
2933
3002
|
`${planEncodedPath(name, planId)}/task-kr-links/${encodeURIComponent(opts.task)}/${encodeURIComponent(opts.kr)}`
|
|
2934
3003
|
);
|
|
2935
3004
|
if (maybeJson(result, opts)) return;
|
|
2936
|
-
console.log(
|
|
3005
|
+
console.log(chalk14.green("unlinked"), `${opts.task} \u2715 ${opts.kr}`);
|
|
2937
3006
|
});
|
|
2938
3007
|
}
|
|
2939
3008
|
);
|
|
2940
3009
|
|
|
2941
3010
|
// src/commands/note.ts
|
|
2942
|
-
import { Command as
|
|
2943
|
-
import
|
|
2944
|
-
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");
|
|
2945
3014
|
async function fetchAllInboxNotes() {
|
|
2946
3015
|
const { inboxes } = await api.get("/api/inboxes");
|
|
2947
3016
|
const results = [];
|
|
@@ -2983,33 +3052,33 @@ function preview2(body, width = 60) {
|
|
|
2983
3052
|
return single.length > width ? single.slice(0, width - 1) + "\u2026" : single;
|
|
2984
3053
|
}
|
|
2985
3054
|
function printNoteRow(n, inboxName) {
|
|
2986
|
-
const oid =
|
|
2987
|
-
const id =
|
|
2988
|
-
const tags = n.tags.length ?
|
|
2989
|
-
const linked = n.linked_task_id ?
|
|
2990
|
-
const inboxPart = inboxName ?
|
|
2991
|
-
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)}`);
|
|
2992
3061
|
}
|
|
2993
3062
|
function printNoteDetail(n, inboxId) {
|
|
2994
|
-
if (n.object_id) console.log(`${
|
|
2995
|
-
console.log(`${
|
|
2996
|
-
if (inboxId) console.log(`${
|
|
2997
|
-
console.log(`${
|
|
2998
|
-
if (n.linked_task_id) console.log(`${
|
|
2999
|
-
if (n.linked_project) console.log(`${
|
|
3000
|
-
console.log(`${
|
|
3001
|
-
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}`);
|
|
3002
3071
|
if (n.published_to?.length) {
|
|
3003
|
-
console.log(
|
|
3072
|
+
console.log(chalk15.bold("published_to:"));
|
|
3004
3073
|
for (const p of n.published_to) {
|
|
3005
|
-
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)}`);
|
|
3006
3075
|
}
|
|
3007
3076
|
}
|
|
3008
|
-
console.log(
|
|
3077
|
+
console.log(chalk15.bold("body:"));
|
|
3009
3078
|
console.log(n.body.split("\n").map((l) => " " + l).join("\n"));
|
|
3010
3079
|
}
|
|
3011
3080
|
function fail4(err) {
|
|
3012
|
-
console.error(
|
|
3081
|
+
console.error(chalk15.red(err.message));
|
|
3013
3082
|
process.exit(1);
|
|
3014
3083
|
}
|
|
3015
3084
|
note.command("list").description("List inbox notes").option("--inbox <id>", "Restrict to a specific inbox").option("--json", "Output JSON").action(async (opts) => {
|
|
@@ -3023,7 +3092,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
|
|
|
3023
3092
|
return;
|
|
3024
3093
|
}
|
|
3025
3094
|
if (!notes.length) {
|
|
3026
|
-
console.log(
|
|
3095
|
+
console.log(chalk15.dim("No notes."));
|
|
3027
3096
|
return;
|
|
3028
3097
|
}
|
|
3029
3098
|
for (const n of notes) printNoteRow(n);
|
|
@@ -3036,7 +3105,7 @@ note.command("list").description("List inbox notes").option("--inbox <id>", "Res
|
|
|
3036
3105
|
}
|
|
3037
3106
|
const flat = all.flatMap(({ inbox, notes }) => notes.map((n) => ({ n, inboxName: inbox.name })));
|
|
3038
3107
|
if (!flat.length) {
|
|
3039
|
-
console.log(
|
|
3108
|
+
console.log(chalk15.dim("No notes."));
|
|
3040
3109
|
return;
|
|
3041
3110
|
}
|
|
3042
3111
|
for (const { n, inboxName } of flat) printNoteRow(n, inboxName);
|
|
@@ -3069,7 +3138,7 @@ note.command("create").description("Create a note in an inbox").requiredOption("
|
|
|
3069
3138
|
console.log(JSON.stringify({ note: created }, null, 2));
|
|
3070
3139
|
return;
|
|
3071
3140
|
}
|
|
3072
|
-
console.log(
|
|
3141
|
+
console.log(chalk15.green(`Created ${created.object_id || created.id}`));
|
|
3073
3142
|
printNoteDetail(created, opts.inbox);
|
|
3074
3143
|
} catch (err) {
|
|
3075
3144
|
fail4(err);
|
|
@@ -3085,7 +3154,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
|
|
|
3085
3154
|
if (opts.linkedTask !== void 0) payload.linked_task_id = opts.linkedTask;
|
|
3086
3155
|
if (opts.linkedProject !== void 0) payload.linked_project = opts.linkedProject;
|
|
3087
3156
|
if (!Object.keys(payload).length) {
|
|
3088
|
-
console.error(
|
|
3157
|
+
console.error(chalk15.yellow("Nothing to update. Pass --body, --tag, --clear-tags, --linked-task, or --linked-project."));
|
|
3089
3158
|
process.exit(1);
|
|
3090
3159
|
}
|
|
3091
3160
|
const { note: updated } = await api.patch(
|
|
@@ -3096,7 +3165,7 @@ note.command("update <id>").description("Update a note (accepts short id or note
|
|
|
3096
3165
|
console.log(JSON.stringify({ note: updated }, null, 2));
|
|
3097
3166
|
return;
|
|
3098
3167
|
}
|
|
3099
|
-
console.log(
|
|
3168
|
+
console.log(chalk15.green(`Updated ${updated.object_id || updated.id}`));
|
|
3100
3169
|
printNoteDetail(updated, inboxId);
|
|
3101
3170
|
} catch (err) {
|
|
3102
3171
|
fail4(err);
|
|
@@ -3108,7 +3177,7 @@ note.command("delete <id>").description("Delete a note (accepts short id or note
|
|
|
3108
3177
|
await api.del(
|
|
3109
3178
|
`/api/inboxes/${encodeURIComponent(inboxId)}/notes/${encodeURIComponent(existing.id)}`
|
|
3110
3179
|
);
|
|
3111
|
-
console.log(
|
|
3180
|
+
console.log(chalk15.green(`Deleted ${existing.object_id || existing.id}`));
|
|
3112
3181
|
} catch (err) {
|
|
3113
3182
|
fail4(err);
|
|
3114
3183
|
}
|
|
@@ -3126,8 +3195,8 @@ note.command("publish <id>").description("Publish a note to a github/linear sour
|
|
|
3126
3195
|
console.log(JSON.stringify({ published }, null, 2));
|
|
3127
3196
|
return;
|
|
3128
3197
|
}
|
|
3129
|
-
console.log(
|
|
3130
|
-
console.log(` ${
|
|
3198
|
+
console.log(chalk15.green(`Published to ${published.source_type}:${published.source_name} ${published.identifier}`));
|
|
3199
|
+
console.log(` ${chalk15.dim(published.url)}`);
|
|
3131
3200
|
} catch (err) {
|
|
3132
3201
|
fail4(err);
|
|
3133
3202
|
}
|
|
@@ -3143,16 +3212,16 @@ note.command("convert <id>").description("Convert a note into a task under a pro
|
|
|
3143
3212
|
console.log(JSON.stringify({ task: task2 }, null, 2));
|
|
3144
3213
|
return;
|
|
3145
3214
|
}
|
|
3146
|
-
console.log(
|
|
3215
|
+
console.log(chalk15.green(`Converted to task ${task2.object_id} (${task2.id}) in ${task2.project}`));
|
|
3147
3216
|
} catch (err) {
|
|
3148
3217
|
fail4(err);
|
|
3149
3218
|
}
|
|
3150
3219
|
});
|
|
3151
3220
|
|
|
3152
3221
|
// src/commands/object.ts
|
|
3153
|
-
import { Command as
|
|
3154
|
-
import
|
|
3155
|
-
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");
|
|
3156
3225
|
function pickFirst(resource, keys) {
|
|
3157
3226
|
for (const k of keys) {
|
|
3158
3227
|
const value = resource[k];
|
|
@@ -3162,16 +3231,16 @@ function pickFirst(resource, keys) {
|
|
|
3162
3231
|
}
|
|
3163
3232
|
function printSummary(result) {
|
|
3164
3233
|
const { type, resource } = result;
|
|
3165
|
-
console.log(`${
|
|
3234
|
+
console.log(`${chalk16.bold("type:")} ${type}`);
|
|
3166
3235
|
const title = pickFirst(resource, ["title", "name"]);
|
|
3167
3236
|
const id = pickFirst(resource, ["object_id", "objectId", "id"]);
|
|
3168
3237
|
const status = pickFirst(resource, ["status"]);
|
|
3169
|
-
if (id) console.log(`${
|
|
3170
|
-
if (title) console.log(`${
|
|
3171
|
-
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}`);
|
|
3172
3241
|
const updatedAt = pickFirst(resource, ["updated_at", "updatedAt"]);
|
|
3173
|
-
if (updatedAt) console.log(`${
|
|
3174
|
-
console.log(
|
|
3242
|
+
if (updatedAt) console.log(`${chalk16.bold("updated_at:")} ${updatedAt}`);
|
|
3243
|
+
console.log(chalk16.dim("(pass --json for full payload)"));
|
|
3175
3244
|
}
|
|
3176
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) => {
|
|
3177
3246
|
try {
|
|
@@ -3184,17 +3253,17 @@ object.command("get <object-id>").description("Resolve an object by object_id (t
|
|
|
3184
3253
|
} catch (err) {
|
|
3185
3254
|
const apiErr = err;
|
|
3186
3255
|
if (apiErr.status === 404) {
|
|
3187
|
-
console.error(
|
|
3256
|
+
console.error(chalk16.red(`not found: ${objectId}`));
|
|
3188
3257
|
process.exit(1);
|
|
3189
3258
|
}
|
|
3190
|
-
console.error(
|
|
3259
|
+
console.error(chalk16.red(err.message));
|
|
3191
3260
|
process.exit(1);
|
|
3192
3261
|
}
|
|
3193
3262
|
});
|
|
3194
3263
|
|
|
3195
3264
|
// src/commands/issue.ts
|
|
3196
|
-
import { Command as
|
|
3197
|
-
import
|
|
3265
|
+
import { Command as Command17 } from "commander";
|
|
3266
|
+
import chalk17 from "chalk";
|
|
3198
3267
|
|
|
3199
3268
|
// src/core/issue/decision.ts
|
|
3200
3269
|
init_node();
|
|
@@ -3368,7 +3437,7 @@ async function propose(opts) {
|
|
|
3368
3437
|
kicks.push({
|
|
3369
3438
|
issue: issue2.file,
|
|
3370
3439
|
agent: agent2,
|
|
3371
|
-
|
|
3440
|
+
agentRunId: "",
|
|
3372
3441
|
sessionName: "",
|
|
3373
3442
|
decisionFile,
|
|
3374
3443
|
error: null,
|
|
@@ -3378,7 +3447,7 @@ async function propose(opts) {
|
|
|
3378
3447
|
kicks.push({
|
|
3379
3448
|
issue: issue2.file,
|
|
3380
3449
|
agent: agent2,
|
|
3381
|
-
|
|
3450
|
+
agentRunId: "",
|
|
3382
3451
|
sessionName: "",
|
|
3383
3452
|
decisionFile,
|
|
3384
3453
|
error: `${decisionFile} already exists (pass --force to overwrite)`
|
|
@@ -3402,7 +3471,7 @@ async function propose(opts) {
|
|
|
3402
3471
|
kicks.push({
|
|
3403
3472
|
issue: issue2.file,
|
|
3404
3473
|
agent: agent2,
|
|
3405
|
-
|
|
3474
|
+
agentRunId: resp.runtime.id,
|
|
3406
3475
|
sessionName: "",
|
|
3407
3476
|
decisionFile,
|
|
3408
3477
|
error: null
|
|
@@ -3411,7 +3480,7 @@ async function propose(opts) {
|
|
|
3411
3480
|
kicks.push({
|
|
3412
3481
|
issue: issue2.file,
|
|
3413
3482
|
agent: agent2,
|
|
3414
|
-
|
|
3483
|
+
agentRunId: "",
|
|
3415
3484
|
sessionName: "",
|
|
3416
3485
|
decisionFile,
|
|
3417
3486
|
error: err.message
|
|
@@ -3419,25 +3488,25 @@ async function propose(opts) {
|
|
|
3419
3488
|
}
|
|
3420
3489
|
}
|
|
3421
3490
|
const runtimesByAgent = {};
|
|
3422
|
-
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;
|
|
3423
3492
|
const proposalFiles = kicks.filter((k) => !k.error).map((k) => k.decisionFile);
|
|
3424
3493
|
if (Object.keys(runtimesByAgent).length > 0 || proposalFiles.length > 0) {
|
|
3425
3494
|
await updateWorkflow(loc.taskYml, {
|
|
3426
3495
|
decisions: {
|
|
3427
3496
|
[issue2.file]: {
|
|
3428
3497
|
proposal_files: proposalFiles.length > 0 ? proposalFiles : void 0,
|
|
3429
|
-
|
|
3498
|
+
proposal_agent_runs: Object.keys(runtimesByAgent).length > 0 ? runtimesByAgent : void 0
|
|
3430
3499
|
}
|
|
3431
3500
|
}
|
|
3432
3501
|
});
|
|
3433
3502
|
}
|
|
3434
3503
|
if (opts.wait) {
|
|
3435
3504
|
for (const k of kicks) {
|
|
3436
|
-
if (k.error || !k.
|
|
3505
|
+
if (k.error || !k.agentRunId) continue;
|
|
3437
3506
|
try {
|
|
3438
|
-
const final = await
|
|
3507
|
+
const final = await waitForAgentRun(k.agentRunId);
|
|
3439
3508
|
if (final.status !== "done") {
|
|
3440
|
-
k.error = final.error || `runtime ${k.
|
|
3509
|
+
k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
|
|
3441
3510
|
} else if (!fs18.existsSync(path17.join(loc.taskDir, k.decisionFile))) {
|
|
3442
3511
|
k.error = `runtime completed but ${k.decisionFile} was not written`;
|
|
3443
3512
|
}
|
|
@@ -3467,7 +3536,7 @@ async function consolidate(opts) {
|
|
|
3467
3536
|
if (proposalFiles.length === 0) {
|
|
3468
3537
|
kicks.push({
|
|
3469
3538
|
issue: issue2.file,
|
|
3470
|
-
|
|
3539
|
+
agentRunId: "",
|
|
3471
3540
|
sessionName: "",
|
|
3472
3541
|
consolidatedFile: consolidatedFileName(issue2),
|
|
3473
3542
|
error: `no DECISION-${issue2.index}-<agent>.md proposals found. Run \`task0 issue propose\` first.`
|
|
@@ -3486,28 +3555,28 @@ async function consolidate(opts) {
|
|
|
3486
3555
|
prompt
|
|
3487
3556
|
}
|
|
3488
3557
|
);
|
|
3489
|
-
const
|
|
3558
|
+
const agentRunId = resp.runtime.id;
|
|
3490
3559
|
await updateWorkflow(loc.taskYml, {
|
|
3491
3560
|
decisions: {
|
|
3492
3561
|
[issue2.file]: {
|
|
3493
3562
|
consolidated_file: consolidatedFile,
|
|
3494
|
-
|
|
3563
|
+
consolidate_agent_run: agentRunId
|
|
3495
3564
|
}
|
|
3496
3565
|
}
|
|
3497
3566
|
});
|
|
3498
3567
|
const kick = {
|
|
3499
3568
|
issue: issue2.file,
|
|
3500
|
-
|
|
3569
|
+
agentRunId,
|
|
3501
3570
|
sessionName: "",
|
|
3502
3571
|
consolidatedFile,
|
|
3503
3572
|
error: null
|
|
3504
3573
|
};
|
|
3505
3574
|
if (opts.wait) {
|
|
3506
3575
|
try {
|
|
3507
|
-
const final = await
|
|
3576
|
+
const final = await waitForAgentRun(agentRunId);
|
|
3508
3577
|
const wrote = fs18.existsSync(path17.join(loc.taskDir, consolidatedFile));
|
|
3509
3578
|
if (final.status !== "done") {
|
|
3510
|
-
kick.error = final.error || `runtime ${
|
|
3579
|
+
kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
|
|
3511
3580
|
} else if (!wrote) {
|
|
3512
3581
|
kick.error = `runtime completed but ${consolidatedFile} was not written`;
|
|
3513
3582
|
}
|
|
@@ -3520,7 +3589,7 @@ async function consolidate(opts) {
|
|
|
3520
3589
|
} catch (err) {
|
|
3521
3590
|
kicks.push({
|
|
3522
3591
|
issue: issue2.file,
|
|
3523
|
-
|
|
3592
|
+
agentRunId: "",
|
|
3524
3593
|
sessionName: "",
|
|
3525
3594
|
consolidatedFile,
|
|
3526
3595
|
error: err.message
|
|
@@ -3580,7 +3649,7 @@ function buildReferenceFiles(taskDir, issue2) {
|
|
|
3580
3649
|
}
|
|
3581
3650
|
|
|
3582
3651
|
// src/commands/issue.ts
|
|
3583
|
-
var issue = new
|
|
3652
|
+
var issue = new Command17("issue").description(
|
|
3584
3653
|
"Resolve blocking ISSUE Open Questions via multi-agent decision proposals"
|
|
3585
3654
|
);
|
|
3586
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) => {
|
|
@@ -3596,7 +3665,7 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
|
|
|
3596
3665
|
wait: opts.wait,
|
|
3597
3666
|
force: opts.force,
|
|
3598
3667
|
ifNeeded: opts.ifNeeded,
|
|
3599
|
-
warn: opts.json ? void 0 : (m) => console.error(
|
|
3668
|
+
warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
|
|
3600
3669
|
});
|
|
3601
3670
|
if (opts.json) {
|
|
3602
3671
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3605,26 +3674,26 @@ issue.command("propose <taskId>").description(`Fan out agents to propose decisio
|
|
|
3605
3674
|
return;
|
|
3606
3675
|
}
|
|
3607
3676
|
if (opts.ifNeeded && result.issues.length === 0) {
|
|
3608
|
-
console.log(
|
|
3677
|
+
console.log(chalk17.dim("no blocking Open Questions; nothing to propose"));
|
|
3609
3678
|
return;
|
|
3610
3679
|
}
|
|
3611
3680
|
let anyFailed = false;
|
|
3612
3681
|
for (const item of result.issues) {
|
|
3613
|
-
console.log(
|
|
3682
|
+
console.log(chalk17.bold(item.issue));
|
|
3614
3683
|
for (const k of item.kicks) {
|
|
3615
3684
|
if (k.error) {
|
|
3616
3685
|
anyFailed = true;
|
|
3617
|
-
console.error(
|
|
3686
|
+
console.error(chalk17.red(` [${k.agent}] ${k.error}`));
|
|
3618
3687
|
} else if (k.skipped === "already-exists") {
|
|
3619
|
-
console.log(
|
|
3688
|
+
console.log(chalk17.dim(` [${k.agent}] skipped (${k.decisionFile} exists)`));
|
|
3620
3689
|
} else {
|
|
3621
|
-
console.log(
|
|
3690
|
+
console.log(chalk17.green(` [${k.agent}]`) + ` runtime ${k.agentRunId} \u2192 ${k.decisionFile}`);
|
|
3622
3691
|
}
|
|
3623
3692
|
}
|
|
3624
3693
|
}
|
|
3625
3694
|
if (anyFailed) process.exit(1);
|
|
3626
3695
|
} catch (err) {
|
|
3627
|
-
console.error(
|
|
3696
|
+
console.error(chalk17.red(err.message));
|
|
3628
3697
|
process.exit(1);
|
|
3629
3698
|
}
|
|
3630
3699
|
});
|
|
@@ -3637,7 +3706,7 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
|
|
|
3637
3706
|
model: opts.model,
|
|
3638
3707
|
effort: opts.effort,
|
|
3639
3708
|
wait: opts.wait,
|
|
3640
|
-
warn: opts.json ? void 0 : (m) => console.error(
|
|
3709
|
+
warn: opts.json ? void 0 : (m) => console.error(chalk17.dim(m))
|
|
3641
3710
|
});
|
|
3642
3711
|
if (opts.json) {
|
|
3643
3712
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3649,16 +3718,16 @@ issue.command("consolidate-propose <taskId>").description("Synthesize DECISION-N
|
|
|
3649
3718
|
for (const k of result.kicks) {
|
|
3650
3719
|
if (k.error) {
|
|
3651
3720
|
anyFailed = true;
|
|
3652
|
-
console.error(
|
|
3721
|
+
console.error(chalk17.red(`[${k.issue}] ${k.error}`));
|
|
3653
3722
|
} else {
|
|
3654
3723
|
console.log(
|
|
3655
|
-
|
|
3724
|
+
chalk17.green(`[${k.issue}]`) + ` runtime ${k.agentRunId} \u2192 ${k.consolidatedFile}` + (k.wrote === false ? chalk17.yellow(" (pending write)") : "")
|
|
3656
3725
|
);
|
|
3657
3726
|
}
|
|
3658
3727
|
}
|
|
3659
3728
|
if (anyFailed) process.exit(1);
|
|
3660
3729
|
} catch (err) {
|
|
3661
|
-
console.error(
|
|
3730
|
+
console.error(chalk17.red(err.message));
|
|
3662
3731
|
process.exit(1);
|
|
3663
3732
|
}
|
|
3664
3733
|
});
|
|
@@ -3670,10 +3739,10 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
3670
3739
|
return;
|
|
3671
3740
|
}
|
|
3672
3741
|
for (const u of result.updated) {
|
|
3673
|
-
console.log(
|
|
3742
|
+
console.log(chalk17.green(`approved ${u.issueFile}`) + ` (${u.questionsReplaced} decisions \u2190 ${u.consolidatedFile})`);
|
|
3674
3743
|
}
|
|
3675
3744
|
} catch (err) {
|
|
3676
|
-
console.error(
|
|
3745
|
+
console.error(chalk17.red(err.message));
|
|
3677
3746
|
process.exit(1);
|
|
3678
3747
|
}
|
|
3679
3748
|
});
|
|
@@ -3681,23 +3750,37 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
3681
3750
|
// src/commands/agent.ts
|
|
3682
3751
|
import fs19 from "fs";
|
|
3683
3752
|
import path18 from "path";
|
|
3684
|
-
import { spawnSync as
|
|
3685
|
-
import { Command as
|
|
3686
|
-
import
|
|
3687
|
-
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
|
+
}
|
|
3688
3770
|
function fail5(message, code = 1) {
|
|
3689
|
-
console.error(
|
|
3771
|
+
console.error(chalk18.red(message));
|
|
3690
3772
|
process.exit(code);
|
|
3691
3773
|
}
|
|
3692
3774
|
function formatAgent(a, withDetails = false) {
|
|
3693
|
-
const tag = a.system ?
|
|
3694
|
-
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}`;
|
|
3695
3778
|
if (!withDetails) return head;
|
|
3696
3779
|
const lines = [head];
|
|
3697
|
-
if (a.description) lines.push(
|
|
3780
|
+
if (a.description) lines.push(chalk18.dim(" " + a.description));
|
|
3698
3781
|
return lines.join("\n");
|
|
3699
3782
|
}
|
|
3700
|
-
var agent = new
|
|
3783
|
+
var agent = new Command18("agent").description("Manage agents and run them against tasks");
|
|
3701
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) => {
|
|
3702
3785
|
try {
|
|
3703
3786
|
const params = new URLSearchParams();
|
|
@@ -3707,7 +3790,7 @@ agent.command("list").description("List visible agents (project + user + built-i
|
|
|
3707
3790
|
const result = await api.get(`/api/agents${qs}`);
|
|
3708
3791
|
if (opts.json) return console.log(JSON.stringify(result.agents, null, 2));
|
|
3709
3792
|
if (!result.agents.length) {
|
|
3710
|
-
console.log(
|
|
3793
|
+
console.log(chalk18.dim("(no agents)"));
|
|
3711
3794
|
return;
|
|
3712
3795
|
}
|
|
3713
3796
|
for (const a of result.agents) console.log(formatAgent(a, true));
|
|
@@ -3718,11 +3801,23 @@ agent.command("list").description("List visible agents (project + user + built-i
|
|
|
3718
3801
|
agent.command("get <ref>").description("Show one agent by object_id or slug").option("--json", "Output JSON").action(async (ref, opts) => {
|
|
3719
3802
|
try {
|
|
3720
3803
|
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
|
|
3721
|
-
if (opts.json) return console.log(JSON.stringify(result
|
|
3804
|
+
if (opts.json) return console.log(JSON.stringify(result, null, 2));
|
|
3722
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
|
+
}
|
|
3723
3814
|
console.log();
|
|
3724
|
-
console.log(
|
|
3725
|
-
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
|
+
}
|
|
3726
3821
|
} catch (err) {
|
|
3727
3822
|
const apiErr = err;
|
|
3728
3823
|
if (apiErr.status === 404) fail5(`not found: ${ref}`);
|
|
@@ -3733,14 +3828,14 @@ agent.command("create").description("Create an agent from a YAML spec file").req
|
|
|
3733
3828
|
let parsed;
|
|
3734
3829
|
try {
|
|
3735
3830
|
const raw = fs19.readFileSync(path18.resolve(opts.fromFile), "utf-8");
|
|
3736
|
-
parsed =
|
|
3831
|
+
parsed = yaml7.load(raw);
|
|
3737
3832
|
} catch (err) {
|
|
3738
3833
|
fail5(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
3739
3834
|
}
|
|
3740
3835
|
try {
|
|
3741
3836
|
const body = { ...parsed, scope: opts.scope, project_root: opts.projectRoot };
|
|
3742
3837
|
const result = await api.post("/api/agents", body);
|
|
3743
|
-
console.log(
|
|
3838
|
+
console.log(chalk18.green(`created ${result.agent.slug} (${result.agent.object_id})`));
|
|
3744
3839
|
} catch (err) {
|
|
3745
3840
|
fail5(err.message);
|
|
3746
3841
|
}
|
|
@@ -3753,14 +3848,14 @@ agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save
|
|
|
3753
3848
|
process.env.TMPDIR || "/tmp",
|
|
3754
3849
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
3755
3850
|
);
|
|
3756
|
-
fs19.writeFileSync(tmp,
|
|
3851
|
+
fs19.writeFileSync(tmp, yaml7.dump(result.agent, { lineWidth: 100 }), "utf-8");
|
|
3757
3852
|
const editor = process.env.EDITOR || "vi";
|
|
3758
|
-
const r =
|
|
3853
|
+
const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
|
|
3759
3854
|
if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
|
|
3760
|
-
const updated =
|
|
3855
|
+
const updated = yaml7.load(fs19.readFileSync(tmp, "utf-8"));
|
|
3761
3856
|
await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
|
|
3762
3857
|
fs19.unlinkSync(tmp);
|
|
3763
|
-
console.log(
|
|
3858
|
+
console.log(chalk18.green(`updated ${ref}`));
|
|
3764
3859
|
} catch (err) {
|
|
3765
3860
|
fail5(err.message);
|
|
3766
3861
|
}
|
|
@@ -3769,8 +3864,42 @@ agent.command("delete <ref>").description("Delete an agent").action(async (ref)
|
|
|
3769
3864
|
try {
|
|
3770
3865
|
const result = await api.del(`/api/agents/${encodeURIComponent(ref)}`);
|
|
3771
3866
|
if (!result.deleted) fail5(`not deleted: ${ref}`);
|
|
3772
|
-
console.log(
|
|
3867
|
+
console.log(chalk18.green(`deleted ${ref}`));
|
|
3868
|
+
} catch (err) {
|
|
3869
|
+
fail5(err.message);
|
|
3870
|
+
}
|
|
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
|
+
}
|
|
3773
3900
|
} catch (err) {
|
|
3901
|
+
const apiErr = err;
|
|
3902
|
+
if (apiErr.status === 404) fail5(`not found: ${ref}`);
|
|
3774
3903
|
fail5(err.message);
|
|
3775
3904
|
}
|
|
3776
3905
|
});
|
|
@@ -3780,27 +3909,54 @@ agent.command("run <ref>").description("Run an agent against a task with a user
|
|
|
3780
3909
|
`/api/agents/${encodeURIComponent(ref)}/run`,
|
|
3781
3910
|
{ task_id: opts.task, prompt: opts.prompt }
|
|
3782
3911
|
);
|
|
3783
|
-
console.log(
|
|
3784
|
-
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}`));
|
|
3785
3914
|
if (opts.stream) {
|
|
3786
3915
|
await streamOutput(ref, result.runtime.id);
|
|
3787
3916
|
}
|
|
3788
3917
|
if (opts.attach) {
|
|
3789
|
-
console.log(
|
|
3918
|
+
console.log(chalk18.dim("--attach not yet wired; runtime is visible in dashboard"));
|
|
3790
3919
|
}
|
|
3791
3920
|
} catch (err) {
|
|
3792
3921
|
fail5(err.message);
|
|
3793
3922
|
}
|
|
3794
3923
|
});
|
|
3795
|
-
|
|
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) {
|
|
3796
3952
|
let lastCount = 0;
|
|
3797
3953
|
for (let i = 0; i < 600; i++) {
|
|
3798
3954
|
const result = await api.get(
|
|
3799
|
-
`/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(
|
|
3955
|
+
`/api/agents/${encodeURIComponent(ref)}/runs/${encodeURIComponent(agentRunId)}/output`
|
|
3800
3956
|
);
|
|
3801
3957
|
for (const line of result.lines.slice(lastCount)) {
|
|
3802
3958
|
if (line.error?.message) {
|
|
3803
|
-
console.error(
|
|
3959
|
+
console.error(chalk18.red(`[error] ${line.error.message}`));
|
|
3804
3960
|
return;
|
|
3805
3961
|
}
|
|
3806
3962
|
const text = line.message?.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
@@ -3812,47 +3968,115 @@ async function streamOutput(ref, runtimeId) {
|
|
|
3812
3968
|
}
|
|
3813
3969
|
|
|
3814
3970
|
// src/commands/daemon.ts
|
|
3815
|
-
import
|
|
3816
|
-
import { Command as
|
|
3817
|
-
import
|
|
3971
|
+
import os7 from "os";
|
|
3972
|
+
import { Command as Command19 } from "commander";
|
|
3973
|
+
import chalk19 from "chalk";
|
|
3818
3974
|
import WebSocket from "ws";
|
|
3819
3975
|
|
|
3820
|
-
// src/core/
|
|
3976
|
+
// src/core/admin-token.ts
|
|
3821
3977
|
import fs20 from "fs";
|
|
3822
3978
|
import os4 from "os";
|
|
3823
3979
|
import path19 from "path";
|
|
3824
3980
|
var CONFIG_DIR2 = path19.join(os4.homedir(), ".config", "task0");
|
|
3825
|
-
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");
|
|
3826
4019
|
function daemonConfigPath() {
|
|
3827
4020
|
return CONFIG_FILE2;
|
|
3828
4021
|
}
|
|
3829
4022
|
function readDaemonIdentity() {
|
|
3830
|
-
if (!
|
|
4023
|
+
if (!fs21.existsSync(CONFIG_FILE2)) return null;
|
|
3831
4024
|
try {
|
|
3832
|
-
const raw =
|
|
4025
|
+
const raw = fs21.readFileSync(CONFIG_FILE2, "utf-8");
|
|
3833
4026
|
return JSON.parse(raw);
|
|
3834
4027
|
} catch {
|
|
3835
4028
|
return null;
|
|
3836
4029
|
}
|
|
3837
4030
|
}
|
|
3838
4031
|
function writeDaemonIdentity(identity) {
|
|
3839
|
-
|
|
3840
|
-
|
|
4032
|
+
fs21.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
4033
|
+
fs21.writeFileSync(CONFIG_FILE2, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
3841
4034
|
try {
|
|
3842
|
-
|
|
4035
|
+
fs21.chmodSync(CONFIG_FILE2, 384);
|
|
3843
4036
|
} catch {
|
|
3844
4037
|
}
|
|
3845
4038
|
}
|
|
3846
4039
|
function clearDaemonIdentity() {
|
|
3847
|
-
if (!
|
|
3848
|
-
|
|
4040
|
+
if (!fs21.existsSync(CONFIG_FILE2)) return false;
|
|
4041
|
+
fs21.unlinkSync(CONFIG_FILE2);
|
|
3849
4042
|
return true;
|
|
3850
4043
|
}
|
|
3851
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
|
+
|
|
3852
4076
|
// src/core/daemon-rpc-handlers.ts
|
|
3853
4077
|
init_node();
|
|
3854
|
-
import
|
|
3855
|
-
import
|
|
4078
|
+
import fs22 from "fs";
|
|
4079
|
+
import path21 from "path";
|
|
3856
4080
|
var MAX_FILE_BYTES = 1 * 1024 * 1024;
|
|
3857
4081
|
function ensureString(value, name) {
|
|
3858
4082
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -3865,7 +4089,7 @@ var rpcHandlers = {
|
|
|
3865
4089
|
// in-process server's /api/tasks scanProject() returns.
|
|
3866
4090
|
async scan_project(params) {
|
|
3867
4091
|
const rootPath = ensureString(params.rootPath, "rootPath");
|
|
3868
|
-
const name = typeof params.name === "string" && params.name ? params.name :
|
|
4092
|
+
const name = typeof params.name === "string" && params.name ? params.name : path21.basename(rootPath);
|
|
3869
4093
|
return scanProject(rootPath, name);
|
|
3870
4094
|
},
|
|
3871
4095
|
// Read a file from disk on the daemon's host. The dashboard uses this to
|
|
@@ -3875,7 +4099,7 @@ var rpcHandlers = {
|
|
|
3875
4099
|
const filePath = ensureString(params.path, "path");
|
|
3876
4100
|
let stat;
|
|
3877
4101
|
try {
|
|
3878
|
-
stat =
|
|
4102
|
+
stat = fs22.statSync(filePath);
|
|
3879
4103
|
} catch {
|
|
3880
4104
|
throw Object.assign(new Error("file not found"), { code: "not_found" });
|
|
3881
4105
|
}
|
|
@@ -3885,23 +4109,23 @@ var rpcHandlers = {
|
|
|
3885
4109
|
if (stat.size > MAX_FILE_BYTES) {
|
|
3886
4110
|
throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
|
|
3887
4111
|
}
|
|
3888
|
-
const content =
|
|
4112
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
3889
4113
|
return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
|
|
3890
4114
|
}
|
|
3891
4115
|
};
|
|
3892
4116
|
|
|
3893
4117
|
// src/core/daemon-service/launchd.ts
|
|
3894
|
-
import { spawnSync as
|
|
3895
|
-
import
|
|
3896
|
-
import
|
|
4118
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
4119
|
+
import fs24 from "fs";
|
|
4120
|
+
import path23 from "path";
|
|
3897
4121
|
|
|
3898
4122
|
// src/core/daemon-service/binary.ts
|
|
3899
|
-
import
|
|
4123
|
+
import fs23 from "fs";
|
|
3900
4124
|
import { fileURLToPath } from "url";
|
|
3901
4125
|
function resolveTask0Invocation() {
|
|
3902
4126
|
const node = process.execPath;
|
|
3903
4127
|
const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
3904
|
-
const main2 =
|
|
4128
|
+
const main2 = fs23.realpathSync(argv1);
|
|
3905
4129
|
if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
|
|
3906
4130
|
throw new Error(
|
|
3907
4131
|
`Refusing to install autostart service pointing at ${main2}.
|
|
@@ -3917,8 +4141,8 @@ function isInstalledBuild(p) {
|
|
|
3917
4141
|
}
|
|
3918
4142
|
|
|
3919
4143
|
// src/core/daemon-service/paths.ts
|
|
3920
|
-
import
|
|
3921
|
-
import
|
|
4144
|
+
import os6 from "os";
|
|
4145
|
+
import path22 from "path";
|
|
3922
4146
|
|
|
3923
4147
|
// src/core/daemon-service/types.ts
|
|
3924
4148
|
var SERVICE_LABEL = "cc.cy0.task0";
|
|
@@ -3927,21 +4151,21 @@ var SERVICE_LABEL = "cc.cy0.task0";
|
|
|
3927
4151
|
function unitPath(scope) {
|
|
3928
4152
|
const platform = process.platform;
|
|
3929
4153
|
if (platform === "darwin") {
|
|
3930
|
-
return scope === "user" ?
|
|
4154
|
+
return scope === "user" ? path22.join(os6.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path22.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
|
|
3931
4155
|
}
|
|
3932
4156
|
if (platform === "linux") {
|
|
3933
|
-
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`);
|
|
3934
4158
|
}
|
|
3935
4159
|
throw new Error(`Unsupported platform for service install: ${platform}`);
|
|
3936
4160
|
}
|
|
3937
4161
|
function logDir() {
|
|
3938
|
-
return
|
|
4162
|
+
return path22.join(os6.homedir(), ".task0", "logs");
|
|
3939
4163
|
}
|
|
3940
4164
|
function logPaths() {
|
|
3941
4165
|
const dir = logDir();
|
|
3942
4166
|
return {
|
|
3943
|
-
out:
|
|
3944
|
-
err:
|
|
4167
|
+
out: path22.join(dir, "daemon.out.log"),
|
|
4168
|
+
err: path22.join(dir, "daemon.err.log")
|
|
3945
4169
|
};
|
|
3946
4170
|
}
|
|
3947
4171
|
|
|
@@ -3994,7 +4218,7 @@ function serviceTarget(scope) {
|
|
|
3994
4218
|
return `${domainTarget(scope)}/${SERVICE_LABEL}`;
|
|
3995
4219
|
}
|
|
3996
4220
|
function run2(cmd, args) {
|
|
3997
|
-
const res =
|
|
4221
|
+
const res = spawnSync6(cmd, args, { encoding: "utf-8" });
|
|
3998
4222
|
return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
3999
4223
|
}
|
|
4000
4224
|
function createLaunchdManager(scope) {
|
|
@@ -4002,8 +4226,8 @@ function createLaunchdManager(scope) {
|
|
|
4002
4226
|
const logs = logPaths();
|
|
4003
4227
|
async function install() {
|
|
4004
4228
|
const inv = resolveTask0Invocation();
|
|
4005
|
-
|
|
4006
|
-
|
|
4229
|
+
fs24.mkdirSync(logDir(), { recursive: true });
|
|
4230
|
+
fs24.mkdirSync(path23.dirname(file), { recursive: true });
|
|
4007
4231
|
const body = renderPlist({
|
|
4008
4232
|
node: inv.node,
|
|
4009
4233
|
main: inv.main,
|
|
@@ -4012,7 +4236,7 @@ function createLaunchdManager(scope) {
|
|
|
4012
4236
|
out: logs.out,
|
|
4013
4237
|
err: logs.err
|
|
4014
4238
|
});
|
|
4015
|
-
|
|
4239
|
+
fs24.writeFileSync(file, body, { mode: 420 });
|
|
4016
4240
|
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
4017
4241
|
if (bootstrap.code !== 0) {
|
|
4018
4242
|
const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
|
|
@@ -4038,9 +4262,9 @@ function createLaunchdManager(scope) {
|
|
|
4038
4262
|
}
|
|
4039
4263
|
async function uninstall() {
|
|
4040
4264
|
run2("launchctl", ["bootout", serviceTarget(scope)]);
|
|
4041
|
-
if (
|
|
4265
|
+
if (fs24.existsSync(file)) {
|
|
4042
4266
|
run2("launchctl", ["unload", file]);
|
|
4043
|
-
|
|
4267
|
+
fs24.unlinkSync(file);
|
|
4044
4268
|
}
|
|
4045
4269
|
}
|
|
4046
4270
|
async function start() {
|
|
@@ -4059,7 +4283,7 @@ function createLaunchdManager(scope) {
|
|
|
4059
4283
|
}
|
|
4060
4284
|
}
|
|
4061
4285
|
async function status() {
|
|
4062
|
-
if (!
|
|
4286
|
+
if (!fs24.existsSync(file)) return "absent";
|
|
4063
4287
|
const printed = run2("launchctl", ["print", serviceTarget(scope)]);
|
|
4064
4288
|
if (printed.code !== 0) return "installed";
|
|
4065
4289
|
const out = printed.stdout;
|
|
@@ -4081,9 +4305,9 @@ function createLaunchdManager(scope) {
|
|
|
4081
4305
|
}
|
|
4082
4306
|
|
|
4083
4307
|
// src/core/daemon-service/systemd.ts
|
|
4084
|
-
import { spawnSync as
|
|
4085
|
-
import
|
|
4086
|
-
import
|
|
4308
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
4309
|
+
import fs25 from "fs";
|
|
4310
|
+
import path24 from "path";
|
|
4087
4311
|
function shellEscape(s) {
|
|
4088
4312
|
if (!/[\s"\\$]/.test(s)) return s;
|
|
4089
4313
|
return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
|
|
@@ -4113,7 +4337,7 @@ function scopeFlag(scope) {
|
|
|
4113
4337
|
return scope === "user" ? ["--user"] : [];
|
|
4114
4338
|
}
|
|
4115
4339
|
function run3(cmd, args) {
|
|
4116
|
-
const res =
|
|
4340
|
+
const res = spawnSync7(cmd, args, { encoding: "utf-8" });
|
|
4117
4341
|
return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
4118
4342
|
}
|
|
4119
4343
|
function createSystemdManager(scope) {
|
|
@@ -4122,8 +4346,8 @@ function createSystemdManager(scope) {
|
|
|
4122
4346
|
const unitName = `${SERVICE_LABEL}.service`;
|
|
4123
4347
|
async function install() {
|
|
4124
4348
|
const inv = resolveTask0Invocation();
|
|
4125
|
-
|
|
4126
|
-
|
|
4349
|
+
fs25.mkdirSync(logDir(), { recursive: true });
|
|
4350
|
+
fs25.mkdirSync(path24.dirname(file), { recursive: true });
|
|
4127
4351
|
const body = renderUnit({
|
|
4128
4352
|
node: inv.node,
|
|
4129
4353
|
main: inv.main,
|
|
@@ -4132,7 +4356,7 @@ function createSystemdManager(scope) {
|
|
|
4132
4356
|
err: logs.err,
|
|
4133
4357
|
scope
|
|
4134
4358
|
});
|
|
4135
|
-
|
|
4359
|
+
fs25.writeFileSync(file, body, { mode: 420 });
|
|
4136
4360
|
const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4137
4361
|
if (reload.code !== 0) {
|
|
4138
4362
|
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
@@ -4141,8 +4365,8 @@ function createSystemdManager(scope) {
|
|
|
4141
4365
|
}
|
|
4142
4366
|
async function uninstall() {
|
|
4143
4367
|
run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
|
|
4144
|
-
if (
|
|
4145
|
-
|
|
4368
|
+
if (fs25.existsSync(file)) {
|
|
4369
|
+
fs25.unlinkSync(file);
|
|
4146
4370
|
}
|
|
4147
4371
|
run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4148
4372
|
}
|
|
@@ -4159,7 +4383,7 @@ function createSystemdManager(scope) {
|
|
|
4159
4383
|
}
|
|
4160
4384
|
}
|
|
4161
4385
|
async function status() {
|
|
4162
|
-
if (!
|
|
4386
|
+
if (!fs25.existsSync(file)) return "absent";
|
|
4163
4387
|
const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
|
|
4164
4388
|
const out = res.stdout.trim();
|
|
4165
4389
|
if (out === "active") return "running";
|
|
@@ -4215,7 +4439,7 @@ function sendRpc(ws, payload) {
|
|
|
4215
4439
|
}
|
|
4216
4440
|
}
|
|
4217
4441
|
function fail6(message, code = 1) {
|
|
4218
|
-
console.error(
|
|
4442
|
+
console.error(chalk19.red(message));
|
|
4219
4443
|
process.exit(code);
|
|
4220
4444
|
}
|
|
4221
4445
|
function loadRequiredIdentity() {
|
|
@@ -4229,10 +4453,18 @@ function serverBase(identity) {
|
|
|
4229
4453
|
if (identity) return identity.server_url.replace(/\/$/, "");
|
|
4230
4454
|
return (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
4231
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
|
+
}
|
|
4232
4464
|
async function jsonGet(url) {
|
|
4233
4465
|
let res;
|
|
4234
4466
|
try {
|
|
4235
|
-
res = await fetch(url);
|
|
4467
|
+
res = await fetch(url, { headers: adminHeaders() });
|
|
4236
4468
|
} catch (error2) {
|
|
4237
4469
|
fail6(`Cannot reach ${url}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
4238
4470
|
}
|
|
@@ -4245,16 +4477,16 @@ function requireRootIfSystem(scope, rerunHint) {
|
|
|
4245
4477
|
if (scope !== "system") return;
|
|
4246
4478
|
const uid = process.getuid?.();
|
|
4247
4479
|
if (uid === 0) return;
|
|
4248
|
-
console.error(
|
|
4480
|
+
console.error(chalk19.red("--system requires root."));
|
|
4249
4481
|
console.error("Re-run with sudo, preserving env:");
|
|
4250
|
-
console.error(
|
|
4482
|
+
console.error(chalk19.cyan(` sudo -E ${rerunHint}`));
|
|
4251
4483
|
process.exit(1);
|
|
4252
4484
|
}
|
|
4253
4485
|
function rerunArgv() {
|
|
4254
4486
|
return ["task0", ...process.argv.slice(2)].join(" ");
|
|
4255
4487
|
}
|
|
4256
|
-
var daemonCmd = new
|
|
4257
|
-
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) => {
|
|
4258
4490
|
const scope = opts.system ? "system" : "user";
|
|
4259
4491
|
if (opts.install) {
|
|
4260
4492
|
requireRootIfSystem(scope, rerunArgv());
|
|
@@ -4264,8 +4496,19 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4264
4496
|
fail6(`Already registered as ${existing.daemon_id}. Pass --force to re-register.`);
|
|
4265
4497
|
}
|
|
4266
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
|
+
}
|
|
4267
4510
|
const body = {
|
|
4268
|
-
hostname:
|
|
4511
|
+
hostname: os7.hostname(),
|
|
4269
4512
|
platform: process.platform,
|
|
4270
4513
|
name: opts.name
|
|
4271
4514
|
};
|
|
@@ -4273,7 +4516,7 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4273
4516
|
try {
|
|
4274
4517
|
res = await fetch(`${base}/api/daemons/register`, {
|
|
4275
4518
|
method: "POST",
|
|
4276
|
-
headers: { "content-type": "application/json" },
|
|
4519
|
+
headers: { "content-type": "application/json", ...authHeader },
|
|
4277
4520
|
body: JSON.stringify(body)
|
|
4278
4521
|
});
|
|
4279
4522
|
} catch (error2) {
|
|
@@ -4293,16 +4536,16 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4293
4536
|
registered_at: data.daemon.registered_at
|
|
4294
4537
|
};
|
|
4295
4538
|
writeDaemonIdentity(identity);
|
|
4296
|
-
console.log(
|
|
4539
|
+
console.log(chalk19.green(`Registered as ${data.daemon.object_id} (${data.daemon.name})`));
|
|
4297
4540
|
console.log(`Identity saved to ${daemonConfigPath()}`);
|
|
4298
4541
|
if (!opts.install) {
|
|
4299
|
-
console.log(
|
|
4300
|
-
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.`));
|
|
4301
4544
|
return;
|
|
4302
4545
|
}
|
|
4303
4546
|
if (!isPlatformSupported()) {
|
|
4304
4547
|
console.log(
|
|
4305
|
-
|
|
4548
|
+
chalk19.yellow(
|
|
4306
4549
|
`Autostart service is not supported on ${process.platform}. Run \`task0 daemon run\` under a supervisor of your choice.`
|
|
4307
4550
|
)
|
|
4308
4551
|
);
|
|
@@ -4312,18 +4555,18 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4312
4555
|
try {
|
|
4313
4556
|
const { unitPath: unitPath2 } = await svc.install({ identity });
|
|
4314
4557
|
const logs = svc.logPaths();
|
|
4315
|
-
console.log(
|
|
4316
|
-
console.log(
|
|
4317
|
-
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}`));
|
|
4318
4561
|
if (opts.start) {
|
|
4319
4562
|
await svc.start();
|
|
4320
|
-
console.log(
|
|
4563
|
+
console.log(chalk19.green("Service started."));
|
|
4321
4564
|
} else {
|
|
4322
|
-
console.log(
|
|
4565
|
+
console.log(chalk19.dim("Skipping start (--no-start). Run `task0 daemon start` when ready."));
|
|
4323
4566
|
}
|
|
4324
4567
|
} catch (error2) {
|
|
4325
|
-
console.error(
|
|
4326
|
-
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."));
|
|
4327
4570
|
process.exit(1);
|
|
4328
4571
|
}
|
|
4329
4572
|
});
|
|
@@ -4336,7 +4579,7 @@ daemonCmd.command("start").description("Start the installed autostart service vi
|
|
|
4336
4579
|
const svc = getServiceManager(scope);
|
|
4337
4580
|
try {
|
|
4338
4581
|
await svc.start();
|
|
4339
|
-
console.log(
|
|
4582
|
+
console.log(chalk19.green("Service started."));
|
|
4340
4583
|
} catch (error2) {
|
|
4341
4584
|
fail6(error2 instanceof Error ? error2.message : String(error2));
|
|
4342
4585
|
}
|
|
@@ -4350,7 +4593,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
|
|
|
4350
4593
|
const svc = getServiceManager(scope);
|
|
4351
4594
|
try {
|
|
4352
4595
|
await svc.stop();
|
|
4353
|
-
console.log(
|
|
4596
|
+
console.log(chalk19.green("Service stopped."));
|
|
4354
4597
|
} catch (error2) {
|
|
4355
4598
|
fail6(error2 instanceof Error ? error2.message : String(error2));
|
|
4356
4599
|
}
|
|
@@ -4358,7 +4601,7 @@ daemonCmd.command("stop").description("Stop the autostart service via launchctl
|
|
|
4358
4601
|
daemonCmd.command("run").description("Run the daemon WebSocket loop in foreground (invoked by the service unit; useful for debugging)").action(async () => {
|
|
4359
4602
|
const identity = loadRequiredIdentity();
|
|
4360
4603
|
const wsUrl = identity.server_url.replace(/^http/, "ws").replace(/\/$/, "") + "/ws/daemon";
|
|
4361
|
-
console.log(
|
|
4604
|
+
console.log(chalk19.green(`Starting daemon ${identity.daemon_id} \u2192 ${wsUrl}`));
|
|
4362
4605
|
let reconnectDelay = 1e3;
|
|
4363
4606
|
let shouldRun = true;
|
|
4364
4607
|
let activeWs = null;
|
|
@@ -4369,7 +4612,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4369
4612
|
activeWs = ws;
|
|
4370
4613
|
ws.on("open", () => {
|
|
4371
4614
|
reconnectDelay = 1e3;
|
|
4372
|
-
console.log(
|
|
4615
|
+
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
4373
4616
|
const hello = {
|
|
4374
4617
|
type: "hello",
|
|
4375
4618
|
daemon_id: identity.daemon_id,
|
|
@@ -4381,7 +4624,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4381
4624
|
const projects = loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
|
|
4382
4625
|
const manifest = { type: "manifest", projects };
|
|
4383
4626
|
ws.send(JSON.stringify(manifest));
|
|
4384
|
-
console.log(
|
|
4627
|
+
console.log(chalk19.dim(`pushed manifest: ${projects.length} project(s)`));
|
|
4385
4628
|
});
|
|
4386
4629
|
ws.on("message", (raw) => {
|
|
4387
4630
|
let msg;
|
|
@@ -4391,12 +4634,12 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4391
4634
|
return;
|
|
4392
4635
|
}
|
|
4393
4636
|
if (msg.type === "welcome") {
|
|
4394
|
-
console.log(
|
|
4637
|
+
console.log(chalk19.green(`welcomed ${msg.server_time}`));
|
|
4395
4638
|
} else if (msg.type === "ping") {
|
|
4396
4639
|
const beat = { type: "heartbeat", ts: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4397
4640
|
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(beat));
|
|
4398
4641
|
} else if (msg.type === "error") {
|
|
4399
|
-
console.error(
|
|
4642
|
+
console.error(chalk19.yellow(`server error: ${msg.message}`));
|
|
4400
4643
|
} else if (msg.type === "rpc_request") {
|
|
4401
4644
|
void dispatchRpc(ws, msg.id, msg.method, msg.params);
|
|
4402
4645
|
}
|
|
@@ -4404,9 +4647,9 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4404
4647
|
ws.on("close", (code, reason) => {
|
|
4405
4648
|
activeWs = null;
|
|
4406
4649
|
const reasonText = reason.toString("utf-8") || "no reason";
|
|
4407
|
-
console.log(
|
|
4650
|
+
console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
|
|
4408
4651
|
if (code === 4001) {
|
|
4409
|
-
console.error(
|
|
4652
|
+
console.error(chalk19.red("superseded by another daemon connection; exiting"));
|
|
4410
4653
|
process.exit(0);
|
|
4411
4654
|
}
|
|
4412
4655
|
if (shouldRun) {
|
|
@@ -4415,7 +4658,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4415
4658
|
}
|
|
4416
4659
|
});
|
|
4417
4660
|
ws.on("error", (err) => {
|
|
4418
|
-
console.error(
|
|
4661
|
+
console.error(chalk19.red(`ws error: ${err.message}`));
|
|
4419
4662
|
});
|
|
4420
4663
|
}
|
|
4421
4664
|
const shutdown = () => {
|
|
@@ -4440,9 +4683,9 @@ daemonCmd.command("list").description("List daemons registered on the configured
|
|
|
4440
4683
|
return;
|
|
4441
4684
|
}
|
|
4442
4685
|
for (const d of data.daemons) {
|
|
4443
|
-
const status = d.status === "online" ?
|
|
4686
|
+
const status = d.status === "online" ? chalk19.green(d.status) : chalk19.dim(d.status);
|
|
4444
4687
|
console.log(
|
|
4445
|
-
`${
|
|
4688
|
+
`${chalk19.bold(d.object_id)} ${d.name.padEnd(20)} ${d.hostname.padEnd(24)} ${d.platform.padEnd(8)} ${status}`
|
|
4446
4689
|
);
|
|
4447
4690
|
}
|
|
4448
4691
|
});
|
|
@@ -4455,9 +4698,9 @@ daemonCmd.command("show [daemonId]").description("Show local daemon identity (no
|
|
|
4455
4698
|
try {
|
|
4456
4699
|
const svc = getServiceManager(scope);
|
|
4457
4700
|
const state2 = await svc.status();
|
|
4458
|
-
console.log(
|
|
4701
|
+
console.log(chalk19.dim(`service (${scope}): ${state2} \u2192 ${svc.unitPath()}`));
|
|
4459
4702
|
} catch (error2) {
|
|
4460
|
-
console.log(
|
|
4703
|
+
console.log(chalk19.dim(`service status unavailable: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
4461
4704
|
}
|
|
4462
4705
|
}
|
|
4463
4706
|
return;
|
|
@@ -4474,21 +4717,169 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
|
|
|
4474
4717
|
const svc = getServiceManager(scope);
|
|
4475
4718
|
try {
|
|
4476
4719
|
await svc.uninstall();
|
|
4477
|
-
console.log(
|
|
4720
|
+
console.log(chalk19.dim(`Service unit removed (${scope}).`));
|
|
4478
4721
|
} catch (error2) {
|
|
4479
4722
|
console.error(
|
|
4480
|
-
|
|
4723
|
+
chalk19.yellow(`Service uninstall warning: ${error2 instanceof Error ? error2.message : String(error2)}`)
|
|
4481
4724
|
);
|
|
4482
4725
|
}
|
|
4483
4726
|
}
|
|
4484
4727
|
}
|
|
4485
4728
|
if (clearDaemonIdentity()) {
|
|
4486
|
-
console.log(
|
|
4729
|
+
console.log(chalk19.green("Daemon identity cleared."));
|
|
4487
4730
|
} else {
|
|
4488
4731
|
console.log("No daemon identity to clear.");
|
|
4489
4732
|
}
|
|
4490
4733
|
});
|
|
4491
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
|
+
|
|
4492
4883
|
// src/commands/error.ts
|
|
4493
4884
|
init_node();
|
|
4494
4885
|
import { Command as Command21 } from "commander";
|
|
@@ -4653,6 +5044,209 @@ error.command("prune").description("Remove captured error reports by age, count,
|
|
|
4653
5044
|
console.log(`Removed ${res.removed.length} report(s); ${res.kept} remaining.`);
|
|
4654
5045
|
});
|
|
4655
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
|
+
|
|
4656
5250
|
// src/core/error-capture.ts
|
|
4657
5251
|
init_node();
|
|
4658
5252
|
var DEFAULT_KEEP = 50;
|
|
@@ -4753,30 +5347,31 @@ function captureTopLevel(err, options) {
|
|
|
4753
5347
|
var TASK0_VERSION = readVersion();
|
|
4754
5348
|
function readVersion() {
|
|
4755
5349
|
try {
|
|
4756
|
-
const here =
|
|
4757
|
-
const pkg = JSON.parse(readFileSync(
|
|
5350
|
+
const here = path25.dirname(fileURLToPath2(import.meta.url));
|
|
5351
|
+
const pkg = JSON.parse(readFileSync(path25.resolve(here, "..", "package.json"), "utf-8"));
|
|
4758
5352
|
return pkg.version ?? "unknown";
|
|
4759
5353
|
} catch {
|
|
4760
5354
|
return "unknown";
|
|
4761
5355
|
}
|
|
4762
5356
|
}
|
|
4763
|
-
var program = new
|
|
5357
|
+
var program = new Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
|
|
4764
5358
|
program.addCommand(source);
|
|
4765
5359
|
program.addCommand(project);
|
|
4766
5360
|
program.addCommand(task);
|
|
4767
|
-
program.addCommand(
|
|
5361
|
+
program.addCommand(agentRun);
|
|
4768
5362
|
program.addCommand(plan);
|
|
4769
5363
|
program.addCommand(okr);
|
|
4770
5364
|
program.addCommand(note);
|
|
4771
5365
|
program.addCommand(run);
|
|
4772
5366
|
program.addCommand(ui);
|
|
4773
|
-
program.addCommand(serve);
|
|
4774
5367
|
program.addCommand(models);
|
|
4775
5368
|
program.addCommand(object);
|
|
4776
5369
|
program.addCommand(issue);
|
|
4777
5370
|
program.addCommand(agent);
|
|
4778
5371
|
program.addCommand(daemonCmd);
|
|
5372
|
+
program.addCommand(userCmd);
|
|
4779
5373
|
program.addCommand(error);
|
|
5374
|
+
program.addCommand(automation);
|
|
4780
5375
|
async function main() {
|
|
4781
5376
|
installErrorCapture({ version: TASK0_VERSION });
|
|
4782
5377
|
try {
|