@hilbras/omninode 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/dist/cli/index.js +325 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +79 -5
- package/dist/index.js +241 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ existing intelligence; it does not try to become another model.
|
|
|
18
18
|
|
|
19
19
|
## Status
|
|
20
20
|
|
|
21
|
-
**v0.
|
|
21
|
+
**v0.5.0 — Phase 4: Roles & Tasks** (see [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.md)
|
|
22
22
|
for the full plan and roadmap).
|
|
23
23
|
|
|
24
24
|
What exists today:
|
|
@@ -44,6 +44,15 @@ What exists today:
|
|
|
44
44
|
`TASK`/`ROLE`/`CONTEXT`/`INSTRUCTION` envelopes in, `REPORT`/`COMPLETION`/
|
|
45
45
|
`ERROR` messages out, parsed into structured reports)
|
|
46
46
|
- Agent factory and registry (`createAgent`, `AgentRegistry`)
|
|
47
|
+
- **Roles & tasks** (`src/roles/`, `src/tasks/`):
|
|
48
|
+
- `RoleRegistry` with assignment validation — a task can only reference roles
|
|
49
|
+
and agents that actually exist
|
|
50
|
+
- Task lifecycle (created → queued → running → completed/failed/cancelled)
|
|
51
|
+
with transition enforcement
|
|
52
|
+
- `TaskEngine` runs tasks through their assigned agent, records structured
|
|
53
|
+
results (summary, reports, errors, timestamps)
|
|
54
|
+
- `TaskStore` interface with a local-first JSON implementation in
|
|
55
|
+
`.omninode/tasks.json` (atomic writes; swap in any backend)
|
|
47
56
|
- Typed error hierarchy (`src/errors/`)
|
|
48
57
|
- Leveled logger with pluggable sink (`src/logger/`)
|
|
49
58
|
- Configuration system: `omninode.yaml` with `${ENV_VAR}` expansion and strict
|
|
@@ -93,6 +102,15 @@ omninode agent add --name opencode --command opencode --input-mode arg
|
|
|
93
102
|
omninode agent test opencode # sends a trivial task over the real adapter
|
|
94
103
|
```
|
|
95
104
|
|
|
105
|
+
Create, run and track tasks (the `Project → Task → Role → Agent` chain):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
omninode task create "audit the auth module" --role security-reviewer --agent opencode
|
|
109
|
+
omninode task run task-xxxxxx
|
|
110
|
+
omninode task status task-xxxxxx
|
|
111
|
+
omninode task list
|
|
112
|
+
```
|
|
113
|
+
|
|
96
114
|
See [omninode.yaml.example](omninode.yaml.example) for a full configuration.
|
|
97
115
|
|
|
98
116
|
### Configuration
|
|
@@ -125,8 +143,8 @@ Phases 0–11 are laid out in [docs/DEVELOPMENT_PLAN.md](docs/DEVELOPMENT_PLAN.m
|
|
|
125
143
|
| v0.1.0 | Foundation |
|
|
126
144
|
| v0.2.0 | Provider System |
|
|
127
145
|
| v0.3.0 | OmniHilbras Integration (optional, like every provider) |
|
|
128
|
-
|
|
|
129
|
-
| v0.5.0 | Roles & Tasks |
|
|
146
|
+
| v0.4.0 | Agent System |
|
|
147
|
+
| **v0.5.0** | **Roles & Tasks (current)** |
|
|
130
148
|
| v0.6.0 | Pipeline Engine |
|
|
131
149
|
| v0.7.0 | Multi-AI Reports |
|
|
132
150
|
| v0.8.0 | Remembera (optional memory integration) |
|
package/dist/cli/index.js
CHANGED
|
@@ -34,6 +34,11 @@ var AgentError = class extends OmniNodeError {
|
|
|
34
34
|
super(code, message, options);
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
+
var TaskError = class extends OmniNodeError {
|
|
38
|
+
constructor(code, message, options = {}) {
|
|
39
|
+
super(code, message, options);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
37
42
|
var ProtocolError = class extends OmniNodeError {
|
|
38
43
|
constructor(message, options = {}) {
|
|
39
44
|
super("PROTOCOL_VIOLATION", message, options);
|
|
@@ -1061,6 +1066,20 @@ function firstLine(text, maxLength = 200) {
|
|
|
1061
1066
|
return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
|
|
1062
1067
|
}
|
|
1063
1068
|
|
|
1069
|
+
// src/agents/registry.ts
|
|
1070
|
+
var AgentRegistry = class {
|
|
1071
|
+
agents = /* @__PURE__ */ new Map();
|
|
1072
|
+
register(agent) {
|
|
1073
|
+
this.agents.set(agent.info.name, agent);
|
|
1074
|
+
}
|
|
1075
|
+
get(name) {
|
|
1076
|
+
return this.agents.get(name);
|
|
1077
|
+
}
|
|
1078
|
+
list() {
|
|
1079
|
+
return [...this.agents.values()].sort((a, b) => a.info.name.localeCompare(b.info.name));
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1064
1083
|
// src/agents/factory.ts
|
|
1065
1084
|
function createAgent(config) {
|
|
1066
1085
|
if (config.integration === "process") {
|
|
@@ -1074,6 +1093,19 @@ function createAgent(config) {
|
|
|
1074
1093
|
}
|
|
1075
1094
|
throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
|
|
1076
1095
|
}
|
|
1096
|
+
function buildAgentRegistry(config, log = logger) {
|
|
1097
|
+
const registry = new AgentRegistry();
|
|
1098
|
+
for (const agentConfig of config.project.agents) {
|
|
1099
|
+
try {
|
|
1100
|
+
registry.register(createAgent(agentConfig));
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
log.warn(
|
|
1103
|
+
`Skipping agent "${agentConfig.name}": ${error instanceof Error ? error.message : String(error)}`
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return registry;
|
|
1108
|
+
}
|
|
1077
1109
|
|
|
1078
1110
|
// src/cli/commands/agents.ts
|
|
1079
1111
|
var collectArgs = (value, previous) => [...previous, value];
|
|
@@ -1179,19 +1211,305 @@ function registerRoleCommands(program) {
|
|
|
1179
1211
|
});
|
|
1180
1212
|
}
|
|
1181
1213
|
|
|
1214
|
+
// src/tasks/store.ts
|
|
1215
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
1216
|
+
import { dirname } from "path";
|
|
1217
|
+
var FileTaskStore = class {
|
|
1218
|
+
filePath;
|
|
1219
|
+
constructor(directory = `${process.cwd()}/.omninode`) {
|
|
1220
|
+
this.filePath = `${directory}/tasks.json`;
|
|
1221
|
+
}
|
|
1222
|
+
async save(task) {
|
|
1223
|
+
const tasks = await this.load();
|
|
1224
|
+
const index = tasks.findIndex((t) => t.id === task.id);
|
|
1225
|
+
if (index >= 0) tasks[index] = task;
|
|
1226
|
+
else tasks.push(task);
|
|
1227
|
+
await this.write(tasks);
|
|
1228
|
+
}
|
|
1229
|
+
async get(id) {
|
|
1230
|
+
const tasks = await this.load();
|
|
1231
|
+
return tasks.find((t) => t.id === id);
|
|
1232
|
+
}
|
|
1233
|
+
async list(filter = {}) {
|
|
1234
|
+
const tasks = await this.load();
|
|
1235
|
+
return tasks.filter((task) => filter.status ? task.status === filter.status : true).filter((task) => filter.project ? task.project === filter.project : true).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
1236
|
+
}
|
|
1237
|
+
async load() {
|
|
1238
|
+
try {
|
|
1239
|
+
const raw = await readFile(this.filePath, "utf8");
|
|
1240
|
+
const parsed = JSON.parse(raw);
|
|
1241
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1242
|
+
} catch {
|
|
1243
|
+
return [];
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async write(tasks) {
|
|
1247
|
+
const tmp = `${this.filePath}.tmp`;
|
|
1248
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
1249
|
+
await writeFile(tmp, `${JSON.stringify(tasks, null, 2)}
|
|
1250
|
+
`, "utf8");
|
|
1251
|
+
await rename(tmp, this.filePath);
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// src/tasks/engine.ts
|
|
1256
|
+
import { randomBytes } from "crypto";
|
|
1257
|
+
var ALLOWED_TRANSITIONS = {
|
|
1258
|
+
created: ["queued", "running", "cancelled"],
|
|
1259
|
+
queued: ["running", "cancelled"],
|
|
1260
|
+
running: ["completed", "failed", "cancelled"],
|
|
1261
|
+
waiting: ["running", "cancelled"],
|
|
1262
|
+
completed: [],
|
|
1263
|
+
failed: [],
|
|
1264
|
+
cancelled: []
|
|
1265
|
+
};
|
|
1266
|
+
var TaskEngine = class {
|
|
1267
|
+
constructor(options) {
|
|
1268
|
+
this.options = options;
|
|
1269
|
+
this.log = (options.log ?? logger).child({ component: "tasks" });
|
|
1270
|
+
}
|
|
1271
|
+
options;
|
|
1272
|
+
log;
|
|
1273
|
+
get store() {
|
|
1274
|
+
return this.options.store;
|
|
1275
|
+
}
|
|
1276
|
+
async create(input) {
|
|
1277
|
+
if (!input.objective || input.objective.trim().length === 0) {
|
|
1278
|
+
throw new TaskError("TASK_INVALID", "Task objective must not be empty.");
|
|
1279
|
+
}
|
|
1280
|
+
if (input.role && !this.options.roles.get(input.role)) {
|
|
1281
|
+
throw new TaskError(
|
|
1282
|
+
"TASK_INVALID",
|
|
1283
|
+
`Role "${input.role}" is not defined. Add it under project.roles in omninode.yaml.`
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
if (input.agent && !this.options.agents.get(input.agent)) {
|
|
1287
|
+
throw new TaskError(
|
|
1288
|
+
"TASK_INVALID",
|
|
1289
|
+
`Agent "${input.agent}" is not registered. Add it with \`omninode agent add\`.`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1293
|
+
const task = {
|
|
1294
|
+
id: generateTaskId(),
|
|
1295
|
+
objective: input.objective.trim(),
|
|
1296
|
+
status: "created",
|
|
1297
|
+
createdAt: now,
|
|
1298
|
+
updatedAt: now,
|
|
1299
|
+
...input.project !== void 0 ? { project: input.project } : {},
|
|
1300
|
+
...input.role !== void 0 ? { role: input.role } : {},
|
|
1301
|
+
...input.agent !== void 0 ? { agent: input.agent } : {},
|
|
1302
|
+
...input.context !== void 0 ? { context: input.context } : {}
|
|
1303
|
+
};
|
|
1304
|
+
await this.options.store.save(task);
|
|
1305
|
+
this.log.info(`Created task ${task.id}.`);
|
|
1306
|
+
return task;
|
|
1307
|
+
}
|
|
1308
|
+
async queue(id) {
|
|
1309
|
+
const task = await this.mustGet(id);
|
|
1310
|
+
return this.transition(task, "queued");
|
|
1311
|
+
}
|
|
1312
|
+
/** Runs the task through its assigned agent and records the result. */
|
|
1313
|
+
async run(id) {
|
|
1314
|
+
const task = await this.mustGet(id);
|
|
1315
|
+
if (task.status !== "created" && task.status !== "queued") {
|
|
1316
|
+
throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be run.`);
|
|
1317
|
+
}
|
|
1318
|
+
if (!task.agent) {
|
|
1319
|
+
throw new TaskError("TASK_INVALID", `Task ${id} has no agent assigned. Re-create it with --agent.`);
|
|
1320
|
+
}
|
|
1321
|
+
const agent = this.options.agents.get(task.agent);
|
|
1322
|
+
if (!agent) {
|
|
1323
|
+
throw new TaskError("TASK_INVALID", `Agent "${task.agent}" is not registered anymore.`);
|
|
1324
|
+
}
|
|
1325
|
+
const role = task.role ? this.options.roles.get(task.role) : void 0;
|
|
1326
|
+
const running = await this.transition(task, "running");
|
|
1327
|
+
this.log.info(`Running task ${task.id} on agent "${task.agent}".`);
|
|
1328
|
+
let output;
|
|
1329
|
+
try {
|
|
1330
|
+
output = await agent.run({
|
|
1331
|
+
taskId: running.id,
|
|
1332
|
+
objective: running.objective,
|
|
1333
|
+
...role ? { role } : {},
|
|
1334
|
+
...running.context?.background ? { context: running.context.background } : {}
|
|
1335
|
+
});
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1338
|
+
this.log.error(`Task ${task.id} failed: ${message}`);
|
|
1339
|
+
return this.transition(running, "failed", {
|
|
1340
|
+
result: { error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const result = {
|
|
1344
|
+
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
1345
|
+
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
1346
|
+
...output.error !== void 0 ? { error: output.error } : {},
|
|
1347
|
+
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1348
|
+
};
|
|
1349
|
+
const finalStatus = output.status === "completed" ? "completed" : "failed";
|
|
1350
|
+
this.log.info(`Task ${task.id} finished: ${finalStatus}.`);
|
|
1351
|
+
return this.transition(running, finalStatus, { result });
|
|
1352
|
+
}
|
|
1353
|
+
async cancel(id) {
|
|
1354
|
+
const task = await this.mustGet(id);
|
|
1355
|
+
if (!ALLOWED_TRANSITIONS[task.status].includes("cancelled")) {
|
|
1356
|
+
throw new TaskError("TASK_INVALID", `Task ${id} is "${task.status}" and cannot be cancelled.`);
|
|
1357
|
+
}
|
|
1358
|
+
if (task.agent) {
|
|
1359
|
+
const agent = this.options.agents.get(task.agent);
|
|
1360
|
+
await agent?.cancel?.(task.id);
|
|
1361
|
+
}
|
|
1362
|
+
return this.transition(task, "cancelled");
|
|
1363
|
+
}
|
|
1364
|
+
async get(id) {
|
|
1365
|
+
return this.options.store.get(id);
|
|
1366
|
+
}
|
|
1367
|
+
async list(filter) {
|
|
1368
|
+
return this.options.store.list(filter);
|
|
1369
|
+
}
|
|
1370
|
+
async mustGet(id) {
|
|
1371
|
+
const task = await this.options.store.get(id);
|
|
1372
|
+
if (!task) {
|
|
1373
|
+
throw new TaskError("TASK_NOT_FOUND", `Task "${id}" does not exist in this project's task store.`);
|
|
1374
|
+
}
|
|
1375
|
+
return task;
|
|
1376
|
+
}
|
|
1377
|
+
async transition(task, to, extra = {}) {
|
|
1378
|
+
const allowed = ALLOWED_TRANSITIONS[task.status] ?? [];
|
|
1379
|
+
if (!allowed.includes(to)) {
|
|
1380
|
+
throw new TaskError("TASK_INVALID", `Task ${task.id} cannot move from "${task.status}" to "${to}".`);
|
|
1381
|
+
}
|
|
1382
|
+
const updated = {
|
|
1383
|
+
...task,
|
|
1384
|
+
status: to,
|
|
1385
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1386
|
+
...extra.result !== void 0 ? { result: { ...task.result, ...extra.result } } : {}
|
|
1387
|
+
};
|
|
1388
|
+
await this.options.store.save(updated);
|
|
1389
|
+
return updated;
|
|
1390
|
+
}
|
|
1391
|
+
};
|
|
1392
|
+
function generateTaskId() {
|
|
1393
|
+
return `task-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/roles/index.ts
|
|
1397
|
+
var RoleRegistry = class {
|
|
1398
|
+
roles = /* @__PURE__ */ new Map();
|
|
1399
|
+
register(role) {
|
|
1400
|
+
this.roles.set(role.id, role);
|
|
1401
|
+
}
|
|
1402
|
+
registerAll(roles) {
|
|
1403
|
+
for (const role of roles) this.register(role);
|
|
1404
|
+
}
|
|
1405
|
+
get(id) {
|
|
1406
|
+
return this.roles.get(id);
|
|
1407
|
+
}
|
|
1408
|
+
list() {
|
|
1409
|
+
return [...this.roles.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
function buildRoleRegistry(config) {
|
|
1413
|
+
const registry = new RoleRegistry();
|
|
1414
|
+
registry.registerAll(config.project.roles);
|
|
1415
|
+
return registry;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// src/tasks/engine-factory.ts
|
|
1419
|
+
function createTaskEngine(config, log = logger, directory) {
|
|
1420
|
+
return new TaskEngine({
|
|
1421
|
+
agents: buildAgentRegistry(config, log),
|
|
1422
|
+
roles: buildRoleRegistry(config),
|
|
1423
|
+
store: new FileTaskStore(directory),
|
|
1424
|
+
log
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1182
1428
|
// src/cli/commands/tasks.ts
|
|
1429
|
+
var collectFiles = (value, previous) => [...previous, value];
|
|
1183
1430
|
function registerTaskCommands(program) {
|
|
1184
|
-
const task = program.command("task").description("Create, run and inspect tasks (
|
|
1185
|
-
task.command("create").description("Create a task (
|
|
1186
|
-
|
|
1431
|
+
const task = program.command("task").description("Create, run and inspect tasks (\xA713).");
|
|
1432
|
+
task.command("create <objective>").description("Create a task. Assign a role and/or agent; run it with `omninode task run`.").option("-r, --role <roleId>", "Role id from project.roles (validated at creation).").option("-a, --agent <name>", "Agent name from project.agents (validated at creation).").option("-f, --file <path>", "File or glob relevant to the task (repeatable).", collectFiles).action(async (objective, options) => {
|
|
1433
|
+
const config = loadConfig();
|
|
1434
|
+
const engine = createTaskEngine(config);
|
|
1435
|
+
const created = await engine.create({
|
|
1436
|
+
objective,
|
|
1437
|
+
project: config.project.name,
|
|
1438
|
+
...options.role !== void 0 ? { role: options.role } : {},
|
|
1439
|
+
...options.agent !== void 0 ? { agent: options.agent } : {},
|
|
1440
|
+
...options.file !== void 0 && options.file.length > 0 ? { context: { files: options.file } } : {}
|
|
1441
|
+
});
|
|
1442
|
+
console.log(`Created task ${created.id} (${created.status})`);
|
|
1443
|
+
console.log(` objective: ${created.objective}`);
|
|
1444
|
+
if (created.role) console.log(` role: ${created.role}`);
|
|
1445
|
+
if (created.agent) console.log(` agent: ${created.agent}`);
|
|
1446
|
+
if (created.context?.files) console.log(` files: ${created.context.files.join(", ")}`);
|
|
1447
|
+
console.log(`Run \`omninode task run ${created.id}\` to execute it.`);
|
|
1448
|
+
});
|
|
1449
|
+
task.command("run <task-id>").description("Run a task through its assigned agent.").action(async (taskId) => {
|
|
1450
|
+
const config = loadConfig();
|
|
1451
|
+
const engine = createTaskEngine(config);
|
|
1452
|
+
console.log(`Running task ${taskId}...`);
|
|
1453
|
+
const finished = await engine.run(taskId);
|
|
1454
|
+
printOutcome(finished);
|
|
1455
|
+
if (finished.status !== "completed") process.exitCode = 1;
|
|
1456
|
+
});
|
|
1457
|
+
task.command("status <task-id>").description("Show task details and current status.").action(async (taskId) => {
|
|
1458
|
+
const config = loadConfig();
|
|
1459
|
+
const engine = createTaskEngine(config);
|
|
1460
|
+
const found = await engine.get(taskId);
|
|
1461
|
+
if (!found) {
|
|
1462
|
+
throw new TaskError(
|
|
1463
|
+
"TASK_NOT_FOUND",
|
|
1464
|
+
`Task "${taskId}" does not exist in this project's task store.`
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
printOutcome(found, { verbose: true });
|
|
1187
1468
|
});
|
|
1188
|
-
task.command("
|
|
1189
|
-
|
|
1469
|
+
task.command("list").description("List tasks in the project's task store.").option("-s, --status <status>", "Filter by status (created, queued, running, completed, failed, cancelled).").action(async (options) => {
|
|
1470
|
+
const config = loadConfig();
|
|
1471
|
+
const engine = createTaskEngine(config);
|
|
1472
|
+
const tasks = await engine.list(
|
|
1473
|
+
options.status !== void 0 ? { status: options.status } : {}
|
|
1474
|
+
);
|
|
1475
|
+
if (tasks.length === 0) {
|
|
1476
|
+
console.log("No tasks found. Create one with `omninode task create`.");
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
for (const t of tasks) {
|
|
1480
|
+
console.log(`${t.id} ${t.status.padEnd(10)} ${truncate(t.objective, 60)}`);
|
|
1481
|
+
}
|
|
1190
1482
|
});
|
|
1191
|
-
task.command("
|
|
1192
|
-
|
|
1483
|
+
task.command("cancel <task-id>").description("Cancel a task that has not finished yet.").action(async (taskId) => {
|
|
1484
|
+
const config = loadConfig();
|
|
1485
|
+
const engine = createTaskEngine(config);
|
|
1486
|
+
const cancelled = await engine.cancel(taskId);
|
|
1487
|
+
console.log(`Task ${cancelled.id} is now "${cancelled.status}".`);
|
|
1193
1488
|
});
|
|
1194
1489
|
}
|
|
1490
|
+
function printOutcome(task, options = {}) {
|
|
1491
|
+
console.log(` id: ${task.id}`);
|
|
1492
|
+
console.log(` status: ${task.status}`);
|
|
1493
|
+
console.log(` created: ${task.createdAt}`);
|
|
1494
|
+
if (options.verbose) {
|
|
1495
|
+
console.log(` project: ${task.project ?? "(none)"}`);
|
|
1496
|
+
if (task.role) console.log(` role: ${task.role}`);
|
|
1497
|
+
if (task.agent) console.log(` agent: ${task.agent}`);
|
|
1498
|
+
if (task.context?.files) console.log(` files: ${task.context.files.join(", ")}`);
|
|
1499
|
+
console.log(` objective: ${task.objective}`);
|
|
1500
|
+
}
|
|
1501
|
+
const result = task.result;
|
|
1502
|
+
if (!result) return;
|
|
1503
|
+
if (result.summary) console.log(` summary: ${result.summary}`);
|
|
1504
|
+
if (result.reports && result.reports.length > 0) {
|
|
1505
|
+
console.log(` reports: ${result.reports.length} structured report(s)`);
|
|
1506
|
+
}
|
|
1507
|
+
if (result.error) console.log(` error: ${result.error}`);
|
|
1508
|
+
if (result.finishedAt) console.log(` ended: ${result.finishedAt}`);
|
|
1509
|
+
}
|
|
1510
|
+
function truncate(text, max) {
|
|
1511
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
1512
|
+
}
|
|
1195
1513
|
|
|
1196
1514
|
// src/cli/commands/reports.ts
|
|
1197
1515
|
function registerReportCommands(program) {
|