@task0/cli 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/dist/main.js +1439 -495
- package/package.json +12 -11
package/dist/main.js
CHANGED
|
@@ -9,27 +9,38 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// ../../packages/shared/dist/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import yaml2 from "js-yaml";
|
|
16
|
-
function readYaml(filePath) {
|
|
17
|
-
if (!fs2.existsSync(filePath))
|
|
18
|
-
return null;
|
|
19
|
-
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
20
|
-
return yaml2.load(raw);
|
|
21
|
-
}
|
|
22
|
-
function readContext(taskDir) {
|
|
23
|
-
const files = fs2.readdirSync(taskDir).filter((f) => f.endsWith(".md"));
|
|
24
|
-
if (files.length === 0)
|
|
25
|
-
return void 0;
|
|
26
|
-
const content = fs2.readFileSync(path2.join(taskDir, files[0]), "utf-8");
|
|
27
|
-
const stripped = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
28
|
-
return stripped.trim() || void 0;
|
|
12
|
+
// ../../packages/shared/dist/types/task.js
|
|
13
|
+
function isActiveTaskStatus(status) {
|
|
14
|
+
return ACTIVE_TASK_STATUS_SET.has(status);
|
|
29
15
|
}
|
|
30
|
-
var
|
|
31
|
-
|
|
16
|
+
var TASK_STATUS_VALUES, ACTIVE_TASK_STATUSES, ATTENTION_TASK_STATUSES, ACTIVE_TASK_STATUS_SET, ATTENTION_TASK_STATUS_SET;
|
|
17
|
+
var init_task = __esm({
|
|
18
|
+
"../../packages/shared/dist/types/task.js"() {
|
|
32
19
|
"use strict";
|
|
20
|
+
TASK_STATUS_VALUES = [
|
|
21
|
+
"todo",
|
|
22
|
+
"triaging",
|
|
23
|
+
"triaged",
|
|
24
|
+
"planning",
|
|
25
|
+
"planned",
|
|
26
|
+
"refining",
|
|
27
|
+
"refined",
|
|
28
|
+
"executing",
|
|
29
|
+
"blocked",
|
|
30
|
+
"done",
|
|
31
|
+
"archived"
|
|
32
|
+
];
|
|
33
|
+
ACTIVE_TASK_STATUSES = [
|
|
34
|
+
"triaging",
|
|
35
|
+
"planning",
|
|
36
|
+
"refining",
|
|
37
|
+
"executing"
|
|
38
|
+
];
|
|
39
|
+
ATTENTION_TASK_STATUSES = [
|
|
40
|
+
"blocked"
|
|
41
|
+
];
|
|
42
|
+
ACTIVE_TASK_STATUS_SET = new Set(ACTIVE_TASK_STATUSES);
|
|
43
|
+
ATTENTION_TASK_STATUS_SET = new Set(ATTENTION_TASK_STATUSES);
|
|
33
44
|
}
|
|
34
45
|
});
|
|
35
46
|
|
|
@@ -105,65 +116,57 @@ var init_object_id = __esm({
|
|
|
105
116
|
}
|
|
106
117
|
});
|
|
107
118
|
|
|
108
|
-
// ../../packages/shared/dist/
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
// ../../packages/shared/dist/node/yaml.js
|
|
120
|
+
import fs5 from "fs";
|
|
121
|
+
import path6 from "path";
|
|
122
|
+
import yaml3 from "js-yaml";
|
|
123
|
+
function readYaml(filePath) {
|
|
124
|
+
if (!fs5.existsSync(filePath))
|
|
125
|
+
return null;
|
|
126
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
127
|
+
return yaml3.load(raw);
|
|
111
128
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
function writeYaml(filePath, data) {
|
|
130
|
+
fs5.writeFileSync(filePath, yaml3.dump(data, { lineWidth: 120 }), "utf-8");
|
|
131
|
+
}
|
|
132
|
+
function readContext(taskDir) {
|
|
133
|
+
const files = fs5.readdirSync(taskDir).filter((f) => f.endsWith(".md"));
|
|
134
|
+
if (files.length === 0)
|
|
135
|
+
return void 0;
|
|
136
|
+
const content = fs5.readFileSync(path6.join(taskDir, files[0]), "utf-8");
|
|
137
|
+
const stripped = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
138
|
+
return stripped.trim() || void 0;
|
|
139
|
+
}
|
|
140
|
+
var init_yaml = __esm({
|
|
141
|
+
"../../packages/shared/dist/node/yaml.js"() {
|
|
115
142
|
"use strict";
|
|
116
|
-
TASK_STATUS_VALUES = [
|
|
117
|
-
"todo",
|
|
118
|
-
"triaging",
|
|
119
|
-
"triaged",
|
|
120
|
-
"planning",
|
|
121
|
-
"planned",
|
|
122
|
-
"refining",
|
|
123
|
-
"refined",
|
|
124
|
-
"executing",
|
|
125
|
-
"blocked",
|
|
126
|
-
"done",
|
|
127
|
-
"archived"
|
|
128
|
-
];
|
|
129
|
-
ACTIVE_TASK_STATUSES = [
|
|
130
|
-
"triaging",
|
|
131
|
-
"planning",
|
|
132
|
-
"refining",
|
|
133
|
-
"executing"
|
|
134
|
-
];
|
|
135
|
-
ATTENTION_TASK_STATUSES = [
|
|
136
|
-
"blocked"
|
|
137
|
-
];
|
|
138
|
-
ACTIVE_TASK_STATUS_SET = new Set(ACTIVE_TASK_STATUSES);
|
|
139
|
-
ATTENTION_TASK_STATUS_SET = new Set(ATTENTION_TASK_STATUSES);
|
|
140
143
|
}
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
// ../../packages/shared/dist/node/scanner.js
|
|
144
|
-
import
|
|
145
|
-
import
|
|
147
|
+
import fs6 from "fs";
|
|
148
|
+
import path7 from "path";
|
|
146
149
|
function scanProject(projectPath, sourceName) {
|
|
147
|
-
const absPath =
|
|
148
|
-
const resolvedSourceName = sourceName ??
|
|
150
|
+
const absPath = path7.resolve(projectPath);
|
|
151
|
+
const resolvedSourceName = sourceName ?? path7.basename(absPath);
|
|
149
152
|
const errors = [];
|
|
150
153
|
const tasks = [];
|
|
151
154
|
const repairs = [];
|
|
152
|
-
const projectYml =
|
|
155
|
+
const projectYml = path7.join(absPath, "task0.yml");
|
|
153
156
|
const projectConfig = readYaml(projectYml);
|
|
154
157
|
if (!projectConfig)
|
|
155
158
|
return { tasks, errors: [`${projectYml} not found`], repairs };
|
|
156
159
|
if (projectConfig.kind !== "project")
|
|
157
160
|
return { tasks, errors: [`Invalid: kind="${projectConfig.kind}"`], repairs };
|
|
158
|
-
const tasksDir =
|
|
159
|
-
if (!
|
|
161
|
+
const tasksDir = path7.join(absPath, projectConfig.tasks_dir);
|
|
162
|
+
if (!fs6.existsSync(tasksDir))
|
|
160
163
|
return { tasks, errors: [`tasks_dir not found: ${tasksDir}`], repairs };
|
|
161
|
-
const entries =
|
|
164
|
+
const entries = fs6.readdirSync(tasksDir, { withFileTypes: true });
|
|
162
165
|
for (const entry of entries) {
|
|
163
166
|
if (!entry.isDirectory())
|
|
164
167
|
continue;
|
|
165
|
-
const taskDir =
|
|
166
|
-
const taskYml =
|
|
168
|
+
const taskDir = path7.join(tasksDir, entry.name);
|
|
169
|
+
const taskYml = path7.join(taskDir, "task0.yml");
|
|
167
170
|
const raw = readYaml(taskYml);
|
|
168
171
|
if (!raw || raw.kind !== "task")
|
|
169
172
|
continue;
|
|
@@ -199,7 +202,7 @@ function scanProject(projectPath, sourceName) {
|
|
|
199
202
|
continue;
|
|
200
203
|
}
|
|
201
204
|
const context = readContext(taskDir);
|
|
202
|
-
const stat =
|
|
205
|
+
const stat = fs6.statSync(taskYml);
|
|
203
206
|
const tags = raw.tags || [];
|
|
204
207
|
const summary = raw.summary || void 0;
|
|
205
208
|
const displayTitle = summary?.title || title;
|
|
@@ -242,18 +245,18 @@ var init_scanner = __esm({
|
|
|
242
245
|
});
|
|
243
246
|
|
|
244
247
|
// ../../packages/shared/dist/node/open-questions.js
|
|
245
|
-
import
|
|
246
|
-
import
|
|
248
|
+
import fs7 from "fs";
|
|
249
|
+
import path8 from "path";
|
|
247
250
|
function readOpenQuestions(taskDir, issueFiles) {
|
|
248
251
|
const result = [];
|
|
249
252
|
for (const file of issueFiles) {
|
|
250
253
|
const m = file.match(/^ISSUE-(\d+)\.md$/);
|
|
251
254
|
if (!m)
|
|
252
255
|
continue;
|
|
253
|
-
const fullPath =
|
|
254
|
-
if (!
|
|
256
|
+
const fullPath = path8.join(taskDir, file);
|
|
257
|
+
if (!fs7.existsSync(fullPath))
|
|
255
258
|
continue;
|
|
256
|
-
const md =
|
|
259
|
+
const md = fs7.readFileSync(fullPath, "utf-8");
|
|
257
260
|
const match = md.match(OPEN_QUESTIONS_SECTION_RE);
|
|
258
261
|
if (!match)
|
|
259
262
|
continue;
|
|
@@ -276,34 +279,34 @@ var init_open_questions = __esm({
|
|
|
276
279
|
});
|
|
277
280
|
|
|
278
281
|
// ../../packages/shared/dist/node/task-state.js
|
|
279
|
-
import
|
|
280
|
-
import
|
|
281
|
-
import
|
|
282
|
-
import
|
|
282
|
+
import fs8 from "fs";
|
|
283
|
+
import os3 from "os";
|
|
284
|
+
import path9 from "path";
|
|
285
|
+
import yaml4 from "js-yaml";
|
|
283
286
|
function findProjectRoot(start = process.cwd()) {
|
|
284
|
-
let dir =
|
|
287
|
+
let dir = path9.resolve(start);
|
|
285
288
|
while (true) {
|
|
286
|
-
const ymlPath =
|
|
287
|
-
if (
|
|
289
|
+
const ymlPath = path9.join(dir, "task0.yml");
|
|
290
|
+
if (fs8.existsSync(ymlPath)) {
|
|
288
291
|
try {
|
|
289
|
-
const raw =
|
|
292
|
+
const raw = yaml4.load(fs8.readFileSync(ymlPath, "utf-8"));
|
|
290
293
|
if (raw?.kind === "project")
|
|
291
294
|
return dir;
|
|
292
295
|
} catch {
|
|
293
296
|
}
|
|
294
297
|
}
|
|
295
|
-
const parent =
|
|
298
|
+
const parent = path9.dirname(dir);
|
|
296
299
|
if (parent === dir)
|
|
297
300
|
return null;
|
|
298
301
|
dir = parent;
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
function readProjectConfig(projectRoot) {
|
|
302
|
-
return
|
|
305
|
+
return yaml4.load(fs8.readFileSync(path9.join(projectRoot, "task0.yml"), "utf-8"));
|
|
303
306
|
}
|
|
304
307
|
function resolveTasksDir(projectRoot, projectConfig) {
|
|
305
308
|
const cfg = projectConfig ?? readProjectConfig(projectRoot);
|
|
306
|
-
return
|
|
309
|
+
return path9.isAbsolute(cfg.tasks_dir) ? cfg.tasks_dir : path9.join(projectRoot, cfg.tasks_dir);
|
|
307
310
|
}
|
|
308
311
|
function resolveTaskByObjectId(objectId, projectRoot) {
|
|
309
312
|
const root = projectRoot ?? findProjectRoot();
|
|
@@ -314,16 +317,16 @@ function resolveTaskByObjectId(objectId, projectRoot) {
|
|
|
314
317
|
throw new Error(`Expected a task object_id like 'tsk_XXXXX', got '${objectId}'. Directory names are no longer accepted; run 'task0 task list' to find the object_id.`);
|
|
315
318
|
}
|
|
316
319
|
const tasksDir = resolveTasksDir(root);
|
|
317
|
-
const entries =
|
|
320
|
+
const entries = fs8.readdirSync(tasksDir, { withFileTypes: true });
|
|
318
321
|
for (const entry of entries) {
|
|
319
322
|
if (!entry.isDirectory())
|
|
320
323
|
continue;
|
|
321
|
-
const taskDir =
|
|
322
|
-
const taskYml =
|
|
323
|
-
if (!
|
|
324
|
+
const taskDir = path9.join(tasksDir, entry.name);
|
|
325
|
+
const taskYml = path9.join(taskDir, "task0.yml");
|
|
326
|
+
if (!fs8.existsSync(taskYml))
|
|
324
327
|
continue;
|
|
325
328
|
try {
|
|
326
|
-
const raw =
|
|
329
|
+
const raw = yaml4.load(fs8.readFileSync(taskYml, "utf-8"));
|
|
327
330
|
if (raw && raw.object_id === objectId) {
|
|
328
331
|
return { projectRoot: root, tasksDir, taskDir, taskYml };
|
|
329
332
|
}
|
|
@@ -333,15 +336,15 @@ function resolveTaskByObjectId(objectId, projectRoot) {
|
|
|
333
336
|
throw new Error(`No task with object_id '${objectId}' found under ${tasksDir}. If tasks pre-date object_id, run 'task0 task migrate' to seed them.`);
|
|
334
337
|
}
|
|
335
338
|
function readTaskYaml(taskYml) {
|
|
336
|
-
if (!
|
|
339
|
+
if (!fs8.existsSync(taskYml))
|
|
337
340
|
throw new Error(`task0.yml not found: ${taskYml}`);
|
|
338
|
-
return
|
|
341
|
+
return yaml4.load(fs8.readFileSync(taskYml, "utf-8")) || {};
|
|
339
342
|
}
|
|
340
343
|
function writeTaskYaml(taskYml, data) {
|
|
341
|
-
|
|
344
|
+
fs8.writeFileSync(taskYml, yaml4.dump(data, { lineWidth: 120 }), "utf-8");
|
|
342
345
|
}
|
|
343
346
|
function taskYamlLockPath(taskDir) {
|
|
344
|
-
return
|
|
347
|
+
return path9.join(taskDir, TASK_YAML_LOCKFILE);
|
|
345
348
|
}
|
|
346
349
|
function isProcessAlive(pid) {
|
|
347
350
|
try {
|
|
@@ -353,17 +356,17 @@ function isProcessAlive(pid) {
|
|
|
353
356
|
}
|
|
354
357
|
function readTaskYamlLockInfo(file) {
|
|
355
358
|
try {
|
|
356
|
-
return JSON.parse(
|
|
359
|
+
return JSON.parse(fs8.readFileSync(file, "utf-8"));
|
|
357
360
|
} catch {
|
|
358
361
|
return null;
|
|
359
362
|
}
|
|
360
363
|
}
|
|
361
364
|
function writeTaskYamlLockInfo(file, info) {
|
|
362
|
-
const fd =
|
|
365
|
+
const fd = fs8.openSync(file, "wx");
|
|
363
366
|
try {
|
|
364
|
-
|
|
367
|
+
fs8.writeFileSync(fd, JSON.stringify(info, null, 2), "utf-8");
|
|
365
368
|
} finally {
|
|
366
|
-
|
|
369
|
+
fs8.closeSync(fd);
|
|
367
370
|
}
|
|
368
371
|
}
|
|
369
372
|
function sleep(ms) {
|
|
@@ -373,7 +376,7 @@ async function acquireTaskYamlLock(taskDir) {
|
|
|
373
376
|
const file = taskYamlLockPath(taskDir);
|
|
374
377
|
const info = {
|
|
375
378
|
pid: process.pid,
|
|
376
|
-
hostname:
|
|
379
|
+
hostname: os3.hostname(),
|
|
377
380
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
378
381
|
};
|
|
379
382
|
const deadline = Date.now() + TASK_YAML_LOCK_TIMEOUT_MS;
|
|
@@ -389,7 +392,7 @@ async function acquireTaskYamlLock(taskDir) {
|
|
|
389
392
|
const existing = readTaskYamlLockInfo(file);
|
|
390
393
|
if (existing && existing.hostname === info.hostname && !isProcessAlive(existing.pid)) {
|
|
391
394
|
try {
|
|
392
|
-
|
|
395
|
+
fs8.unlinkSync(file);
|
|
393
396
|
continue;
|
|
394
397
|
} catch (err) {
|
|
395
398
|
if (err.code !== "ENOENT") {
|
|
@@ -411,7 +414,7 @@ function releaseTaskYamlLock(taskDir, info) {
|
|
|
411
414
|
return;
|
|
412
415
|
}
|
|
413
416
|
try {
|
|
414
|
-
|
|
417
|
+
fs8.unlinkSync(file);
|
|
415
418
|
} catch (err) {
|
|
416
419
|
if (err.code !== "ENOENT") {
|
|
417
420
|
throw err;
|
|
@@ -419,7 +422,7 @@ function releaseTaskYamlLock(taskDir, info) {
|
|
|
419
422
|
}
|
|
420
423
|
}
|
|
421
424
|
async function withTaskYamlLock(taskDir, fn) {
|
|
422
|
-
const key =
|
|
425
|
+
const key = path9.resolve(taskDir);
|
|
423
426
|
const prev = yamlLocks.get(key) ?? Promise.resolve();
|
|
424
427
|
const next = prev.then(async () => {
|
|
425
428
|
const lock = await acquireTaskYamlLock(taskDir);
|
|
@@ -450,7 +453,7 @@ function readTaskWorkflow(taskYml) {
|
|
|
450
453
|
return raw.workflow || {};
|
|
451
454
|
}
|
|
452
455
|
async function updateTaskWorkflow(taskYml, patch) {
|
|
453
|
-
const taskDir =
|
|
456
|
+
const taskDir = path9.dirname(taskYml);
|
|
454
457
|
return withTaskYamlLock(taskDir, () => {
|
|
455
458
|
const raw = readTaskYaml(taskYml);
|
|
456
459
|
const current = raw.workflow || {};
|
|
@@ -580,19 +583,19 @@ var init_redact = __esm({
|
|
|
580
583
|
});
|
|
581
584
|
|
|
582
585
|
// ../../packages/shared/dist/node/error-reports.js
|
|
583
|
-
import
|
|
584
|
-
import
|
|
585
|
-
import
|
|
586
|
+
import fs9 from "fs";
|
|
587
|
+
import os4 from "os";
|
|
588
|
+
import path10 from "path";
|
|
586
589
|
import { spawnSync } from "child_process";
|
|
587
590
|
import crypto from "crypto";
|
|
588
591
|
function task0Home() {
|
|
589
592
|
const override = process.env.TASK0_HOME;
|
|
590
593
|
if (override && override.length > 0)
|
|
591
594
|
return override;
|
|
592
|
-
return
|
|
595
|
+
return path10.join(os4.homedir(), ".task0");
|
|
593
596
|
}
|
|
594
597
|
function errorsRoot() {
|
|
595
|
-
return
|
|
598
|
+
return path10.join(task0Home(), "errors");
|
|
596
599
|
}
|
|
597
600
|
function createErrorReportId() {
|
|
598
601
|
return `err_${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -693,13 +696,13 @@ function buildErrorReport(input) {
|
|
|
693
696
|
function writeErrorReportSync(report, rootOverride) {
|
|
694
697
|
const root = rootOverride ?? errorsRoot();
|
|
695
698
|
const dirName = errorReportDirName(new Date(report.captured_at), report.id);
|
|
696
|
-
const dir =
|
|
697
|
-
|
|
698
|
-
const finalPath =
|
|
699
|
-
const tmpPath =
|
|
699
|
+
const dir = path10.join(root, dirName);
|
|
700
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
701
|
+
const finalPath = path10.join(dir, REPORT_FILENAME);
|
|
702
|
+
const tmpPath = path10.join(dir, TMP_FILENAME);
|
|
700
703
|
const json = JSON.stringify(report, null, 2);
|
|
701
|
-
|
|
702
|
-
|
|
704
|
+
fs9.writeFileSync(tmpPath, json, "utf-8");
|
|
705
|
+
fs9.renameSync(tmpPath, finalPath);
|
|
703
706
|
return { dir, path: finalPath };
|
|
704
707
|
}
|
|
705
708
|
function parseReportDir(name) {
|
|
@@ -716,7 +719,7 @@ function listErrorReports(rootOverride) {
|
|
|
716
719
|
};
|
|
717
720
|
let entries;
|
|
718
721
|
try {
|
|
719
|
-
entries =
|
|
722
|
+
entries = fs9.readdirSync(root, { withFileTypes: true });
|
|
720
723
|
} catch (err) {
|
|
721
724
|
if (err.code === "ENOENT")
|
|
722
725
|
return result;
|
|
@@ -728,11 +731,11 @@ function listErrorReports(rootOverride) {
|
|
|
728
731
|
const parsed = parseReportDir(entry.name);
|
|
729
732
|
if (!parsed)
|
|
730
733
|
continue;
|
|
731
|
-
const dir =
|
|
732
|
-
const file =
|
|
734
|
+
const dir = path10.join(root, entry.name);
|
|
735
|
+
const file = path10.join(dir, REPORT_FILENAME);
|
|
733
736
|
let raw;
|
|
734
737
|
try {
|
|
735
|
-
raw =
|
|
738
|
+
raw = fs9.readFileSync(file, "utf-8");
|
|
736
739
|
} catch {
|
|
737
740
|
result.skipped.unreadable += 1;
|
|
738
741
|
continue;
|
|
@@ -750,7 +753,7 @@ function listErrorReports(rootOverride) {
|
|
|
750
753
|
}
|
|
751
754
|
let size = 0;
|
|
752
755
|
try {
|
|
753
|
-
size =
|
|
756
|
+
size = fs9.statSync(file).size;
|
|
754
757
|
} catch {
|
|
755
758
|
}
|
|
756
759
|
result.reports.push({
|
|
@@ -785,7 +788,7 @@ function resolveErrorReport(query, rootOverride) {
|
|
|
785
788
|
return { kind: "miss", query };
|
|
786
789
|
}
|
|
787
790
|
function readErrorReport(summary) {
|
|
788
|
-
const raw =
|
|
791
|
+
const raw = fs9.readFileSync(summary.path, "utf-8");
|
|
789
792
|
return JSON.parse(raw);
|
|
790
793
|
}
|
|
791
794
|
function pruneErrorReports(opts, rootOverride) {
|
|
@@ -823,7 +826,7 @@ function pruneErrorReports(opts, rootOverride) {
|
|
|
823
826
|
}
|
|
824
827
|
function removeReportDir(dir) {
|
|
825
828
|
try {
|
|
826
|
-
|
|
829
|
+
fs9.rmSync(dir, { recursive: true, force: true });
|
|
827
830
|
return true;
|
|
828
831
|
} catch {
|
|
829
832
|
return false;
|
|
@@ -841,9 +844,9 @@ var init_error_reports = __esm({
|
|
|
841
844
|
});
|
|
842
845
|
|
|
843
846
|
// ../../packages/shared/dist/node/file-lock.js
|
|
844
|
-
import
|
|
845
|
-
import
|
|
846
|
-
import
|
|
847
|
+
import fs10 from "fs";
|
|
848
|
+
import os5 from "os";
|
|
849
|
+
import path11 from "path";
|
|
847
850
|
var init_file_lock = __esm({
|
|
848
851
|
"../../packages/shared/dist/node/file-lock.js"() {
|
|
849
852
|
"use strict";
|
|
@@ -858,6 +861,202 @@ var init_tmux = __esm({
|
|
|
858
861
|
}
|
|
859
862
|
});
|
|
860
863
|
|
|
864
|
+
// ../../packages/shared/dist/node/agent-skills.js
|
|
865
|
+
import fs11 from "fs";
|
|
866
|
+
import path12 from "path";
|
|
867
|
+
import yaml5 from "js-yaml";
|
|
868
|
+
function isRecord(value) {
|
|
869
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
870
|
+
}
|
|
871
|
+
function extractFrontmatter(raw) {
|
|
872
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
873
|
+
if (!match)
|
|
874
|
+
return null;
|
|
875
|
+
try {
|
|
876
|
+
const parsed = yaml5.load(match[1]);
|
|
877
|
+
return isRecord(parsed) ? parsed : null;
|
|
878
|
+
} catch {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function readTextIfExists(filePath) {
|
|
883
|
+
try {
|
|
884
|
+
if (!fs11.existsSync(filePath))
|
|
885
|
+
return null;
|
|
886
|
+
return fs11.readFileSync(filePath, "utf-8");
|
|
887
|
+
} catch {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function getSymlinkInfo(...candidatePaths) {
|
|
892
|
+
for (const candidatePath of candidatePaths) {
|
|
893
|
+
try {
|
|
894
|
+
const stat = fs11.lstatSync(candidatePath);
|
|
895
|
+
if (!stat.isSymbolicLink())
|
|
896
|
+
continue;
|
|
897
|
+
const target = fs11.readlinkSync(candidatePath);
|
|
898
|
+
return {
|
|
899
|
+
isSymlink: true,
|
|
900
|
+
symlinkTarget: path12.isAbsolute(target) ? target : path12.resolve(path12.dirname(candidatePath), target)
|
|
901
|
+
};
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return { isSymlink: false };
|
|
906
|
+
}
|
|
907
|
+
function normalizeString(value) {
|
|
908
|
+
return typeof value === "string" ? value.trim() : "";
|
|
909
|
+
}
|
|
910
|
+
function normalizeGlobs(value) {
|
|
911
|
+
if (typeof value === "string") {
|
|
912
|
+
const glob = value.trim();
|
|
913
|
+
return glob ? [glob] : void 0;
|
|
914
|
+
}
|
|
915
|
+
if (Array.isArray(value)) {
|
|
916
|
+
const globs = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
917
|
+
return globs.length > 0 ? globs : void 0;
|
|
918
|
+
}
|
|
919
|
+
return void 0;
|
|
920
|
+
}
|
|
921
|
+
function normalizeBoolean(value) {
|
|
922
|
+
if (typeof value === "boolean")
|
|
923
|
+
return value;
|
|
924
|
+
if (value === "true")
|
|
925
|
+
return true;
|
|
926
|
+
if (value === "false")
|
|
927
|
+
return false;
|
|
928
|
+
return void 0;
|
|
929
|
+
}
|
|
930
|
+
function sortSkills(skills) {
|
|
931
|
+
return skills.sort((a, b) => AGENT_ORDER[a.agent] - AGENT_ORDER[b.agent] || SCOPE_ORDER[a.scope] - SCOPE_ORDER[b.scope] || KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name) || a.filePath.localeCompare(b.filePath));
|
|
932
|
+
}
|
|
933
|
+
function pushInstructionIfExists(skills, agent2, scope, filePath, name = path12.basename(filePath), description2 = "", kind = "instruction") {
|
|
934
|
+
const raw = readTextIfExists(filePath);
|
|
935
|
+
if (raw === null)
|
|
936
|
+
return;
|
|
937
|
+
skills.push({
|
|
938
|
+
agent: agent2,
|
|
939
|
+
scope,
|
|
940
|
+
kind,
|
|
941
|
+
name,
|
|
942
|
+
description: description2,
|
|
943
|
+
filePath,
|
|
944
|
+
...getSymlinkInfo(filePath)
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
function scanClaudeSkillDir(skills, skillsDir, scope) {
|
|
948
|
+
let entries = [];
|
|
949
|
+
try {
|
|
950
|
+
if (!fs11.existsSync(skillsDir))
|
|
951
|
+
return;
|
|
952
|
+
entries = fs11.readdirSync(skillsDir, { withFileTypes: true });
|
|
953
|
+
} catch {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
for (const entry of entries) {
|
|
957
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
958
|
+
continue;
|
|
959
|
+
const skillDirPath = path12.join(skillsDir, entry.name);
|
|
960
|
+
const skillFilePath = path12.join(skillDirPath, "SKILL.md");
|
|
961
|
+
const raw = readTextIfExists(skillFilePath);
|
|
962
|
+
if (raw === null)
|
|
963
|
+
continue;
|
|
964
|
+
const frontmatter = extractFrontmatter(raw);
|
|
965
|
+
skills.push({
|
|
966
|
+
agent: "claude_code",
|
|
967
|
+
scope,
|
|
968
|
+
kind: "skill",
|
|
969
|
+
name: normalizeString(frontmatter?.name) || entry.name,
|
|
970
|
+
description: normalizeString(frontmatter?.description),
|
|
971
|
+
filePath: skillFilePath,
|
|
972
|
+
...getSymlinkInfo(skillDirPath, skillFilePath)
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function scanCursorRuleFile(skills, filePath, scope) {
|
|
977
|
+
const raw = readTextIfExists(filePath);
|
|
978
|
+
if (raw === null)
|
|
979
|
+
return;
|
|
980
|
+
const frontmatter = extractFrontmatter(raw);
|
|
981
|
+
const baseName = path12.basename(filePath, ".mdc");
|
|
982
|
+
skills.push({
|
|
983
|
+
agent: "cursor",
|
|
984
|
+
scope,
|
|
985
|
+
kind: "rule",
|
|
986
|
+
name: normalizeString(frontmatter?.name) || baseName,
|
|
987
|
+
description: normalizeString(frontmatter?.description),
|
|
988
|
+
filePath,
|
|
989
|
+
globs: normalizeGlobs(frontmatter?.globs),
|
|
990
|
+
alwaysApply: normalizeBoolean(frontmatter?.alwaysApply),
|
|
991
|
+
...getSymlinkInfo(filePath)
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
function scanCursorRulesDir(skills, rulesDir, scope) {
|
|
995
|
+
let entries = [];
|
|
996
|
+
try {
|
|
997
|
+
if (!fs11.existsSync(rulesDir))
|
|
998
|
+
return;
|
|
999
|
+
entries = fs11.readdirSync(rulesDir, { withFileTypes: true });
|
|
1000
|
+
} catch {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
for (const entry of entries) {
|
|
1004
|
+
if (!entry.name.endsWith(".mdc"))
|
|
1005
|
+
continue;
|
|
1006
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
1007
|
+
continue;
|
|
1008
|
+
scanCursorRuleFile(skills, path12.join(rulesDir, entry.name), scope);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function getProjectAgentSkills(projectPath) {
|
|
1012
|
+
const absProjectPath = path12.resolve(projectPath);
|
|
1013
|
+
let projectStat;
|
|
1014
|
+
try {
|
|
1015
|
+
projectStat = fs11.statSync(absProjectPath);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return [];
|
|
1018
|
+
}
|
|
1019
|
+
if (!projectStat.isDirectory())
|
|
1020
|
+
return [];
|
|
1021
|
+
const skills = [];
|
|
1022
|
+
scanClaudeSkillDir(skills, path12.join(absProjectPath, ".claude", "skills"), "project");
|
|
1023
|
+
pushInstructionIfExists(skills, "claude_code", "project", path12.join(absProjectPath, "CLAUDE.md"));
|
|
1024
|
+
pushInstructionIfExists(skills, "claude_code", "project", path12.join(absProjectPath, "AGENTS.md"));
|
|
1025
|
+
pushInstructionIfExists(skills, "codex", "project", path12.join(absProjectPath, "AGENTS.md"));
|
|
1026
|
+
scanCursorRulesDir(skills, path12.join(absProjectPath, ".cursor", "rules"), "project");
|
|
1027
|
+
pushInstructionIfExists(skills, "cursor", "project", path12.join(absProjectPath, ".cursorrules"), "Legacy Cursor Rules", "", "rule");
|
|
1028
|
+
return sortSkills(skills);
|
|
1029
|
+
}
|
|
1030
|
+
function getGlobalAgentSkills() {
|
|
1031
|
+
const homeDir = process.env.HOME;
|
|
1032
|
+
if (!homeDir)
|
|
1033
|
+
return [];
|
|
1034
|
+
const skills = [];
|
|
1035
|
+
scanClaudeSkillDir(skills, path12.join(homeDir, ".claude", "skills"), "global");
|
|
1036
|
+
scanCursorRulesDir(skills, path12.join(homeDir, ".cursor", "rules"), "global");
|
|
1037
|
+
return sortSkills(skills);
|
|
1038
|
+
}
|
|
1039
|
+
var AGENT_ORDER, SCOPE_ORDER, KIND_ORDER;
|
|
1040
|
+
var init_agent_skills = __esm({
|
|
1041
|
+
"../../packages/shared/dist/node/agent-skills.js"() {
|
|
1042
|
+
"use strict";
|
|
1043
|
+
AGENT_ORDER = {
|
|
1044
|
+
claude_code: 0,
|
|
1045
|
+
codex: 1,
|
|
1046
|
+
cursor: 2
|
|
1047
|
+
};
|
|
1048
|
+
SCOPE_ORDER = {
|
|
1049
|
+
project: 0,
|
|
1050
|
+
global: 1
|
|
1051
|
+
};
|
|
1052
|
+
KIND_ORDER = {
|
|
1053
|
+
skill: 0,
|
|
1054
|
+
rule: 1,
|
|
1055
|
+
instruction: 2
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
|
|
861
1060
|
// ../../packages/shared/dist/node/index.js
|
|
862
1061
|
var init_node = __esm({
|
|
863
1062
|
"../../packages/shared/dist/node/index.js"() {
|
|
@@ -870,6 +1069,7 @@ var init_node = __esm({
|
|
|
870
1069
|
init_redact();
|
|
871
1070
|
init_file_lock();
|
|
872
1071
|
init_tmux();
|
|
1072
|
+
init_agent_skills();
|
|
873
1073
|
init_error_report();
|
|
874
1074
|
}
|
|
875
1075
|
});
|
|
@@ -889,7 +1089,7 @@ __export(task_state_exports, {
|
|
|
889
1089
|
withTaskYamlLock: () => withTaskYamlLock,
|
|
890
1090
|
writeTaskYaml: () => writeTaskYaml
|
|
891
1091
|
});
|
|
892
|
-
import
|
|
1092
|
+
import fs12 from "fs";
|
|
893
1093
|
function readWorkflow(taskYml) {
|
|
894
1094
|
return readTaskWorkflow(taskYml);
|
|
895
1095
|
}
|
|
@@ -898,14 +1098,14 @@ async function updateWorkflow(taskYml, patch) {
|
|
|
898
1098
|
}
|
|
899
1099
|
function nextArtifactIndex(taskDir, prefix, ext = "md") {
|
|
900
1100
|
const pattern = new RegExp(`^${prefix}-(\\d+).*\\.${ext}$`);
|
|
901
|
-
const entries =
|
|
1101
|
+
const entries = fs12.readdirSync(taskDir);
|
|
902
1102
|
const indices = entries.map((name) => name.match(pattern)?.[1]).filter((v) => v != null).map(Number);
|
|
903
1103
|
const next = indices.length > 0 ? Math.max(...indices) + 1 : 1;
|
|
904
1104
|
return String(next).padStart(2, "0");
|
|
905
1105
|
}
|
|
906
1106
|
function latestArtifact(taskDir, pattern) {
|
|
907
|
-
if (!
|
|
908
|
-
const matches =
|
|
1107
|
+
if (!fs12.existsSync(taskDir)) return null;
|
|
1108
|
+
const matches = fs12.readdirSync(taskDir).filter((name) => pattern.test(name));
|
|
909
1109
|
if (matches.length === 0) return null;
|
|
910
1110
|
matches.sort();
|
|
911
1111
|
return matches[matches.length - 1] || null;
|
|
@@ -918,153 +1118,292 @@ var init_task_state2 = __esm({
|
|
|
918
1118
|
});
|
|
919
1119
|
|
|
920
1120
|
// src/main.ts
|
|
921
|
-
import { readFileSync } from "fs";
|
|
922
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
923
|
-
import path25 from "path";
|
|
924
1121
|
import { Command as Command23 } from "commander";
|
|
925
1122
|
|
|
926
1123
|
// src/commands/source.ts
|
|
927
1124
|
import { Command } from "commander";
|
|
928
|
-
import
|
|
1125
|
+
import path3 from "path";
|
|
929
1126
|
import chalk from "chalk";
|
|
930
1127
|
|
|
931
|
-
// src/
|
|
1128
|
+
// src/types.ts
|
|
1129
|
+
init_task();
|
|
1130
|
+
|
|
1131
|
+
// src/core/admin-token.ts
|
|
932
1132
|
import fs from "fs";
|
|
1133
|
+
import os from "os";
|
|
933
1134
|
import path from "path";
|
|
934
|
-
|
|
935
|
-
var
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1135
|
+
var CONFIG_DIR = path.join(os.homedir(), ".config", "task0");
|
|
1136
|
+
var TOKEN_FILE = path.join(CONFIG_DIR, "admin.token");
|
|
1137
|
+
var cached = null;
|
|
1138
|
+
var AdminTokenUnavailableError = class extends Error {
|
|
1139
|
+
constructor() {
|
|
1140
|
+
super(
|
|
1141
|
+
`Admin token not found.
|
|
1142
|
+
\u2022 If the task0 server runs on this host, run the task0-server binary once \u2014 the token will be generated at ${TOKEN_FILE}.
|
|
1143
|
+
\u2022 Otherwise, copy the token from the server host and set TASK0_ADMIN_TOKEN.`
|
|
1144
|
+
);
|
|
1145
|
+
this.name = "AdminTokenUnavailableError";
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
function readAdminToken() {
|
|
1149
|
+
if (cached) return cached;
|
|
1150
|
+
const fromEnv = process.env.TASK0_ADMIN_TOKEN?.trim();
|
|
1151
|
+
if (fromEnv) {
|
|
1152
|
+
cached = fromEnv;
|
|
1153
|
+
return cached;
|
|
1154
|
+
}
|
|
1155
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
1156
|
+
const v = fs.readFileSync(TOKEN_FILE, "utf-8").trim();
|
|
1157
|
+
if (v) {
|
|
1158
|
+
cached = v;
|
|
1159
|
+
return cached;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
throw new AdminTokenUnavailableError();
|
|
943
1163
|
}
|
|
944
|
-
function
|
|
945
|
-
return {
|
|
1164
|
+
function adminAuthHeader() {
|
|
1165
|
+
return { authorization: `Bearer ${readAdminToken()}` };
|
|
946
1166
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1167
|
+
|
|
1168
|
+
// src/core/daemon-config.ts
|
|
1169
|
+
import fs2 from "fs";
|
|
1170
|
+
import os2 from "os";
|
|
1171
|
+
import path2 from "path";
|
|
1172
|
+
var CONFIG_DIR2 = path2.join(os2.homedir(), ".config", "task0");
|
|
1173
|
+
var CONFIG_FILE = path2.join(CONFIG_DIR2, "daemon.json");
|
|
1174
|
+
function daemonConfigPath() {
|
|
1175
|
+
return CONFIG_FILE;
|
|
955
1176
|
}
|
|
956
|
-
function
|
|
957
|
-
|
|
958
|
-
|
|
1177
|
+
function readDaemonIdentity() {
|
|
1178
|
+
if (!fs2.existsSync(CONFIG_FILE)) return null;
|
|
1179
|
+
try {
|
|
1180
|
+
const raw = fs2.readFileSync(CONFIG_FILE, "utf-8");
|
|
1181
|
+
return JSON.parse(raw);
|
|
1182
|
+
} catch {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
959
1185
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
}
|
|
966
|
-
config.sources.push(entry);
|
|
1186
|
+
function writeDaemonIdentity(identity) {
|
|
1187
|
+
fs2.mkdirSync(CONFIG_DIR2, { recursive: true });
|
|
1188
|
+
fs2.writeFileSync(CONFIG_FILE, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
1189
|
+
try {
|
|
1190
|
+
fs2.chmodSync(CONFIG_FILE, 384);
|
|
1191
|
+
} catch {
|
|
967
1192
|
}
|
|
968
|
-
saveConfig(config);
|
|
969
1193
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
if (idx < 0) return false;
|
|
974
|
-
config.sources.splice(idx, 1);
|
|
975
|
-
saveConfig(config);
|
|
1194
|
+
function clearDaemonIdentity() {
|
|
1195
|
+
if (!fs2.existsSync(CONFIG_FILE)) return false;
|
|
1196
|
+
fs2.unlinkSync(CONFIG_FILE);
|
|
976
1197
|
return true;
|
|
977
1198
|
}
|
|
978
|
-
function getSource(name) {
|
|
979
|
-
return loadConfig().sources.find((s) => s.name === name);
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// src/commands/source.ts
|
|
983
|
-
init_node();
|
|
984
1199
|
|
|
985
|
-
// src/
|
|
986
|
-
|
|
1200
|
+
// src/core/hub-client.ts
|
|
1201
|
+
var HubUnreachableError = class extends Error {
|
|
1202
|
+
constructor(url, cause) {
|
|
1203
|
+
super(`Cannot reach hub at ${url}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
1204
|
+
this.name = "HubUnreachableError";
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
var HubResponseError = class extends Error {
|
|
1208
|
+
status;
|
|
1209
|
+
code;
|
|
1210
|
+
body;
|
|
1211
|
+
constructor(status, body, code) {
|
|
1212
|
+
super(`Hub returned ${status}: ${body}`);
|
|
1213
|
+
this.name = "HubResponseError";
|
|
1214
|
+
this.status = status;
|
|
1215
|
+
this.code = code;
|
|
1216
|
+
this.body = body;
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
function resolveHubUrl() {
|
|
1220
|
+
const fromEnv = process.env.TASK0_API_URL?.trim();
|
|
1221
|
+
if (fromEnv) return fromEnv.replace(/\/$/, "");
|
|
1222
|
+
const identity = readDaemonIdentity();
|
|
1223
|
+
if (identity?.server_url) return identity.server_url.replace(/\/$/, "");
|
|
1224
|
+
return "http://127.0.0.1:4318";
|
|
1225
|
+
}
|
|
1226
|
+
function resolveAuthHeader() {
|
|
1227
|
+
const apiToken = process.env.TASK0_API_TOKEN?.trim();
|
|
1228
|
+
if (apiToken) return { authorization: `Bearer ${apiToken}` };
|
|
1229
|
+
try {
|
|
1230
|
+
return adminAuthHeader();
|
|
1231
|
+
} catch (error2) {
|
|
1232
|
+
if (error2 instanceof AdminTokenUnavailableError) {
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
`No CLI credential available. Set TASK0_API_TOKEN (preferred \u2014 issue one via the dashboard) or place a server admin token at ${error2.message.includes(".token") ? error2.message.split("at ")[1]?.split(".")[0] + ".token" : "~/.config/task0/admin.token"}.`
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
throw error2;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
async function callHub(pathname, opts = {}) {
|
|
1241
|
+
const base = resolveHubUrl();
|
|
1242
|
+
const url = new URL(pathname.startsWith("/") ? pathname : `/${pathname}`, base + "/");
|
|
1243
|
+
for (const [k, v] of Object.entries(opts.query ?? {})) {
|
|
1244
|
+
if (v !== void 0) url.searchParams.set(k, v);
|
|
1245
|
+
}
|
|
1246
|
+
const init = {
|
|
1247
|
+
method: opts.method ?? "GET",
|
|
1248
|
+
headers: {
|
|
1249
|
+
...resolveAuthHeader(),
|
|
1250
|
+
...opts.body !== void 0 ? { "content-type": "application/json" } : {}
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
|
|
1254
|
+
if (opts.timeoutMs) init.signal = AbortSignal.timeout(opts.timeoutMs);
|
|
1255
|
+
let res;
|
|
1256
|
+
try {
|
|
1257
|
+
res = await fetch(url, init);
|
|
1258
|
+
} catch (error2) {
|
|
1259
|
+
throw new HubUnreachableError(url.toString(), error2);
|
|
1260
|
+
}
|
|
1261
|
+
if (!res.ok) {
|
|
1262
|
+
const body = await res.text().catch(() => "");
|
|
1263
|
+
let code = null;
|
|
1264
|
+
try {
|
|
1265
|
+
const parsed = JSON.parse(body);
|
|
1266
|
+
if (typeof parsed?.code === "string") code = parsed.code;
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
throw new HubResponseError(res.status, body, code);
|
|
1270
|
+
}
|
|
1271
|
+
if (res.status === 204) return void 0;
|
|
1272
|
+
const text = await res.text();
|
|
1273
|
+
if (!text) return void 0;
|
|
1274
|
+
return JSON.parse(text);
|
|
1275
|
+
}
|
|
1276
|
+
function localDaemonId() {
|
|
1277
|
+
const identity = readDaemonIdentity();
|
|
1278
|
+
return identity?.daemon_id ?? null;
|
|
1279
|
+
}
|
|
987
1280
|
|
|
988
1281
|
// src/commands/source.ts
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1282
|
+
function reportHubError(error2) {
|
|
1283
|
+
if (error2 instanceof HubUnreachableError) {
|
|
1284
|
+
console.error(chalk.red(error2.message));
|
|
1285
|
+
console.error(chalk.dim("Tip: ensure the hub is running and TASK0_API_URL points to it."));
|
|
1286
|
+
process.exit(1);
|
|
1287
|
+
}
|
|
1288
|
+
if (error2 instanceof HubResponseError) {
|
|
1289
|
+
console.error(chalk.red(`Hub error (${error2.status}): ${error2.body || "(empty body)"}`));
|
|
996
1290
|
process.exit(1);
|
|
997
1291
|
}
|
|
998
|
-
|
|
1292
|
+
throw error2;
|
|
1293
|
+
}
|
|
1294
|
+
function requireLocalDaemon() {
|
|
1295
|
+
const id = localDaemonId();
|
|
1296
|
+
if (!id) {
|
|
1297
|
+
console.error(chalk.red("This host is not registered as a daemon."));
|
|
1298
|
+
console.error(chalk.dim("Run `task0 daemon register --server <url>` first."));
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
return id;
|
|
1302
|
+
}
|
|
1303
|
+
var source = new Command("source").description("Manage task sources");
|
|
1304
|
+
source.command("add <path>").description("Register a local project on this host's daemon (via hub)").option("-n, --name <name>", "Source name (defaults to directory name)").action(async (inputPath, opts) => {
|
|
1305
|
+
const absPath = path3.resolve(inputPath);
|
|
1306
|
+
const name = opts.name || path3.basename(absPath);
|
|
1307
|
+
const daemonId = requireLocalDaemon();
|
|
1308
|
+
let resp;
|
|
1309
|
+
try {
|
|
1310
|
+
resp = await callHub(`/api/daemons/${encodeURIComponent(daemonId)}/projects`, {
|
|
1311
|
+
method: "POST",
|
|
1312
|
+
body: { path: absPath, name }
|
|
1313
|
+
});
|
|
1314
|
+
} catch (error2) {
|
|
1315
|
+
reportHubError(error2);
|
|
1316
|
+
}
|
|
999
1317
|
console.log(chalk.green(`Added source "${name}" \u2192 ${absPath}`));
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
for (const err of result.errors) console.warn(chalk.yellow(` warn: ${err}`));
|
|
1318
|
+
if (typeof resp.taskCount === "number") {
|
|
1319
|
+
console.log(` ${resp.taskCount} tasks found`);
|
|
1003
1320
|
}
|
|
1004
1321
|
});
|
|
1005
|
-
source.command("list").description("List registered task sources").action(() => {
|
|
1006
|
-
|
|
1007
|
-
|
|
1322
|
+
source.command("list").description("List registered task sources (queries the hub)").action(async () => {
|
|
1323
|
+
let data;
|
|
1324
|
+
try {
|
|
1325
|
+
data = await callHub("/api/config");
|
|
1326
|
+
} catch (error2) {
|
|
1327
|
+
if (error2 instanceof HubUnreachableError) {
|
|
1328
|
+
console.error(chalk.red(error2.message));
|
|
1329
|
+
console.error(chalk.dim("Tip: ensure the hub is running and TASK0_API_URL points to it."));
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
if (error2 instanceof HubResponseError) {
|
|
1333
|
+
console.error(chalk.red(`Hub error (${error2.status}): ${error2.body || "(empty body)"}`));
|
|
1334
|
+
process.exit(1);
|
|
1335
|
+
}
|
|
1336
|
+
throw error2;
|
|
1337
|
+
}
|
|
1338
|
+
if (data.sources.length === 0) {
|
|
1008
1339
|
console.log("No sources registered. Use `task0 source add <path>` to add one.");
|
|
1009
1340
|
return;
|
|
1010
1341
|
}
|
|
1011
|
-
for (const s of
|
|
1342
|
+
for (const s of data.sources) {
|
|
1012
1343
|
const status = s.enabled ? chalk.green("\u25CF") : chalk.dim("\u25CB");
|
|
1013
|
-
|
|
1344
|
+
const where = s.path ?? (s.type === "github" ? "(github)" : s.type === "linear" ? "(linear)" : "");
|
|
1345
|
+
const owner = s.daemon_id ? chalk.dim(` @${s.daemon_id}`) : "";
|
|
1346
|
+
console.log(`${status} ${s.name} ${chalk.dim(s.type)} ${where}${owner}`);
|
|
1014
1347
|
}
|
|
1015
1348
|
});
|
|
1016
|
-
source.command("remove <name>").description("Remove a
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
const config = loadConfig();
|
|
1026
|
-
const sources = name ? (() => {
|
|
1027
|
-
const s = getSource(name);
|
|
1028
|
-
if (!s) {
|
|
1029
|
-
console.error(chalk.red(`Source "${name}" not found`));
|
|
1349
|
+
source.command("remove <name>").description("Remove a project source from this host's daemon (via hub)").action(async (name) => {
|
|
1350
|
+
const daemonId = requireLocalDaemon();
|
|
1351
|
+
try {
|
|
1352
|
+
await callHub(`/api/daemons/${encodeURIComponent(daemonId)}/projects/${encodeURIComponent(name)}`, {
|
|
1353
|
+
method: "DELETE"
|
|
1354
|
+
});
|
|
1355
|
+
} catch (error2) {
|
|
1356
|
+
if (error2 instanceof HubResponseError && error2.status === 404) {
|
|
1357
|
+
console.error(chalk.red(`Source "${name}" not found on this daemon.`));
|
|
1030
1358
|
process.exit(1);
|
|
1031
1359
|
}
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1360
|
+
reportHubError(error2);
|
|
1361
|
+
}
|
|
1362
|
+
console.log(chalk.green(`Removed source "${name}"`));
|
|
1363
|
+
});
|
|
1364
|
+
source.command("scan [name]").description("Scan source(s) via the hub and display tasks").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
1365
|
+
let data;
|
|
1366
|
+
try {
|
|
1367
|
+
data = await callHub("/api/tasks", { query: { source: name } });
|
|
1368
|
+
} catch (error2) {
|
|
1369
|
+
reportHubError(error2);
|
|
1370
|
+
}
|
|
1371
|
+
if (data.tasks.length === 0) {
|
|
1372
|
+
console.log(name ? `No tasks in source "${name}".` : "No tasks.");
|
|
1036
1373
|
return;
|
|
1037
1374
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1375
|
+
if (opts.json) {
|
|
1376
|
+
const out = data.tasks.map((t) => ({ ...t, _source: t.project }));
|
|
1377
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
const byProject = /* @__PURE__ */ new Map();
|
|
1381
|
+
for (const t of data.tasks) {
|
|
1382
|
+
const list = byProject.get(t.project) ?? [];
|
|
1383
|
+
list.push(t);
|
|
1384
|
+
byProject.set(t.project, list);
|
|
1385
|
+
}
|
|
1386
|
+
for (const [project2, tasks] of byProject) {
|
|
1387
|
+
console.log(chalk.bold(`
|
|
1388
|
+
${project2}`));
|
|
1389
|
+
const objectIdWidth = Math.max(...tasks.map((t) => (t.object_id || "").length), 9);
|
|
1390
|
+
for (const t of tasks) {
|
|
1391
|
+
const statusColor2 = isActiveTaskStatus(t.status) ? chalk.green : t.status === "blocked" ? chalk.red : t.status === "todo" ? chalk.yellow : t.status === "done" ? chalk.dim : chalk.white;
|
|
1392
|
+
const objectId = chalk.cyan((t.object_id || "-").padEnd(objectIdWidth));
|
|
1393
|
+
console.log(` ${objectId} ${statusColor2(t.status.padEnd(8))} ${chalk.dim(t.id)} ${t.title}`);
|
|
1056
1394
|
}
|
|
1395
|
+
console.log(chalk.dim(` ${tasks.length} tasks`));
|
|
1057
1396
|
}
|
|
1058
|
-
if (
|
|
1059
|
-
console.
|
|
1397
|
+
if (data.errors.length > 0) {
|
|
1398
|
+
for (const err of data.errors) console.warn(chalk.yellow(`warn: ${err}`));
|
|
1060
1399
|
}
|
|
1061
1400
|
});
|
|
1062
1401
|
|
|
1063
1402
|
// src/commands/project.ts
|
|
1064
1403
|
import { Command as Command2 } from "commander";
|
|
1065
|
-
import
|
|
1066
|
-
import
|
|
1067
|
-
import
|
|
1404
|
+
import fs3 from "fs";
|
|
1405
|
+
import path4 from "path";
|
|
1406
|
+
import yaml from "js-yaml";
|
|
1068
1407
|
import chalk2 from "chalk";
|
|
1069
1408
|
|
|
1070
1409
|
// ../../packages/shared/dist/index.js
|
|
@@ -1072,44 +1411,45 @@ init_object_id();
|
|
|
1072
1411
|
|
|
1073
1412
|
// src/commands/project.ts
|
|
1074
1413
|
var project = new Command2("project").description("Manage projects");
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (!fs8.existsSync(ymlPath)) return "-";
|
|
1414
|
+
project.command("list").description("List registered projects (queries the hub)").action(async () => {
|
|
1415
|
+
let data;
|
|
1078
1416
|
try {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1417
|
+
data = await callHub("/api/config");
|
|
1418
|
+
} catch (error2) {
|
|
1419
|
+
if (error2 instanceof HubUnreachableError) {
|
|
1420
|
+
console.error(chalk2.red(error2.message));
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
}
|
|
1423
|
+
if (error2 instanceof HubResponseError) {
|
|
1424
|
+
console.error(chalk2.red(`Hub error (${error2.status}): ${error2.body || "(empty body)"}`));
|
|
1425
|
+
process.exit(1);
|
|
1426
|
+
}
|
|
1427
|
+
throw error2;
|
|
1084
1428
|
}
|
|
1085
|
-
|
|
1086
|
-
project.command("list").description("List registered projects").action(() => {
|
|
1087
|
-
const projects = loadConfig().sources.filter((s) => s.type === "project");
|
|
1429
|
+
const projects = data.sources.filter((s) => s.type === "project");
|
|
1088
1430
|
if (projects.length === 0) {
|
|
1089
1431
|
console.log("No projects registered. Use `task0 source add <path>` to add one.");
|
|
1090
1432
|
return;
|
|
1091
1433
|
}
|
|
1092
|
-
const
|
|
1093
|
-
const
|
|
1094
|
-
const nameWidth = Math.max(...rows.map((r) => r.p.name.length), 4);
|
|
1095
|
-
for (const { p, objectId } of rows) {
|
|
1434
|
+
const nameWidth = Math.max(...projects.map((p) => p.name.length), 4);
|
|
1435
|
+
for (const p of projects) {
|
|
1096
1436
|
const status = p.enabled ? chalk2.green("\u25CF") : chalk2.dim("\u25CB");
|
|
1097
|
-
const oid = chalk2.cyan(objectId.padEnd(objectIdWidth));
|
|
1098
1437
|
const name = p.name.padEnd(nameWidth);
|
|
1099
|
-
|
|
1438
|
+
const owner = p.daemon_id ? chalk2.dim(` @${p.daemon_id}`) : "";
|
|
1439
|
+
console.log(`${status} ${name} ${chalk2.dim(p.path ?? "")}${owner}`);
|
|
1100
1440
|
}
|
|
1101
1441
|
});
|
|
1102
1442
|
project.command("init").description("Initialize task0.yml in the current directory").option("-d, --tasks-dir <dir>", "Tasks directory", ".task0/tasks").action((opts) => {
|
|
1103
1443
|
const cwd = process.cwd();
|
|
1104
|
-
const ymlPath =
|
|
1105
|
-
if (
|
|
1444
|
+
const ymlPath = path4.join(cwd, "task0.yml");
|
|
1445
|
+
if (fs3.existsSync(ymlPath)) {
|
|
1106
1446
|
console.error(chalk2.yellow("task0.yml already exists"));
|
|
1107
1447
|
process.exit(1);
|
|
1108
1448
|
}
|
|
1109
1449
|
const config = { kind: "project", object_id: generateObjectId("project"), tasks_dir: opts.tasksDir };
|
|
1110
|
-
|
|
1111
|
-
const tasksDir =
|
|
1112
|
-
|
|
1450
|
+
fs3.writeFileSync(ymlPath, yaml.dump(config), "utf-8");
|
|
1451
|
+
const tasksDir = path4.join(cwd, opts.tasksDir);
|
|
1452
|
+
fs3.mkdirSync(tasksDir, { recursive: true });
|
|
1113
1453
|
console.log(chalk2.green("Initialized task0 project"));
|
|
1114
1454
|
console.log(` ${ymlPath}`);
|
|
1115
1455
|
console.log(` ${tasksDir}/`);
|
|
@@ -1118,61 +1458,57 @@ project.command("init").description("Initialize task0.yml in the current directo
|
|
|
1118
1458
|
// src/commands/task.ts
|
|
1119
1459
|
import { Command as Command8 } from "commander";
|
|
1120
1460
|
import { execSync } from "child_process";
|
|
1121
|
-
import
|
|
1122
|
-
import
|
|
1123
|
-
import
|
|
1461
|
+
import fs17 from "fs";
|
|
1462
|
+
import path15 from "path";
|
|
1463
|
+
import yaml6 from "js-yaml";
|
|
1124
1464
|
import chalk8 from "chalk";
|
|
1125
1465
|
|
|
1126
1466
|
// src/lib/api.ts
|
|
1127
|
-
var DEFAULT_BASE = "http://127.0.0.1:4318";
|
|
1128
1467
|
function apiBaseUrl() {
|
|
1129
|
-
return process.env.TASK0_API_URL ||
|
|
1468
|
+
return process.env.TASK0_API_URL || "http://127.0.0.1:4318";
|
|
1130
1469
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
let res;
|
|
1134
|
-
try {
|
|
1135
|
-
res = await fetch(url, {
|
|
1136
|
-
method,
|
|
1137
|
-
headers: body !== void 0 ? { "content-type": "application/json" } : void 0,
|
|
1138
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
1139
|
-
});
|
|
1140
|
-
} catch (error2) {
|
|
1470
|
+
function toApiError(method, pathname, error2) {
|
|
1471
|
+
if (error2 instanceof HubUnreachableError) {
|
|
1141
1472
|
const err = new Error(
|
|
1142
1473
|
`Cannot reach task0 API at ${apiBaseUrl()}. Start the task0-server binary (download from GitHub Releases) or repoint TASK0_API_URL.`
|
|
1143
1474
|
);
|
|
1144
1475
|
err.cause = error2;
|
|
1145
|
-
|
|
1476
|
+
return err;
|
|
1146
1477
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1478
|
+
if (error2 instanceof HubResponseError) {
|
|
1479
|
+
let parsedBody = error2.body;
|
|
1480
|
+
try {
|
|
1481
|
+
parsedBody = error2.body ? JSON.parse(error2.body) : null;
|
|
1482
|
+
} catch {
|
|
1483
|
+
}
|
|
1484
|
+
const message = parsedBody?.error || error2.body || error2.message;
|
|
1485
|
+
const err = new Error(`API ${method} ${pathname} failed (${error2.status}): ${message}`);
|
|
1486
|
+
err.status = error2.status;
|
|
1487
|
+
err.body = parsedBody;
|
|
1488
|
+
return err;
|
|
1153
1489
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1490
|
+
return error2 instanceof Error ? error2 : new Error(String(error2));
|
|
1491
|
+
}
|
|
1492
|
+
async function request(method, pathname, body) {
|
|
1493
|
+
try {
|
|
1494
|
+
return await callHub(pathname, { method, body });
|
|
1495
|
+
} catch (error2) {
|
|
1496
|
+
throw toApiError(method, pathname, error2);
|
|
1160
1497
|
}
|
|
1161
|
-
return parsed;
|
|
1162
1498
|
}
|
|
1163
1499
|
var api = {
|
|
1164
|
-
get: (
|
|
1165
|
-
post: (
|
|
1166
|
-
put: (
|
|
1167
|
-
patch: (
|
|
1168
|
-
del: (
|
|
1500
|
+
get: (path29) => request("GET", path29),
|
|
1501
|
+
post: (path29, body) => request("POST", path29, body ?? {}),
|
|
1502
|
+
put: (path29, body) => request("PUT", path29, body ?? {}),
|
|
1503
|
+
patch: (path29, body) => request("PATCH", path29, body ?? {}),
|
|
1504
|
+
del: (path29) => request("DELETE", path29)
|
|
1169
1505
|
};
|
|
1170
1506
|
|
|
1171
1507
|
// src/commands/task/triage.ts
|
|
1172
1508
|
import { Command as Command3 } from "commander";
|
|
1173
1509
|
import chalk3 from "chalk";
|
|
1174
|
-
import
|
|
1175
|
-
import
|
|
1510
|
+
import fs13 from "fs";
|
|
1511
|
+
import path13 from "path";
|
|
1176
1512
|
|
|
1177
1513
|
// src/core/agent-run-wait.ts
|
|
1178
1514
|
async function getAgentRun(id) {
|
|
@@ -1196,6 +1532,54 @@ async function waitForAgentRun(id, opts = {}) {
|
|
|
1196
1532
|
}
|
|
1197
1533
|
}
|
|
1198
1534
|
|
|
1535
|
+
// src/core/config.ts
|
|
1536
|
+
import fs4 from "fs";
|
|
1537
|
+
import path5 from "path";
|
|
1538
|
+
import yaml2 from "js-yaml";
|
|
1539
|
+
var CONFIG_DIR3 = path5.join(
|
|
1540
|
+
process.env.HOME || process.env.USERPROFILE || "~",
|
|
1541
|
+
".config",
|
|
1542
|
+
"task0"
|
|
1543
|
+
);
|
|
1544
|
+
var CONFIG_FILE2 = path5.join(CONFIG_DIR3, "config.yml");
|
|
1545
|
+
function ensureConfigDir() {
|
|
1546
|
+
fs4.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
1547
|
+
}
|
|
1548
|
+
function defaultConfig() {
|
|
1549
|
+
return { sources: [] };
|
|
1550
|
+
}
|
|
1551
|
+
function loadConfig() {
|
|
1552
|
+
if (!fs4.existsSync(CONFIG_FILE2)) return defaultConfig();
|
|
1553
|
+
const raw = fs4.readFileSync(CONFIG_FILE2, "utf-8");
|
|
1554
|
+
const data = yaml2.load(raw);
|
|
1555
|
+
return {
|
|
1556
|
+
...data ?? {},
|
|
1557
|
+
sources: Array.isArray(data?.sources) ? data.sources : []
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function saveConfig(config) {
|
|
1561
|
+
ensureConfigDir();
|
|
1562
|
+
fs4.writeFileSync(CONFIG_FILE2, yaml2.dump(config, { lineWidth: 120 }), "utf-8");
|
|
1563
|
+
}
|
|
1564
|
+
function addSource(entry) {
|
|
1565
|
+
const config = loadConfig();
|
|
1566
|
+
const existing = config.sources.findIndex((s) => s.name === entry.name);
|
|
1567
|
+
if (existing >= 0) {
|
|
1568
|
+
config.sources[existing] = entry;
|
|
1569
|
+
} else {
|
|
1570
|
+
config.sources.push(entry);
|
|
1571
|
+
}
|
|
1572
|
+
saveConfig(config);
|
|
1573
|
+
}
|
|
1574
|
+
function removeSource(name) {
|
|
1575
|
+
const config = loadConfig();
|
|
1576
|
+
const idx = config.sources.findIndex((s) => s.name === name);
|
|
1577
|
+
if (idx < 0) return false;
|
|
1578
|
+
config.sources.splice(idx, 1);
|
|
1579
|
+
saveConfig(config);
|
|
1580
|
+
return true;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1199
1583
|
// src/core/model-options.ts
|
|
1200
1584
|
function resolveModelOptions(agent2, cli, warn = () => {
|
|
1201
1585
|
}) {
|
|
@@ -1223,8 +1607,8 @@ init_task_state2();
|
|
|
1223
1607
|
var ISSUE_DETAIL_RE = /^ISSUE-\d+\.md$/;
|
|
1224
1608
|
var TRIAGE_SKILL_NAME = "triage";
|
|
1225
1609
|
function resolveSkillFilePath(projectRoot, skillName) {
|
|
1226
|
-
const p =
|
|
1227
|
-
return
|
|
1610
|
+
const p = path13.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
1611
|
+
return fs13.existsSync(p) ? p : null;
|
|
1228
1612
|
}
|
|
1229
1613
|
var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md + ISSUE-NN.md").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--agent <name>", "Agent (claude-code|codex)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--idea <file>", "IDEA file (default: latest IDEA-NN.md)").option("--force", "Overwrite existing ISSUE files").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
1230
1614
|
try {
|
|
@@ -1243,7 +1627,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1243
1627
|
process.exit(1);
|
|
1244
1628
|
}
|
|
1245
1629
|
for (const name of existingIssues) {
|
|
1246
|
-
|
|
1630
|
+
fs13.rmSync(path13.join(loc.taskDir, name), { force: true });
|
|
1247
1631
|
}
|
|
1248
1632
|
}
|
|
1249
1633
|
if (opts.model || opts.effort) {
|
|
@@ -1281,8 +1665,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1281
1665
|
console.error(chalk3.red(`triage failed: ${final.error || "unknown"}`));
|
|
1282
1666
|
process.exit(1);
|
|
1283
1667
|
}
|
|
1284
|
-
const hasOverview =
|
|
1285
|
-
const issueFiles =
|
|
1668
|
+
const hasOverview = fs13.existsSync(path13.join(loc.taskDir, "ISSUE.md"));
|
|
1669
|
+
const issueFiles = fs13.readdirSync(loc.taskDir).filter((name) => ISSUE_DETAIL_RE.test(name)).sort();
|
|
1286
1670
|
if (!hasOverview || issueFiles.length === 0) {
|
|
1287
1671
|
const missing = [];
|
|
1288
1672
|
if (!hasOverview) missing.push("ISSUE.md");
|
|
@@ -1292,7 +1676,7 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1292
1676
|
}
|
|
1293
1677
|
let blockingQuestionCount = 0;
|
|
1294
1678
|
for (const name of issueFiles) {
|
|
1295
|
-
const content =
|
|
1679
|
+
const content = fs13.readFileSync(path13.join(loc.taskDir, name), "utf-8");
|
|
1296
1680
|
blockingQuestionCount += countBlockingQuestions(content);
|
|
1297
1681
|
}
|
|
1298
1682
|
await updateWorkflow(loc.taskYml, {
|
|
@@ -1320,8 +1704,8 @@ var triage = new Command3("triage").description("Decompose IDEA into ISSUE.md +
|
|
|
1320
1704
|
}
|
|
1321
1705
|
});
|
|
1322
1706
|
function listIssueArtifacts(taskDir) {
|
|
1323
|
-
if (!
|
|
1324
|
-
return
|
|
1707
|
+
if (!fs13.existsSync(taskDir)) return [];
|
|
1708
|
+
return fs13.readdirSync(taskDir).filter((name) => name === "ISSUE.md" || ISSUE_DETAIL_RE.test(name)).sort();
|
|
1325
1709
|
}
|
|
1326
1710
|
function countBlockingQuestions(md) {
|
|
1327
1711
|
const match = md.match(/## Open Questions\s*\n([\s\S]*?)(\n## |\n*$)/i);
|
|
@@ -1335,13 +1719,13 @@ function countBlockingQuestions(md) {
|
|
|
1335
1719
|
// src/commands/task/exec.ts
|
|
1336
1720
|
import { Command as Command4 } from "commander";
|
|
1337
1721
|
import chalk4 from "chalk";
|
|
1338
|
-
import
|
|
1339
|
-
import
|
|
1722
|
+
import fs14 from "fs";
|
|
1723
|
+
import path14 from "path";
|
|
1340
1724
|
init_task_state2();
|
|
1341
1725
|
var PLAN_EXECUTE_SKILL_NAME = "plan-execute";
|
|
1342
1726
|
function resolveSkillFilePath2(projectRoot, skillName) {
|
|
1343
|
-
const p =
|
|
1344
|
-
return
|
|
1727
|
+
const p = path14.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
1728
|
+
return fs14.existsSync(p) ? p : null;
|
|
1345
1729
|
}
|
|
1346
1730
|
var exec = new Command4("exec").description("Execute a plan against the task (cwd = project root; agent sets up its own worktree if needed)").argument("<objectId>", "Task object_id (tsk_XXXXX)").option("--agent <name>", "Agent (claude-code|codex|cursor)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--plan <file>", "Plan file (default: refined plan, else latest PLAN)").option("--no-commit", "Skip commit").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
1347
1731
|
try {
|
|
@@ -1434,7 +1818,7 @@ var summarize = new Command5("summarize").description("Generate a concise title
|
|
|
1434
1818
|
});
|
|
1435
1819
|
|
|
1436
1820
|
// src/commands/task/comment.ts
|
|
1437
|
-
import
|
|
1821
|
+
import fs15 from "fs";
|
|
1438
1822
|
import { Command as Command6 } from "commander";
|
|
1439
1823
|
import chalk6 from "chalk";
|
|
1440
1824
|
var comment = new Command6("comment").description("Manage comments on a task");
|
|
@@ -1444,8 +1828,8 @@ function fail(err) {
|
|
|
1444
1828
|
}
|
|
1445
1829
|
function readBodyFromOpts(opts) {
|
|
1446
1830
|
if (opts.body !== void 0) return opts.body;
|
|
1447
|
-
if (opts.file === "-") return
|
|
1448
|
-
if (opts.file) return
|
|
1831
|
+
if (opts.file === "-") return fs15.readFileSync(0, "utf-8");
|
|
1832
|
+
if (opts.file) return fs15.readFileSync(opts.file, "utf-8");
|
|
1449
1833
|
throw new Error("Provide --body or --file");
|
|
1450
1834
|
}
|
|
1451
1835
|
function preview(body, width = 60) {
|
|
@@ -1544,7 +1928,7 @@ comment.command("delete <cmtId>").description("Delete a comment by its cmt_ id")
|
|
|
1544
1928
|
});
|
|
1545
1929
|
|
|
1546
1930
|
// src/commands/task/description.ts
|
|
1547
|
-
import
|
|
1931
|
+
import fs16 from "fs";
|
|
1548
1932
|
import { Command as Command7 } from "commander";
|
|
1549
1933
|
import chalk7 from "chalk";
|
|
1550
1934
|
var description = new Command7("description").description("Show or update the task description");
|
|
@@ -1554,8 +1938,8 @@ function fail2(err) {
|
|
|
1554
1938
|
}
|
|
1555
1939
|
function readBodyFromOpts2(opts) {
|
|
1556
1940
|
if (opts.body !== void 0) return opts.body;
|
|
1557
|
-
if (opts.file === "-") return
|
|
1558
|
-
if (opts.file) return
|
|
1941
|
+
if (opts.file === "-") return fs16.readFileSync(0, "utf-8");
|
|
1942
|
+
if (opts.file) return fs16.readFileSync(opts.file, "utf-8");
|
|
1559
1943
|
throw new Error("Provide --body or --file (use --file - to read stdin)");
|
|
1560
1944
|
}
|
|
1561
1945
|
description.command("show <taskId>").description("Print the current description (taskId is short id or tsk_)").option("--json", "Output JSON").action(async (taskId, opts) => {
|
|
@@ -1604,12 +1988,12 @@ task.addCommand(comment);
|
|
|
1604
1988
|
task.addCommand(description);
|
|
1605
1989
|
task.command("init <input>").description("Create a task from a description or Linear/GitHub issue URL").action(async (input) => {
|
|
1606
1990
|
const cwd = process.cwd();
|
|
1607
|
-
const projectYml =
|
|
1608
|
-
if (!
|
|
1991
|
+
const projectYml = path15.join(cwd, "task0.yml");
|
|
1992
|
+
if (!fs17.existsSync(projectYml)) {
|
|
1609
1993
|
console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
|
|
1610
1994
|
process.exit(1);
|
|
1611
1995
|
}
|
|
1612
|
-
const projectConfig =
|
|
1996
|
+
const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
|
|
1613
1997
|
if (projectConfig.kind !== "project") {
|
|
1614
1998
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
1615
1999
|
process.exit(1);
|
|
@@ -1625,38 +2009,38 @@ task.command("init <input>").description("Create a task from a description or Li
|
|
|
1625
2009
|
});
|
|
1626
2010
|
task.command("migrate").description("Add task0.yml to legacy task directories that lack one").option("--dry-run", "Show what would be created without writing").action((opts) => {
|
|
1627
2011
|
const cwd = process.cwd();
|
|
1628
|
-
const projectYml =
|
|
1629
|
-
if (!
|
|
2012
|
+
const projectYml = path15.join(cwd, "task0.yml");
|
|
2013
|
+
if (!fs17.existsSync(projectYml)) {
|
|
1630
2014
|
console.error(chalk8.red("Not a task0 project (task0.yml not found). Run `task0 project init` first."));
|
|
1631
2015
|
process.exit(1);
|
|
1632
2016
|
}
|
|
1633
|
-
const projectConfig =
|
|
2017
|
+
const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
|
|
1634
2018
|
if (projectConfig.kind !== "project") {
|
|
1635
2019
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
1636
2020
|
process.exit(1);
|
|
1637
2021
|
}
|
|
1638
|
-
const tasksDir =
|
|
1639
|
-
if (!
|
|
2022
|
+
const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
|
|
2023
|
+
if (!fs17.existsSync(tasksDir)) {
|
|
1640
2024
|
console.error(chalk8.red(`Tasks directory not found: ${tasksDir}`));
|
|
1641
2025
|
process.exit(1);
|
|
1642
2026
|
}
|
|
1643
|
-
const entries =
|
|
2027
|
+
const entries = fs17.readdirSync(tasksDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1644
2028
|
let migrated = 0;
|
|
1645
2029
|
let skipped = 0;
|
|
1646
2030
|
let seededObjectIds = 0;
|
|
1647
2031
|
for (const name of entries) {
|
|
1648
|
-
const taskDir =
|
|
1649
|
-
const taskYml =
|
|
1650
|
-
if (
|
|
2032
|
+
const taskDir = path15.join(tasksDir, name);
|
|
2033
|
+
const taskYml = path15.join(taskDir, "task0.yml");
|
|
2034
|
+
if (fs17.existsSync(taskYml)) {
|
|
1651
2035
|
skipped++;
|
|
1652
2036
|
try {
|
|
1653
|
-
const raw =
|
|
2037
|
+
const raw = yaml6.load(fs17.readFileSync(taskYml, "utf-8"));
|
|
1654
2038
|
if (raw && raw.kind === "task" && !raw.object_id) {
|
|
1655
2039
|
raw.object_id = generateObjectId("task");
|
|
1656
2040
|
if (opts.dryRun) {
|
|
1657
2041
|
console.log(chalk8.dim(`[dry-run] seed object_id: ${taskYml}`));
|
|
1658
2042
|
} else {
|
|
1659
|
-
|
|
2043
|
+
fs17.writeFileSync(taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
1660
2044
|
console.log(chalk8.green(` seed object_id: ${taskYml}`));
|
|
1661
2045
|
}
|
|
1662
2046
|
seededObjectIds++;
|
|
@@ -1681,7 +2065,7 @@ task.command("migrate").description("Add task0.yml to legacy task directories th
|
|
|
1681
2065
|
if (opts.dryRun) {
|
|
1682
2066
|
console.log(chalk8.dim(`[dry-run] ${taskYml}`));
|
|
1683
2067
|
} else {
|
|
1684
|
-
|
|
2068
|
+
fs17.writeFileSync(taskYml, yaml6.dump(taskConfig), "utf-8");
|
|
1685
2069
|
console.log(chalk8.green(` ${taskYml}`));
|
|
1686
2070
|
}
|
|
1687
2071
|
migrated++;
|
|
@@ -1741,7 +2125,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
1741
2125
|
try {
|
|
1742
2126
|
const { resolveTaskByObjectId: resolveTaskByObjectId2 } = await Promise.resolve().then(() => (init_task_state2(), task_state_exports));
|
|
1743
2127
|
const loc = resolveTaskByObjectId2(id);
|
|
1744
|
-
const raw =
|
|
2128
|
+
const raw = yaml6.load(fs17.readFileSync(loc.taskYml, "utf-8"));
|
|
1745
2129
|
if (opts.phase) {
|
|
1746
2130
|
const phase = opts.phase.trim();
|
|
1747
2131
|
if (!phase) {
|
|
@@ -1754,7 +2138,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
1754
2138
|
return;
|
|
1755
2139
|
}
|
|
1756
2140
|
raw.workflow = { ...workflow2, phase };
|
|
1757
|
-
|
|
2141
|
+
fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
1758
2142
|
console.log(chalk8.green(`${id} phase: ${phase}`));
|
|
1759
2143
|
return;
|
|
1760
2144
|
}
|
|
@@ -1765,7 +2149,7 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
1765
2149
|
raw.status = "done";
|
|
1766
2150
|
const workflow = raw.workflow ?? {};
|
|
1767
2151
|
raw.workflow = { ...workflow, phase: "completed" };
|
|
1768
|
-
|
|
2152
|
+
fs17.writeFileSync(loc.taskYml, yaml6.dump(raw, { lineWidth: 120 }), "utf-8");
|
|
1769
2153
|
console.log(chalk8.green(`${id} marked as done`));
|
|
1770
2154
|
} catch (err) {
|
|
1771
2155
|
console.error(chalk8.red(err.message));
|
|
@@ -1774,33 +2158,33 @@ task.command("done <id>").description("Write task lifecycle state to task0.yml \
|
|
|
1774
2158
|
});
|
|
1775
2159
|
task.command("archive <id>").description("Archive a task (append to tasks.tar, remove from tasks/)").action((id) => {
|
|
1776
2160
|
const cwd = process.cwd();
|
|
1777
|
-
const projectYml =
|
|
1778
|
-
if (!
|
|
2161
|
+
const projectYml = path15.join(cwd, "task0.yml");
|
|
2162
|
+
if (!fs17.existsSync(projectYml)) {
|
|
1779
2163
|
console.error(chalk8.red("Not a task0 project (task0.yml not found)."));
|
|
1780
2164
|
process.exit(1);
|
|
1781
2165
|
}
|
|
1782
|
-
const projectConfig =
|
|
2166
|
+
const projectConfig = yaml6.load(fs17.readFileSync(projectYml, "utf-8"));
|
|
1783
2167
|
if (projectConfig.kind !== "project") {
|
|
1784
2168
|
console.error(chalk8.red('Invalid task0.yml: kind is not "project"'));
|
|
1785
2169
|
process.exit(1);
|
|
1786
2170
|
}
|
|
1787
|
-
const tasksDir =
|
|
1788
|
-
const taskDir =
|
|
1789
|
-
if (!
|
|
2171
|
+
const tasksDir = path15.join(cwd, projectConfig.tasks_dir);
|
|
2172
|
+
const taskDir = path15.join(tasksDir, id);
|
|
2173
|
+
if (!fs17.existsSync(taskDir)) {
|
|
1790
2174
|
console.error(chalk8.red(`Task "${id}" not found at ${taskDir}`));
|
|
1791
2175
|
process.exit(1);
|
|
1792
2176
|
}
|
|
1793
2177
|
const tarFile = tasksDir + ".tar";
|
|
1794
2178
|
archiveTaskToTar(tasksDir, id, tarFile);
|
|
1795
|
-
|
|
2179
|
+
fs17.rmSync(taskDir, { recursive: true });
|
|
1796
2180
|
console.log(chalk8.green(`Archived "${id}" \u2192 ${tarFile}`));
|
|
1797
2181
|
});
|
|
1798
2182
|
function archiveTaskToTar(tasksDir, taskId, tarFile) {
|
|
1799
|
-
if (
|
|
2183
|
+
if (fs17.existsSync(tarFile) && !fs17.statSync(tarFile).isFile()) {
|
|
1800
2184
|
console.error(chalk8.red(`${tarFile} exists but is not a file (likely a leftover directory). Remove it manually first.`));
|
|
1801
2185
|
process.exit(1);
|
|
1802
2186
|
}
|
|
1803
|
-
if (
|
|
2187
|
+
if (fs17.existsSync(tarFile)) {
|
|
1804
2188
|
execSync(`tar -rf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
|
|
1805
2189
|
} else {
|
|
1806
2190
|
execSync(`tar -cf ${JSON.stringify(tarFile)} -C ${JSON.stringify(tasksDir)} ${JSON.stringify(taskId)}`);
|
|
@@ -1810,9 +2194,9 @@ function archiveTaskToTar(tasksDir, taskId, tarFile) {
|
|
|
1810
2194
|
// src/commands/ui.ts
|
|
1811
2195
|
import { Command as Command9 } from "commander";
|
|
1812
2196
|
import { spawn } from "child_process";
|
|
1813
|
-
import
|
|
2197
|
+
import path16 from "path";
|
|
1814
2198
|
import chalk9 from "chalk";
|
|
1815
|
-
var DASHBOARD_DIR =
|
|
2199
|
+
var DASHBOARD_DIR = path16.resolve(
|
|
1816
2200
|
import.meta.dirname,
|
|
1817
2201
|
"..",
|
|
1818
2202
|
"..",
|
|
@@ -2093,12 +2477,12 @@ models.command("default <agent>").description("Get or set default model / effort
|
|
|
2093
2477
|
});
|
|
2094
2478
|
|
|
2095
2479
|
// src/commands/agent-run.ts
|
|
2096
|
-
import
|
|
2097
|
-
import
|
|
2480
|
+
import fs18 from "fs";
|
|
2481
|
+
import path17 from "path";
|
|
2098
2482
|
import { Command as Command11 } from "commander";
|
|
2099
2483
|
import chalk11 from "chalk";
|
|
2100
2484
|
import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
|
|
2101
|
-
import
|
|
2485
|
+
import yaml7 from "js-yaml";
|
|
2102
2486
|
var agentRun = new Command11("agent-run").description("Inspect and control agent runs");
|
|
2103
2487
|
agentRun.command("list").description("List active agent runs").option("--json", "Output JSON").option("--task <id>", "Filter by task id").action(async (opts) => {
|
|
2104
2488
|
try {
|
|
@@ -2222,7 +2606,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
|
|
|
2222
2606
|
console.log(formatProfile(result.runtime));
|
|
2223
2607
|
console.log();
|
|
2224
2608
|
console.log(chalk11.dim("--- exec ---"));
|
|
2225
|
-
console.log(
|
|
2609
|
+
console.log(yaml7.dump(result.runtime.exec, { lineWidth: 100 }));
|
|
2226
2610
|
} catch (err) {
|
|
2227
2611
|
const apiErr = err;
|
|
2228
2612
|
if (apiErr.status === 404) failProfile(`not found: ${ref}`);
|
|
@@ -2232,7 +2616,7 @@ profile.command("get <ref>").description("Show one runtime profile by slug or ob
|
|
|
2232
2616
|
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
2617
|
let parsed;
|
|
2234
2618
|
try {
|
|
2235
|
-
parsed =
|
|
2619
|
+
parsed = yaml7.load(fs18.readFileSync(path17.resolve(opts.fromFile), "utf-8"));
|
|
2236
2620
|
} catch (err) {
|
|
2237
2621
|
failProfile(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
2238
2622
|
}
|
|
@@ -2248,14 +2632,14 @@ profile.command("edit <ref>").description("Open the runtime profile YAML in $EDI
|
|
|
2248
2632
|
try {
|
|
2249
2633
|
const result = await api.get(`/api/runtime-profiles/${encodeURIComponent(ref)}`);
|
|
2250
2634
|
if (result.runtime.system) failProfile("cannot edit a system runtime profile");
|
|
2251
|
-
const tmp =
|
|
2252
|
-
|
|
2635
|
+
const tmp = path17.join(process.env.TMPDIR || "/tmp", `task0-runtime-${result.runtime.slug}-${Date.now()}.yml`);
|
|
2636
|
+
fs18.writeFileSync(tmp, yaml7.dump(result.runtime, { lineWidth: 100 }), "utf-8");
|
|
2253
2637
|
const editor = process.env.EDITOR || "vi";
|
|
2254
2638
|
const r = spawnSync3(editor, [tmp], { stdio: "inherit" });
|
|
2255
2639
|
if (r.status !== 0) failProfile(`editor exited with status ${r.status}`);
|
|
2256
|
-
const updated =
|
|
2640
|
+
const updated = yaml7.load(fs18.readFileSync(tmp, "utf-8"));
|
|
2257
2641
|
await api.put(`/api/runtime-profiles/${encodeURIComponent(ref)}`, updated);
|
|
2258
|
-
|
|
2642
|
+
fs18.unlinkSync(tmp);
|
|
2259
2643
|
console.log(chalk11.green(`updated ${ref}`));
|
|
2260
2644
|
} catch (err) {
|
|
2261
2645
|
failProfile(err.message);
|
|
@@ -2275,13 +2659,13 @@ agentRun.addCommand(profile);
|
|
|
2275
2659
|
// src/commands/plan.ts
|
|
2276
2660
|
import { Command as Command12 } from "commander";
|
|
2277
2661
|
import chalk12 from "chalk";
|
|
2278
|
-
import
|
|
2279
|
-
import
|
|
2662
|
+
import fs19 from "fs";
|
|
2663
|
+
import path18 from "path";
|
|
2280
2664
|
init_task_state2();
|
|
2281
2665
|
var PLAN_GENERATE_SKILL_NAME = "plan-generate";
|
|
2282
2666
|
function resolveSkillFilePath3(projectRoot, skillName) {
|
|
2283
|
-
const p =
|
|
2284
|
-
return
|
|
2667
|
+
const p = path18.join(projectRoot, ".claude", "skills", skillName, "SKILL.md");
|
|
2668
|
+
return fs19.existsSync(p) ? p : null;
|
|
2285
2669
|
}
|
|
2286
2670
|
var plan = new Command12("plan").description("Generate and refine plans");
|
|
2287
2671
|
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) => {
|
|
@@ -2413,7 +2797,7 @@ var PLAN_REFINE_SKILL_NAME = "plan-refine";
|
|
|
2413
2797
|
plan.command("refine <objectId>").description("Synthesize plan files + ISSUE files into a refined plan").option("--agent <name>", "Agent to run refine (claude-code|codex)", "claude-code").option("--model <id>", "Model id or alias (see `task0 models refresh`)").option("--effort <level>", "Reasoning effort (e.g. low|medium|high)").option("--wait", "Wait for completion").option("--json", "Output JSON").action(async (objectId, opts) => {
|
|
2414
2798
|
try {
|
|
2415
2799
|
const loc = resolveTaskByObjectId(objectId);
|
|
2416
|
-
const files =
|
|
2800
|
+
const files = fs19.readdirSync(loc.taskDir);
|
|
2417
2801
|
const planFiles = files.filter((f) => /^PLAN-\d+-(codex|claude-code|cursor)\.md$/.test(f));
|
|
2418
2802
|
if (planFiles.length === 0) {
|
|
2419
2803
|
console.error(chalk12.red("No PLAN-NN-<agent>.md files found. Run `task0 plan generate` first."));
|
|
@@ -2455,8 +2839,8 @@ plan.command("refine <objectId>").description("Synthesize plan files + ISSUE fil
|
|
|
2455
2839
|
if (!opts.json) console.log(chalk12.dim(`[refine] ${s.status}${s.phase ? " " + s.phase : ""}`));
|
|
2456
2840
|
}
|
|
2457
2841
|
});
|
|
2458
|
-
const refinedPath =
|
|
2459
|
-
const wrote =
|
|
2842
|
+
const refinedPath = path18.join(loc.taskDir, refinedFile);
|
|
2843
|
+
const wrote = fs19.existsSync(refinedPath);
|
|
2460
2844
|
if (final.status === "done" && wrote) {
|
|
2461
2845
|
await updateWorkflow(loc.taskYml, {
|
|
2462
2846
|
phase: "refined",
|
|
@@ -2608,8 +2992,8 @@ import { Command as Command14 } from "commander";
|
|
|
2608
2992
|
import chalk14 from "chalk";
|
|
2609
2993
|
|
|
2610
2994
|
// src/lib/project.ts
|
|
2611
|
-
import
|
|
2612
|
-
import
|
|
2995
|
+
import path19 from "path";
|
|
2996
|
+
import fs20 from "fs";
|
|
2613
2997
|
function resolveProjectName(opts) {
|
|
2614
2998
|
if (opts.project && opts.project.length > 0) return opts.project;
|
|
2615
2999
|
const config = loadConfig();
|
|
@@ -2619,15 +3003,15 @@ function resolveProjectName(opts) {
|
|
|
2619
3003
|
"Cannot resolve project: no registered projects. Use `task0 source add <path>` first, or pass --project <name>."
|
|
2620
3004
|
);
|
|
2621
3005
|
}
|
|
2622
|
-
const cwd =
|
|
3006
|
+
const cwd = fs20.realpathSync(process.cwd());
|
|
2623
3007
|
for (const source2 of projects) {
|
|
2624
3008
|
let sourceAbs;
|
|
2625
3009
|
try {
|
|
2626
|
-
sourceAbs =
|
|
3010
|
+
sourceAbs = fs20.realpathSync(path19.resolve(source2.path));
|
|
2627
3011
|
} catch {
|
|
2628
|
-
sourceAbs =
|
|
3012
|
+
sourceAbs = path19.resolve(source2.path);
|
|
2629
3013
|
}
|
|
2630
|
-
if (cwd === sourceAbs || cwd.startsWith(sourceAbs +
|
|
3014
|
+
if (cwd === sourceAbs || cwd.startsWith(sourceAbs + path19.sep)) {
|
|
2631
3015
|
return source2.name;
|
|
2632
3016
|
}
|
|
2633
3017
|
}
|
|
@@ -3267,12 +3651,12 @@ import chalk17 from "chalk";
|
|
|
3267
3651
|
|
|
3268
3652
|
// src/core/issue/decision.ts
|
|
3269
3653
|
init_node();
|
|
3270
|
-
import
|
|
3271
|
-
import
|
|
3654
|
+
import fs21 from "fs";
|
|
3655
|
+
import path20 from "path";
|
|
3272
3656
|
init_task_state2();
|
|
3273
3657
|
var DECISION_FILE_RE = /^DECISION-(\d+)-([a-z0-9-]+)\.md$/;
|
|
3274
3658
|
function selectBlockingIssues(taskDir, explicitIssue) {
|
|
3275
|
-
const files =
|
|
3659
|
+
const files = fs21.existsSync(taskDir) ? fs21.readdirSync(taskDir) : [];
|
|
3276
3660
|
const issueFiles = files.filter((f) => /^ISSUE-\d+\.md$/.test(f)).sort();
|
|
3277
3661
|
const all = readOpenQuestions(taskDir, issueFiles);
|
|
3278
3662
|
if (!explicitIssue) return all;
|
|
@@ -3376,12 +3760,12 @@ function parseConsolidatedAnswers(md) {
|
|
|
3376
3760
|
return sections;
|
|
3377
3761
|
}
|
|
3378
3762
|
function rewriteIssueWithDecisions(taskDir, issueFile, consolidatedFile) {
|
|
3379
|
-
const issuePath =
|
|
3380
|
-
const consolidatedPath =
|
|
3381
|
-
if (!
|
|
3382
|
-
if (!
|
|
3383
|
-
const issueMd =
|
|
3384
|
-
const consolidatedMd =
|
|
3763
|
+
const issuePath = path20.join(taskDir, issueFile);
|
|
3764
|
+
const consolidatedPath = path20.join(taskDir, consolidatedFile);
|
|
3765
|
+
if (!fs21.existsSync(issuePath)) throw new Error(`${issueFile} not found in ${taskDir}`);
|
|
3766
|
+
if (!fs21.existsSync(consolidatedPath)) throw new Error(`${consolidatedFile} not found in ${taskDir}`);
|
|
3767
|
+
const issueMd = fs21.readFileSync(issuePath, "utf-8");
|
|
3768
|
+
const consolidatedMd = fs21.readFileSync(consolidatedPath, "utf-8");
|
|
3385
3769
|
const answers = parseConsolidatedAnswers(consolidatedMd);
|
|
3386
3770
|
if (answers.length === 0) {
|
|
3387
3771
|
throw new Error(`${consolidatedFile} has no "## Qn" sections; cannot derive Decisions`);
|
|
@@ -3403,7 +3787,7 @@ _Resolved from [${consolidatedFile}](${consolidatedFile}); see that file for rea
|
|
|
3403
3787
|
throw new Error(`${issueFile} has no "## Open Questions" section to replace`);
|
|
3404
3788
|
}
|
|
3405
3789
|
const next = issueMd.replace(openQRe, decisionsSection.trimEnd() + "\n");
|
|
3406
|
-
|
|
3790
|
+
fs21.writeFileSync(issuePath, next, "utf-8");
|
|
3407
3791
|
return { replaced: answers.length };
|
|
3408
3792
|
}
|
|
3409
3793
|
async function propose(opts) {
|
|
@@ -3431,8 +3815,8 @@ async function propose(opts) {
|
|
|
3431
3815
|
const kicks = [];
|
|
3432
3816
|
for (const agent2 of opts.agents) {
|
|
3433
3817
|
const decisionFile = decisionFileName(issue2, agent2);
|
|
3434
|
-
const decisionPath =
|
|
3435
|
-
if (
|
|
3818
|
+
const decisionPath = path20.join(loc.taskDir, decisionFile);
|
|
3819
|
+
if (fs21.existsSync(decisionPath) && !opts.force) {
|
|
3436
3820
|
if (opts.ifNeeded) {
|
|
3437
3821
|
kicks.push({
|
|
3438
3822
|
issue: issue2.file,
|
|
@@ -3507,7 +3891,7 @@ async function propose(opts) {
|
|
|
3507
3891
|
const final = await waitForAgentRun(k.agentRunId);
|
|
3508
3892
|
if (final.status !== "done") {
|
|
3509
3893
|
k.error = final.error || `runtime ${k.agentRunId} ended with ${final.status}`;
|
|
3510
|
-
} else if (!
|
|
3894
|
+
} else if (!fs21.existsSync(path20.join(loc.taskDir, k.decisionFile))) {
|
|
3511
3895
|
k.error = `runtime completed but ${k.decisionFile} was not written`;
|
|
3512
3896
|
}
|
|
3513
3897
|
} catch (err) {
|
|
@@ -3528,7 +3912,7 @@ async function consolidate(opts) {
|
|
|
3528
3912
|
const kicks = [];
|
|
3529
3913
|
const modelOpts = resolveModelOptions(opts.agent, { model: opts.model, effort: opts.effort }, opts.warn);
|
|
3530
3914
|
for (const issue2 of issues) {
|
|
3531
|
-
const files =
|
|
3915
|
+
const files = fs21.readdirSync(loc.taskDir);
|
|
3532
3916
|
const proposalFiles = files.filter((f) => {
|
|
3533
3917
|
const m = f.match(DECISION_FILE_RE);
|
|
3534
3918
|
return !!m && m[1] === issue2.index && m[2] !== "consolidated";
|
|
@@ -3574,7 +3958,7 @@ async function consolidate(opts) {
|
|
|
3574
3958
|
if (opts.wait) {
|
|
3575
3959
|
try {
|
|
3576
3960
|
const final = await waitForAgentRun(agentRunId);
|
|
3577
|
-
const wrote =
|
|
3961
|
+
const wrote = fs21.existsSync(path20.join(loc.taskDir, consolidatedFile));
|
|
3578
3962
|
if (final.status !== "done") {
|
|
3579
3963
|
kick.error = final.error || `runtime ${agentRunId} ended with ${final.status}`;
|
|
3580
3964
|
} else if (!wrote) {
|
|
@@ -3640,7 +4024,7 @@ async function approve(opts) {
|
|
|
3640
4024
|
return { updated };
|
|
3641
4025
|
}
|
|
3642
4026
|
function buildReferenceFiles(taskDir, issue2) {
|
|
3643
|
-
const names =
|
|
4027
|
+
const names = fs21.readdirSync(taskDir);
|
|
3644
4028
|
const refs = [issue2.file];
|
|
3645
4029
|
if (names.includes("ISSUE.md")) refs.push("ISSUE.md");
|
|
3646
4030
|
const ideaFile = `IDEA-${issue2.index}.md`;
|
|
@@ -3748,12 +4132,12 @@ issue.command("approve <taskId>").description("Apply the consolidated decisions
|
|
|
3748
4132
|
});
|
|
3749
4133
|
|
|
3750
4134
|
// src/commands/agent.ts
|
|
3751
|
-
import
|
|
3752
|
-
import
|
|
4135
|
+
import fs22 from "fs";
|
|
4136
|
+
import path21 from "path";
|
|
3753
4137
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
3754
4138
|
import { Command as Command18 } from "commander";
|
|
3755
4139
|
import chalk18 from "chalk";
|
|
3756
|
-
import
|
|
4140
|
+
import yaml8 from "js-yaml";
|
|
3757
4141
|
function statusBadge(s) {
|
|
3758
4142
|
if (s === "working") return chalk18.cyan("\u25CF");
|
|
3759
4143
|
if (s === "error") return chalk18.red("\u25CF");
|
|
@@ -3813,10 +4197,10 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
|
|
|
3813
4197
|
}
|
|
3814
4198
|
console.log();
|
|
3815
4199
|
console.log(chalk18.dim("--- spec ---"));
|
|
3816
|
-
console.log(
|
|
4200
|
+
console.log(yaml8.dump(result.agent.spec, { lineWidth: 100 }));
|
|
3817
4201
|
if (result.agent.mcp_config) {
|
|
3818
4202
|
console.log(chalk18.dim("--- mcp_config ---"));
|
|
3819
|
-
console.log(
|
|
4203
|
+
console.log(yaml8.dump(result.agent.mcp_config, { lineWidth: 100 }));
|
|
3820
4204
|
}
|
|
3821
4205
|
} catch (err) {
|
|
3822
4206
|
const apiErr = err;
|
|
@@ -3827,8 +4211,8 @@ agent.command("get <ref>").description("Show one agent by object_id or slug").op
|
|
|
3827
4211
|
agent.command("create").description("Create an agent from a YAML spec file").requiredOption("--from-file <file>", "YAML spec file with at least kind, slug, spec").option("--scope <scope>", "Storage scope: user (default) or project", "user").option("--project-root <path>", "Project root (required for --scope project)").action(async (opts) => {
|
|
3828
4212
|
let parsed;
|
|
3829
4213
|
try {
|
|
3830
|
-
const raw =
|
|
3831
|
-
parsed =
|
|
4214
|
+
const raw = fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8");
|
|
4215
|
+
parsed = yaml8.load(raw);
|
|
3832
4216
|
} catch (err) {
|
|
3833
4217
|
fail5(`cannot read ${opts.fromFile}: ${err.message}`);
|
|
3834
4218
|
}
|
|
@@ -3844,17 +4228,17 @@ agent.command("edit <ref>").description("Open the agent YAML in $EDITOR and save
|
|
|
3844
4228
|
try {
|
|
3845
4229
|
const result = await api.get(`/api/agents/${encodeURIComponent(ref)}`);
|
|
3846
4230
|
if (result.agent.system) fail5("cannot edit a system agent");
|
|
3847
|
-
const tmp =
|
|
4231
|
+
const tmp = path21.join(
|
|
3848
4232
|
process.env.TMPDIR || "/tmp",
|
|
3849
4233
|
`task0-agent-${result.agent.slug}-${Date.now()}.yml`
|
|
3850
4234
|
);
|
|
3851
|
-
|
|
4235
|
+
fs22.writeFileSync(tmp, yaml8.dump(result.agent, { lineWidth: 100 }), "utf-8");
|
|
3852
4236
|
const editor = process.env.EDITOR || "vi";
|
|
3853
4237
|
const r = spawnSync5(editor, [tmp], { stdio: "inherit" });
|
|
3854
4238
|
if (r.status !== 0) fail5(`editor exited with status ${r.status}`);
|
|
3855
|
-
const updated =
|
|
4239
|
+
const updated = yaml8.load(fs22.readFileSync(tmp, "utf-8"));
|
|
3856
4240
|
await api.put(`/api/agents/${encodeURIComponent(ref)}`, updated);
|
|
3857
|
-
|
|
4241
|
+
fs22.unlinkSync(tmp);
|
|
3858
4242
|
console.log(chalk18.green(`updated ${ref}`));
|
|
3859
4243
|
} catch (err) {
|
|
3860
4244
|
fail5(err.message);
|
|
@@ -3925,7 +4309,7 @@ var mcp = new Command18("mcp").description("Manage per-agent MCP config (mcp_con
|
|
|
3925
4309
|
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
4310
|
let parsed;
|
|
3927
4311
|
try {
|
|
3928
|
-
parsed = JSON.parse(
|
|
4312
|
+
parsed = JSON.parse(fs22.readFileSync(path21.resolve(opts.fromFile), "utf-8"));
|
|
3929
4313
|
} catch (err) {
|
|
3930
4314
|
fail5(`cannot parse ${opts.fromFile}: ${err.message}`);
|
|
3931
4315
|
}
|
|
@@ -3968,78 +4352,76 @@ async function streamOutput(ref, agentRunId) {
|
|
|
3968
4352
|
}
|
|
3969
4353
|
|
|
3970
4354
|
// src/commands/daemon.ts
|
|
3971
|
-
import
|
|
4355
|
+
import os8 from "os";
|
|
3972
4356
|
import { Command as Command19 } from "commander";
|
|
3973
4357
|
import chalk19 from "chalk";
|
|
3974
4358
|
import WebSocket from "ws";
|
|
3975
4359
|
|
|
3976
|
-
// src/core/
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
var
|
|
3981
|
-
var
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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;
|
|
4360
|
+
// src/core/daemon-agent-run-sink.ts
|
|
4361
|
+
var BUFFER_CAP = 5e3;
|
|
4362
|
+
var bound = null;
|
|
4363
|
+
var seqByRun = /* @__PURE__ */ new Map();
|
|
4364
|
+
var buffer = [];
|
|
4365
|
+
var logHistoryByRun = /* @__PURE__ */ new Map();
|
|
4366
|
+
function bindAgentRunFrameSink(sink) {
|
|
4367
|
+
bound = sink;
|
|
4368
|
+
if (!sink) return;
|
|
4369
|
+
while (buffer.length > 0) {
|
|
4370
|
+
const frame = buffer.shift();
|
|
4371
|
+
try {
|
|
4372
|
+
sink.send(frame);
|
|
4373
|
+
} catch {
|
|
4005
4374
|
}
|
|
4006
4375
|
}
|
|
4007
|
-
throw new AdminTokenUnavailableError();
|
|
4008
4376
|
}
|
|
4009
|
-
function
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
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");
|
|
4019
|
-
function daemonConfigPath() {
|
|
4020
|
-
return CONFIG_FILE2;
|
|
4021
|
-
}
|
|
4022
|
-
function readDaemonIdentity() {
|
|
4023
|
-
if (!fs21.existsSync(CONFIG_FILE2)) return null;
|
|
4024
|
-
try {
|
|
4025
|
-
const raw = fs21.readFileSync(CONFIG_FILE2, "utf-8");
|
|
4026
|
-
return JSON.parse(raw);
|
|
4027
|
-
} catch {
|
|
4028
|
-
return null;
|
|
4377
|
+
function deliverOrBuffer(frame) {
|
|
4378
|
+
if (bound) {
|
|
4379
|
+
try {
|
|
4380
|
+
bound.send(frame);
|
|
4381
|
+
return;
|
|
4382
|
+
} catch {
|
|
4383
|
+
}
|
|
4029
4384
|
}
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
fs21.mkdirSync(CONFIG_DIR3, { recursive: true });
|
|
4033
|
-
fs21.writeFileSync(CONFIG_FILE2, JSON.stringify(identity, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
4034
|
-
try {
|
|
4035
|
-
fs21.chmodSync(CONFIG_FILE2, 384);
|
|
4036
|
-
} catch {
|
|
4385
|
+
if (buffer.length >= BUFFER_CAP) {
|
|
4386
|
+
buffer.shift();
|
|
4037
4387
|
}
|
|
4388
|
+
buffer.push(frame);
|
|
4038
4389
|
}
|
|
4039
|
-
function
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4390
|
+
function pruneRunHistory(runId) {
|
|
4391
|
+
logHistoryByRun.delete(runId);
|
|
4392
|
+
seqByRun.delete(runId);
|
|
4393
|
+
}
|
|
4394
|
+
function emitAgentRunStatus(runId, status, opts = {}) {
|
|
4395
|
+
deliverOrBuffer({
|
|
4396
|
+
type: "agent_run_status",
|
|
4397
|
+
run_id: runId,
|
|
4398
|
+
status,
|
|
4399
|
+
phase: opts.phase ?? null,
|
|
4400
|
+
exit_code: opts.exitCode ?? null,
|
|
4401
|
+
error: opts.error ?? null,
|
|
4402
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
4403
|
+
});
|
|
4404
|
+
if (status === "completed" || status === "failed" || status === "killed") {
|
|
4405
|
+
pruneRunHistory(runId);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
function replayAfterRanges(ranges) {
|
|
4409
|
+
if (!bound) return 0;
|
|
4410
|
+
let sent = 0;
|
|
4411
|
+
for (const { run_id, after_seq } of ranges) {
|
|
4412
|
+
const ring = logHistoryByRun.get(run_id);
|
|
4413
|
+
if (!ring) continue;
|
|
4414
|
+
for (const frame of ring) {
|
|
4415
|
+
if (frame.seq <= after_seq) continue;
|
|
4416
|
+
try {
|
|
4417
|
+
bound.send(frame);
|
|
4418
|
+
sent += 1;
|
|
4419
|
+
} catch {
|
|
4420
|
+
return sent;
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
return sent;
|
|
4043
4425
|
}
|
|
4044
4426
|
|
|
4045
4427
|
// src/core/register-auth.ts
|
|
@@ -4075,8 +4457,191 @@ function pickRegisterAuth(flagToken) {
|
|
|
4075
4457
|
|
|
4076
4458
|
// src/core/daemon-rpc-handlers.ts
|
|
4077
4459
|
init_node();
|
|
4078
|
-
import
|
|
4079
|
-
import
|
|
4460
|
+
import fs25 from "fs";
|
|
4461
|
+
import path24 from "path";
|
|
4462
|
+
|
|
4463
|
+
// src/core/daemon-agent-run-runner.ts
|
|
4464
|
+
import { execFileSync } from "child_process";
|
|
4465
|
+
import fs24 from "fs";
|
|
4466
|
+
import path23 from "path";
|
|
4467
|
+
|
|
4468
|
+
// src/core/daemon-agent-run-dir.ts
|
|
4469
|
+
import fs23 from "fs";
|
|
4470
|
+
import os6 from "os";
|
|
4471
|
+
import path22 from "path";
|
|
4472
|
+
function agentRunRoot() {
|
|
4473
|
+
return process.env.TASK0_DAEMON_AGENT_RUN_DIR || path22.join(os6.homedir(), ".config", "task0", "agent-run");
|
|
4474
|
+
}
|
|
4475
|
+
function agentRunDir(runId) {
|
|
4476
|
+
return path22.join(agentRunRoot(), runId);
|
|
4477
|
+
}
|
|
4478
|
+
function agentRunStatusPath(runId) {
|
|
4479
|
+
return path22.join(agentRunDir(runId), "status.json");
|
|
4480
|
+
}
|
|
4481
|
+
function ensureAgentRunDir(runId) {
|
|
4482
|
+
const dir = agentRunDir(runId);
|
|
4483
|
+
fs23.mkdirSync(dir, { recursive: true });
|
|
4484
|
+
return dir;
|
|
4485
|
+
}
|
|
4486
|
+
function removeAgentRunDir(runId) {
|
|
4487
|
+
const dir = agentRunDir(runId);
|
|
4488
|
+
if (!fs23.existsSync(dir)) return;
|
|
4489
|
+
fs23.rmSync(dir, { recursive: true, force: true });
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
// src/core/daemon-agent-run-runner.ts
|
|
4493
|
+
var active = /* @__PURE__ */ new Map();
|
|
4494
|
+
var STATUS_POLL_MS = 500;
|
|
4495
|
+
var SESSION_PROBE_MS = 3e3;
|
|
4496
|
+
function getTmuxBin() {
|
|
4497
|
+
const fromEnv = process.env.TASK0_TMUX_BIN;
|
|
4498
|
+
if (fromEnv) return fromEnv;
|
|
4499
|
+
return "tmux";
|
|
4500
|
+
}
|
|
4501
|
+
function checkTmuxAvailable() {
|
|
4502
|
+
try {
|
|
4503
|
+
execFileSync(getTmuxBin(), ["-V"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
4504
|
+
return { ok: true };
|
|
4505
|
+
} catch (err) {
|
|
4506
|
+
return { ok: false, reason: `tmux not available: ${err instanceof Error ? err.message : String(err)}` };
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
function isSessionAlive2(sessionName) {
|
|
4510
|
+
try {
|
|
4511
|
+
execFileSync(getTmuxBin(), ["has-session", "-t", sessionName], {
|
|
4512
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4513
|
+
});
|
|
4514
|
+
return true;
|
|
4515
|
+
} catch {
|
|
4516
|
+
return false;
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
function killSession2(sessionName) {
|
|
4520
|
+
try {
|
|
4521
|
+
execFileSync(getTmuxBin(), ["kill-session", "-t", sessionName], {
|
|
4522
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4523
|
+
});
|
|
4524
|
+
return true;
|
|
4525
|
+
} catch {
|
|
4526
|
+
return false;
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
function readStatusFile(runId) {
|
|
4530
|
+
const filePath = agentRunStatusPath(runId);
|
|
4531
|
+
if (!fs24.existsSync(filePath)) return null;
|
|
4532
|
+
try {
|
|
4533
|
+
const raw = fs24.readFileSync(filePath, "utf-8");
|
|
4534
|
+
const parsed = JSON.parse(raw);
|
|
4535
|
+
return { raw, parsed };
|
|
4536
|
+
} catch {
|
|
4537
|
+
return null;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
function deriveStatus(parsed) {
|
|
4541
|
+
const raw = typeof parsed.status === "string" ? parsed.status : "starting";
|
|
4542
|
+
let status = "starting";
|
|
4543
|
+
if (raw === "running") status = "running";
|
|
4544
|
+
else if (raw === "done" || raw === "completed") status = "completed";
|
|
4545
|
+
else if (raw === "error" || raw === "failed") status = "failed";
|
|
4546
|
+
const phase = typeof parsed.phase === "string" ? parsed.phase : null;
|
|
4547
|
+
const error2 = typeof parsed.error === "string" && parsed.error ? parsed.error : null;
|
|
4548
|
+
return { status, phase, error: error2 };
|
|
4549
|
+
}
|
|
4550
|
+
function launchAgentRun(params) {
|
|
4551
|
+
const tmuxCheck = checkTmuxAvailable();
|
|
4552
|
+
if (!tmuxCheck.ok) {
|
|
4553
|
+
throw Object.assign(new Error(tmuxCheck.reason ?? "tmux unavailable"), { code: "tmux_unavailable" });
|
|
4554
|
+
}
|
|
4555
|
+
if (active.has(params.runId)) {
|
|
4556
|
+
throw Object.assign(new Error(`agent run already active: ${params.runId}`), { code: "already_running" });
|
|
4557
|
+
}
|
|
4558
|
+
if (!fs24.existsSync(params.workspace)) {
|
|
4559
|
+
throw Object.assign(new Error(`workspace not found: ${params.workspace}`), { code: "workspace_missing" });
|
|
4560
|
+
}
|
|
4561
|
+
const runDir = ensureAgentRunDir(params.runId);
|
|
4562
|
+
const scriptPath = path23.join(runDir, "script.sh");
|
|
4563
|
+
fs24.writeFileSync(scriptPath, params.scriptContent, { mode: 493 });
|
|
4564
|
+
if (params.promptContent !== void 0) {
|
|
4565
|
+
fs24.writeFileSync(path23.join(runDir, "prompt.txt"), params.promptContent, "utf-8");
|
|
4566
|
+
}
|
|
4567
|
+
for (const [name, content] of Object.entries(params.auxFiles ?? {})) {
|
|
4568
|
+
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
4569
|
+
throw Object.assign(new Error(`invalid aux file name: ${name}`), { code: "invalid_params" });
|
|
4570
|
+
}
|
|
4571
|
+
fs24.writeFileSync(path23.join(runDir, name), content, "utf-8");
|
|
4572
|
+
}
|
|
4573
|
+
fs24.writeFileSync(
|
|
4574
|
+
agentRunStatusPath(params.runId),
|
|
4575
|
+
JSON.stringify({ status: "starting", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
4576
|
+
"utf-8"
|
|
4577
|
+
);
|
|
4578
|
+
try {
|
|
4579
|
+
execFileSync(
|
|
4580
|
+
getTmuxBin(),
|
|
4581
|
+
["new-session", "-d", "-s", params.sessionName, "-c", params.workspace, `bash ${JSON.stringify(scriptPath)}`],
|
|
4582
|
+
{ encoding: "utf-8", timeout: 5e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
4583
|
+
);
|
|
4584
|
+
} catch (err) {
|
|
4585
|
+
removeAgentRunDir(params.runId);
|
|
4586
|
+
throw Object.assign(
|
|
4587
|
+
new Error(`failed to start tmux session: ${err instanceof Error ? err.message : String(err)}`),
|
|
4588
|
+
{ code: "spawn_failed" }
|
|
4589
|
+
);
|
|
4590
|
+
}
|
|
4591
|
+
emitAgentRunStatus(params.runId, "starting", { phase: null });
|
|
4592
|
+
const entry = {
|
|
4593
|
+
runId: params.runId,
|
|
4594
|
+
sessionName: params.sessionName,
|
|
4595
|
+
lastStatusJson: null,
|
|
4596
|
+
finished: false,
|
|
4597
|
+
pollInterval: setInterval(() => pollOne(params.runId), STATUS_POLL_MS)
|
|
4598
|
+
};
|
|
4599
|
+
active.set(params.runId, entry);
|
|
4600
|
+
const sessionProbe = setInterval(() => {
|
|
4601
|
+
const cur = active.get(params.runId);
|
|
4602
|
+
if (!cur || cur.finished) {
|
|
4603
|
+
clearInterval(sessionProbe);
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
if (!isSessionAlive2(cur.sessionName)) {
|
|
4607
|
+
finishRun(params.runId, "killed", { error: "tmux session disappeared" });
|
|
4608
|
+
clearInterval(sessionProbe);
|
|
4609
|
+
}
|
|
4610
|
+
}, SESSION_PROBE_MS);
|
|
4611
|
+
return { agentRunDir: runDir, sessionName: params.sessionName };
|
|
4612
|
+
}
|
|
4613
|
+
function pollOne(runId) {
|
|
4614
|
+
const entry = active.get(runId);
|
|
4615
|
+
if (!entry || entry.finished) return;
|
|
4616
|
+
const current = readStatusFile(runId);
|
|
4617
|
+
if (!current) return;
|
|
4618
|
+
if (current.raw === entry.lastStatusJson) return;
|
|
4619
|
+
entry.lastStatusJson = current.raw;
|
|
4620
|
+
const { status, phase, error: error2 } = deriveStatus(current.parsed);
|
|
4621
|
+
emitAgentRunStatus(runId, status, { phase, error: error2 });
|
|
4622
|
+
if (status === "completed" || status === "failed") {
|
|
4623
|
+
finishRun(runId, status, { phase, error: error2 });
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
function finishRun(runId, status, opts = {}) {
|
|
4627
|
+
const entry = active.get(runId);
|
|
4628
|
+
if (!entry || entry.finished) return;
|
|
4629
|
+
entry.finished = true;
|
|
4630
|
+
clearInterval(entry.pollInterval);
|
|
4631
|
+
emitAgentRunStatus(runId, status, opts);
|
|
4632
|
+
active.delete(runId);
|
|
4633
|
+
}
|
|
4634
|
+
function cancelAgentRun(runId) {
|
|
4635
|
+
const entry = active.get(runId);
|
|
4636
|
+
if (!entry) {
|
|
4637
|
+
return { ok: false, sessionName: null };
|
|
4638
|
+
}
|
|
4639
|
+
killSession2(entry.sessionName);
|
|
4640
|
+
finishRun(runId, "killed", { error: "cancelled by hub" });
|
|
4641
|
+
return { ok: true, sessionName: entry.sessionName };
|
|
4642
|
+
}
|
|
4643
|
+
|
|
4644
|
+
// src/core/daemon-rpc-handlers.ts
|
|
4080
4645
|
var MAX_FILE_BYTES = 1 * 1024 * 1024;
|
|
4081
4646
|
function ensureString(value, name) {
|
|
4082
4647
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -4084,13 +4649,47 @@ function ensureString(value, name) {
|
|
|
4084
4649
|
}
|
|
4085
4650
|
return value;
|
|
4086
4651
|
}
|
|
4652
|
+
function optionalString(value) {
|
|
4653
|
+
if (value === void 0 || value === null) return void 0;
|
|
4654
|
+
if (typeof value !== "string") {
|
|
4655
|
+
throw Object.assign(new Error("expected string"), { code: "invalid_params" });
|
|
4656
|
+
}
|
|
4657
|
+
return value;
|
|
4658
|
+
}
|
|
4659
|
+
function optionalBoolean(value) {
|
|
4660
|
+
if (value === void 0 || value === null) return void 0;
|
|
4661
|
+
if (typeof value !== "boolean") {
|
|
4662
|
+
throw Object.assign(new Error("expected boolean"), { code: "invalid_params" });
|
|
4663
|
+
}
|
|
4664
|
+
return value;
|
|
4665
|
+
}
|
|
4666
|
+
function listProjects() {
|
|
4667
|
+
return loadConfig().sources.filter((source2) => source2.type === "project");
|
|
4668
|
+
}
|
|
4669
|
+
function applyRepair(r) {
|
|
4670
|
+
const raw = readYaml(r.taskYml);
|
|
4671
|
+
if (!raw) return;
|
|
4672
|
+
if (r.reason === "missing_object_id") raw.object_id = generateObjectId("task");
|
|
4673
|
+
if (r.reason === "id_mismatch") raw.id = r.dirName;
|
|
4674
|
+
writeYaml(r.taskYml, raw);
|
|
4675
|
+
}
|
|
4676
|
+
function scanProjectWithRepair(projectPath, sourceName) {
|
|
4677
|
+
let result = scanProject(projectPath, sourceName);
|
|
4678
|
+
if (result.repairs.length > 0) {
|
|
4679
|
+
for (const r of result.repairs) applyRepair(r);
|
|
4680
|
+
result = scanProject(projectPath, sourceName);
|
|
4681
|
+
}
|
|
4682
|
+
return result;
|
|
4683
|
+
}
|
|
4087
4684
|
var rpcHandlers = {
|
|
4088
4685
|
// Scan a local project for its task manifest. Returns the same shape the
|
|
4089
|
-
// in-process
|
|
4686
|
+
// hub's in-process scanProject() used to return — with auto-repair of
|
|
4687
|
+
// missing object_id / id mismatches applied in place on the daemon's FS.
|
|
4090
4688
|
async scan_project(params) {
|
|
4091
4689
|
const rootPath = ensureString(params.rootPath, "rootPath");
|
|
4092
|
-
const name = typeof params.name === "string" && params.name ? params.name :
|
|
4093
|
-
|
|
4690
|
+
const name = typeof params.name === "string" && params.name ? params.name : path24.basename(rootPath);
|
|
4691
|
+
const result = scanProjectWithRepair(rootPath, name);
|
|
4692
|
+
return { tasks: result.tasks, errors: result.errors };
|
|
4094
4693
|
},
|
|
4095
4694
|
// Read a file from disk on the daemon's host. The dashboard uses this to
|
|
4096
4695
|
// peek into archived task content, runtime logs, etc. Cap is 1 MiB to
|
|
@@ -4099,7 +4698,7 @@ var rpcHandlers = {
|
|
|
4099
4698
|
const filePath = ensureString(params.path, "path");
|
|
4100
4699
|
let stat;
|
|
4101
4700
|
try {
|
|
4102
|
-
stat =
|
|
4701
|
+
stat = fs25.statSync(filePath);
|
|
4103
4702
|
} catch {
|
|
4104
4703
|
throw Object.assign(new Error("file not found"), { code: "not_found" });
|
|
4105
4704
|
}
|
|
@@ -4109,23 +4708,253 @@ var rpcHandlers = {
|
|
|
4109
4708
|
if (stat.size > MAX_FILE_BYTES) {
|
|
4110
4709
|
throw Object.assign(new Error(`file too large (${stat.size} bytes > ${MAX_FILE_BYTES})`), { code: "too_large" });
|
|
4111
4710
|
}
|
|
4112
|
-
const content =
|
|
4711
|
+
const content = fs25.readFileSync(filePath, "utf-8");
|
|
4113
4712
|
return { content, size: stat.size, modifiedAt: stat.mtime.toISOString() };
|
|
4713
|
+
},
|
|
4714
|
+
// ---------------------------------------------------------------------
|
|
4715
|
+
// Project source CRUD. The daemon owns its host's project list; the hub
|
|
4716
|
+
// routes /api/daemons/:id/projects through these handlers instead of
|
|
4717
|
+
// writing config.yml directly. After every mutation we re-push manifest
|
|
4718
|
+
// so the hub's daemon_projects table catches up without waiting for a
|
|
4719
|
+
// restart.
|
|
4720
|
+
// ---------------------------------------------------------------------
|
|
4721
|
+
async project_list() {
|
|
4722
|
+
return { projects: listProjects() };
|
|
4723
|
+
},
|
|
4724
|
+
async project_add(params, ctx) {
|
|
4725
|
+
const rawPath = ensureString(params.path, "path");
|
|
4726
|
+
const absPath = path24.resolve(rawPath);
|
|
4727
|
+
if (!fs25.existsSync(absPath)) {
|
|
4728
|
+
throw Object.assign(new Error(`path does not exist: ${absPath}`), { code: "not_found" });
|
|
4729
|
+
}
|
|
4730
|
+
const stat = fs25.statSync(absPath);
|
|
4731
|
+
if (!stat.isDirectory()) {
|
|
4732
|
+
throw Object.assign(new Error(`path is not a directory: ${absPath}`), { code: "invalid_target" });
|
|
4733
|
+
}
|
|
4734
|
+
const name = optionalString(params.name)?.trim() || path24.basename(absPath);
|
|
4735
|
+
const result = scanProjectWithRepair(absPath, name);
|
|
4736
|
+
addSource({ name, type: "project", path: absPath, enabled: true });
|
|
4737
|
+
ctx.notifyManifestChanged();
|
|
4738
|
+
return {
|
|
4739
|
+
project: { name, type: "project", path: absPath, enabled: true },
|
|
4740
|
+
taskCount: result.tasks.length
|
|
4741
|
+
};
|
|
4742
|
+
},
|
|
4743
|
+
async project_remove(params, ctx) {
|
|
4744
|
+
const name = ensureString(params.name, "name");
|
|
4745
|
+
const ok = removeSource(name);
|
|
4746
|
+
if (!ok) {
|
|
4747
|
+
throw Object.assign(new Error(`project not found: ${name}`), { code: "not_found" });
|
|
4748
|
+
}
|
|
4749
|
+
ctx.notifyManifestChanged();
|
|
4750
|
+
return { ok: true };
|
|
4751
|
+
},
|
|
4752
|
+
async project_set_enabled(params, ctx) {
|
|
4753
|
+
const name = ensureString(params.name, "name");
|
|
4754
|
+
const enabled = optionalBoolean(params.enabled);
|
|
4755
|
+
if (enabled === void 0) {
|
|
4756
|
+
throw Object.assign(new Error("enabled is required (boolean)"), { code: "invalid_params" });
|
|
4757
|
+
}
|
|
4758
|
+
const config = loadConfig();
|
|
4759
|
+
const source2 = config.sources.find((s) => s.name === name && s.type === "project");
|
|
4760
|
+
if (!source2) {
|
|
4761
|
+
throw Object.assign(new Error(`project not found: ${name}`), { code: "not_found" });
|
|
4762
|
+
}
|
|
4763
|
+
source2.enabled = enabled;
|
|
4764
|
+
saveConfig(config);
|
|
4765
|
+
ctx.notifyManifestChanged();
|
|
4766
|
+
return { project: source2 };
|
|
4767
|
+
},
|
|
4768
|
+
// ---------------------------------------------------------------------
|
|
4769
|
+
// Task content R/W. These are the deep-pull operations from Q5(c): the
|
|
4770
|
+
// hub asks the daemon to read/write/create/delete a task's yaml + sibling
|
|
4771
|
+
// files on the daemon's FS. After every mutation we re-push manifest so
|
|
4772
|
+
// hub's daemon_tasks index reflects the new state without a restart.
|
|
4773
|
+
// ---------------------------------------------------------------------
|
|
4774
|
+
// Read a task's task0.yml (parsed) plus an optional list of sibling files
|
|
4775
|
+
// in the same task directory. Path is resolved from (projectName, taskId).
|
|
4776
|
+
async task_read(params) {
|
|
4777
|
+
const projectName = ensureString(params.projectName, "projectName");
|
|
4778
|
+
const taskId = ensureString(params.taskId, "taskId");
|
|
4779
|
+
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4780
|
+
if (!project2) {
|
|
4781
|
+
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4782
|
+
}
|
|
4783
|
+
const projectAbs = path24.resolve(project2.path);
|
|
4784
|
+
const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
|
|
4785
|
+
if (!projectConfig) {
|
|
4786
|
+
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4787
|
+
}
|
|
4788
|
+
const taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
|
|
4789
|
+
if (!fs25.existsSync(taskDir)) {
|
|
4790
|
+
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4791
|
+
}
|
|
4792
|
+
const taskYml = path24.join(taskDir, "task0.yml");
|
|
4793
|
+
const yaml9 = readYaml(taskYml);
|
|
4794
|
+
if (!yaml9) {
|
|
4795
|
+
throw Object.assign(new Error(`task yaml missing or unreadable: ${taskYml}`), { code: "not_found" });
|
|
4796
|
+
}
|
|
4797
|
+
const files = fs25.readdirSync(taskDir).filter((name) => name !== "task0.yml");
|
|
4798
|
+
return { task_dir: taskDir, yaml: yaml9, files };
|
|
4799
|
+
},
|
|
4800
|
+
// Create a task directory + write task0.yml + write any additional named
|
|
4801
|
+
// files. Hub decides the taskId and yaml content (slug/object_id are
|
|
4802
|
+
// generated server-side); daemon just commits to disk.
|
|
4803
|
+
async task_create(params, ctx) {
|
|
4804
|
+
const projectName = ensureString(params.projectName, "projectName");
|
|
4805
|
+
const taskId = ensureString(params.taskId, "taskId");
|
|
4806
|
+
const yaml9 = params.yaml;
|
|
4807
|
+
if (!yaml9 || typeof yaml9 !== "object") {
|
|
4808
|
+
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4809
|
+
}
|
|
4810
|
+
const files = params.files ?? {};
|
|
4811
|
+
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4812
|
+
if (!project2) {
|
|
4813
|
+
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4814
|
+
}
|
|
4815
|
+
const projectAbs = path24.resolve(project2.path);
|
|
4816
|
+
const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
|
|
4817
|
+
if (!projectConfig) {
|
|
4818
|
+
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4819
|
+
}
|
|
4820
|
+
const taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
|
|
4821
|
+
if (fs25.existsSync(taskDir)) {
|
|
4822
|
+
throw Object.assign(new Error(`task already exists: ${taskId}`), { code: "already_exists" });
|
|
4823
|
+
}
|
|
4824
|
+
fs25.mkdirSync(taskDir, { recursive: true });
|
|
4825
|
+
writeYaml(path24.join(taskDir, "task0.yml"), yaml9);
|
|
4826
|
+
for (const [name, content] of Object.entries(files)) {
|
|
4827
|
+
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
4828
|
+
throw Object.assign(new Error(`invalid file name: ${name}`), { code: "invalid_params" });
|
|
4829
|
+
}
|
|
4830
|
+
fs25.writeFileSync(path24.join(taskDir, name), content, "utf-8");
|
|
4831
|
+
}
|
|
4832
|
+
ctx.notifyManifestChanged();
|
|
4833
|
+
return { task_dir: taskDir };
|
|
4834
|
+
},
|
|
4835
|
+
// Patch a task's task0.yml in place. The hub computes the desired yaml
|
|
4836
|
+
// (typically by loading + applying its own metadata patch), then sends
|
|
4837
|
+
// the full replacement object back. Atomic at the file level — no
|
|
4838
|
+
// partial writes.
|
|
4839
|
+
async task_write_yaml(params, ctx) {
|
|
4840
|
+
const projectName = ensureString(params.projectName, "projectName");
|
|
4841
|
+
const taskId = ensureString(params.taskId, "taskId");
|
|
4842
|
+
const yaml9 = params.yaml;
|
|
4843
|
+
if (!yaml9 || typeof yaml9 !== "object") {
|
|
4844
|
+
throw Object.assign(new Error("yaml (object) is required"), { code: "invalid_params" });
|
|
4845
|
+
}
|
|
4846
|
+
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4847
|
+
if (!project2) {
|
|
4848
|
+
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4849
|
+
}
|
|
4850
|
+
const projectAbs = path24.resolve(project2.path);
|
|
4851
|
+
const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
|
|
4852
|
+
if (!projectConfig) {
|
|
4853
|
+
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4854
|
+
}
|
|
4855
|
+
const taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
|
|
4856
|
+
const taskYml = path24.join(taskDir, "task0.yml");
|
|
4857
|
+
if (!fs25.existsSync(taskYml)) {
|
|
4858
|
+
throw Object.assign(new Error(`task yaml not found: ${taskYml}`), { code: "not_found" });
|
|
4859
|
+
}
|
|
4860
|
+
writeYaml(taskYml, yaml9);
|
|
4861
|
+
ctx.notifyManifestChanged();
|
|
4862
|
+
return { task_dir: taskDir };
|
|
4863
|
+
},
|
|
4864
|
+
// Delete an entire task directory (use sparingly; mostly for archive
|
|
4865
|
+
// workflows). Returns the deleted path so the hub can emit an event.
|
|
4866
|
+
async task_delete(params, ctx) {
|
|
4867
|
+
const projectName = ensureString(params.projectName, "projectName");
|
|
4868
|
+
const taskId = ensureString(params.taskId, "taskId");
|
|
4869
|
+
const project2 = listProjects().find((p) => p.name === projectName);
|
|
4870
|
+
if (!project2) {
|
|
4871
|
+
throw Object.assign(new Error(`project not found: ${projectName}`), { code: "not_found" });
|
|
4872
|
+
}
|
|
4873
|
+
const projectAbs = path24.resolve(project2.path);
|
|
4874
|
+
const projectConfig = readYaml(path24.join(projectAbs, "task0.yml"));
|
|
4875
|
+
if (!projectConfig) {
|
|
4876
|
+
throw Object.assign(new Error(`invalid project: missing task0.yml at ${projectAbs}`), { code: "invalid_project" });
|
|
4877
|
+
}
|
|
4878
|
+
const taskDir = path24.join(projectAbs, projectConfig.tasks_dir, taskId);
|
|
4879
|
+
if (!fs25.existsSync(taskDir)) {
|
|
4880
|
+
throw Object.assign(new Error(`task not found: ${taskId}`), { code: "not_found" });
|
|
4881
|
+
}
|
|
4882
|
+
fs25.rmSync(taskDir, { recursive: true, force: true });
|
|
4883
|
+
ctx.notifyManifestChanged();
|
|
4884
|
+
return { task_dir: taskDir, deleted: true };
|
|
4885
|
+
},
|
|
4886
|
+
// ---------------------------------------------------------------------
|
|
4887
|
+
// Agent run lifecycle (P3.A scaffolding — handlers reject until P3.B
|
|
4888
|
+
// moves the actual spawn / tmux / supervise logic from hub's
|
|
4889
|
+
// agent-run-launcher.ts into the daemon).
|
|
4890
|
+
//
|
|
4891
|
+
// Wire shape (subject to refinement in P3.B):
|
|
4892
|
+
// agent_run_launch → {projectName, runId, runType, agentSlug,
|
|
4893
|
+
// workspace, prompt, ...} → {tmuxSession}
|
|
4894
|
+
// Daemon then streams agent_run_log /
|
|
4895
|
+
// agent_run_status frames over WS.
|
|
4896
|
+
// agent_run_cancel → {runId} → {ok: true}
|
|
4897
|
+
// ---------------------------------------------------------------------
|
|
4898
|
+
// Skill discovery on the daemon's host. Mirrors hub-side
|
|
4899
|
+
// resolveAgentSkill() — given a workspace + agent + filePath, returns
|
|
4900
|
+
// the matching AgentSkill record or null. P3.C uses this so the hub
|
|
4901
|
+
// can dispatch agent runs to remote daemons without itself needing FS
|
|
4902
|
+
// access to the workspace.
|
|
4903
|
+
async agent_skill_resolve(params) {
|
|
4904
|
+
const workspace = ensureString(params.workspace, "workspace");
|
|
4905
|
+
const agent2 = ensureString(params.agent, "agent");
|
|
4906
|
+
const filePath = ensureString(params.filePath, "filePath");
|
|
4907
|
+
if (agent2 !== "claude_code" && agent2 !== "codex" && agent2 !== "cursor") {
|
|
4908
|
+
throw Object.assign(new Error(`unknown skill agent: ${agent2}`), { code: "invalid_params" });
|
|
4909
|
+
}
|
|
4910
|
+
const projectSkills = getProjectAgentSkills(workspace);
|
|
4911
|
+
const globalSkills = getGlobalAgentSkills();
|
|
4912
|
+
const all = [...projectSkills, ...globalSkills];
|
|
4913
|
+
const resolved = path24.resolve(filePath);
|
|
4914
|
+
const skill = all.find(
|
|
4915
|
+
(s) => s.agent === agent2 && path24.resolve(s.filePath) === resolved
|
|
4916
|
+
) ?? null;
|
|
4917
|
+
return { skill };
|
|
4918
|
+
},
|
|
4919
|
+
async agent_run_launch(params) {
|
|
4920
|
+
const runId = ensureString(params.runId, "runId");
|
|
4921
|
+
const sessionName = ensureString(params.sessionName, "sessionName");
|
|
4922
|
+
const workspace = ensureString(params.workspace, "workspace");
|
|
4923
|
+
const scriptContent = ensureString(params.scriptContent, "scriptContent");
|
|
4924
|
+
const auxFiles = params.auxFiles && typeof params.auxFiles === "object" ? params.auxFiles : {};
|
|
4925
|
+
const promptContent = typeof params.promptContent === "string" ? params.promptContent : void 0;
|
|
4926
|
+
const { agentRunDir: agentRunDir2, sessionName: actualSessionName } = launchAgentRun({
|
|
4927
|
+
runId,
|
|
4928
|
+
sessionName,
|
|
4929
|
+
workspace,
|
|
4930
|
+
scriptContent,
|
|
4931
|
+
auxFiles,
|
|
4932
|
+
promptContent
|
|
4933
|
+
});
|
|
4934
|
+
return { agent_run_dir: agentRunDir2, tmux_session: actualSessionName };
|
|
4935
|
+
},
|
|
4936
|
+
async agent_run_cancel(params) {
|
|
4937
|
+
const runId = ensureString(params.runId, "runId");
|
|
4938
|
+
const result = cancelAgentRun(runId);
|
|
4939
|
+
if (!result.ok) {
|
|
4940
|
+
throw Object.assign(new Error(`no active agent run: ${runId}`), { code: "not_found" });
|
|
4941
|
+
}
|
|
4942
|
+
return { ok: true, tmux_session: result.sessionName };
|
|
4114
4943
|
}
|
|
4115
4944
|
};
|
|
4116
4945
|
|
|
4117
4946
|
// src/core/daemon-service/launchd.ts
|
|
4118
4947
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
4119
|
-
import
|
|
4120
|
-
import
|
|
4948
|
+
import fs27 from "fs";
|
|
4949
|
+
import path26 from "path";
|
|
4121
4950
|
|
|
4122
4951
|
// src/core/daemon-service/binary.ts
|
|
4123
|
-
import
|
|
4952
|
+
import fs26 from "fs";
|
|
4124
4953
|
import { fileURLToPath } from "url";
|
|
4125
4954
|
function resolveTask0Invocation() {
|
|
4126
4955
|
const node = process.execPath;
|
|
4127
4956
|
const argv1 = process.argv[1] ?? fileURLToPath(import.meta.url);
|
|
4128
|
-
const main2 =
|
|
4957
|
+
const main2 = fs26.realpathSync(argv1);
|
|
4129
4958
|
if (!isInstalledBuild(main2) && process.env.TASK0_ALLOW_DEV_SERVICE !== "1") {
|
|
4130
4959
|
throw new Error(
|
|
4131
4960
|
`Refusing to install autostart service pointing at ${main2}.
|
|
@@ -4141,8 +4970,8 @@ function isInstalledBuild(p) {
|
|
|
4141
4970
|
}
|
|
4142
4971
|
|
|
4143
4972
|
// src/core/daemon-service/paths.ts
|
|
4144
|
-
import
|
|
4145
|
-
import
|
|
4973
|
+
import os7 from "os";
|
|
4974
|
+
import path25 from "path";
|
|
4146
4975
|
|
|
4147
4976
|
// src/core/daemon-service/types.ts
|
|
4148
4977
|
var SERVICE_LABEL = "cc.cy0.task0";
|
|
@@ -4151,21 +4980,21 @@ var SERVICE_LABEL = "cc.cy0.task0";
|
|
|
4151
4980
|
function unitPath(scope) {
|
|
4152
4981
|
const platform = process.platform;
|
|
4153
4982
|
if (platform === "darwin") {
|
|
4154
|
-
return scope === "user" ?
|
|
4983
|
+
return scope === "user" ? path25.join(os7.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`) : path25.join("/", "Library", "LaunchDaemons", `${SERVICE_LABEL}.plist`);
|
|
4155
4984
|
}
|
|
4156
4985
|
if (platform === "linux") {
|
|
4157
|
-
return scope === "user" ?
|
|
4986
|
+
return scope === "user" ? path25.join(os7.homedir(), ".config", "systemd", "user", `${SERVICE_LABEL}.service`) : path25.join("/", "etc", "systemd", "system", `${SERVICE_LABEL}.service`);
|
|
4158
4987
|
}
|
|
4159
4988
|
throw new Error(`Unsupported platform for service install: ${platform}`);
|
|
4160
4989
|
}
|
|
4161
4990
|
function logDir() {
|
|
4162
|
-
return
|
|
4991
|
+
return path25.join(os7.homedir(), ".task0", "logs");
|
|
4163
4992
|
}
|
|
4164
4993
|
function logPaths() {
|
|
4165
4994
|
const dir = logDir();
|
|
4166
4995
|
return {
|
|
4167
|
-
out:
|
|
4168
|
-
err:
|
|
4996
|
+
out: path25.join(dir, "daemon.out.log"),
|
|
4997
|
+
err: path25.join(dir, "daemon.err.log")
|
|
4169
4998
|
};
|
|
4170
4999
|
}
|
|
4171
5000
|
|
|
@@ -4226,8 +5055,8 @@ function createLaunchdManager(scope) {
|
|
|
4226
5055
|
const logs = logPaths();
|
|
4227
5056
|
async function install() {
|
|
4228
5057
|
const inv = resolveTask0Invocation();
|
|
4229
|
-
|
|
4230
|
-
|
|
5058
|
+
fs27.mkdirSync(logDir(), { recursive: true });
|
|
5059
|
+
fs27.mkdirSync(path26.dirname(file), { recursive: true });
|
|
4231
5060
|
const body = renderPlist({
|
|
4232
5061
|
node: inv.node,
|
|
4233
5062
|
main: inv.main,
|
|
@@ -4236,7 +5065,7 @@ function createLaunchdManager(scope) {
|
|
|
4236
5065
|
out: logs.out,
|
|
4237
5066
|
err: logs.err
|
|
4238
5067
|
});
|
|
4239
|
-
|
|
5068
|
+
fs27.writeFileSync(file, body, { mode: 420 });
|
|
4240
5069
|
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
4241
5070
|
if (bootstrap.code !== 0) {
|
|
4242
5071
|
const already = /already loaded|service already bootstrapped/i.test(bootstrap.stderr);
|
|
@@ -4262,9 +5091,9 @@ function createLaunchdManager(scope) {
|
|
|
4262
5091
|
}
|
|
4263
5092
|
async function uninstall() {
|
|
4264
5093
|
run2("launchctl", ["bootout", serviceTarget(scope)]);
|
|
4265
|
-
if (
|
|
5094
|
+
if (fs27.existsSync(file)) {
|
|
4266
5095
|
run2("launchctl", ["unload", file]);
|
|
4267
|
-
|
|
5096
|
+
fs27.unlinkSync(file);
|
|
4268
5097
|
}
|
|
4269
5098
|
}
|
|
4270
5099
|
async function start() {
|
|
@@ -4283,7 +5112,7 @@ function createLaunchdManager(scope) {
|
|
|
4283
5112
|
}
|
|
4284
5113
|
}
|
|
4285
5114
|
async function status() {
|
|
4286
|
-
if (!
|
|
5115
|
+
if (!fs27.existsSync(file)) return "absent";
|
|
4287
5116
|
const printed = run2("launchctl", ["print", serviceTarget(scope)]);
|
|
4288
5117
|
if (printed.code !== 0) return "installed";
|
|
4289
5118
|
const out = printed.stdout;
|
|
@@ -4306,8 +5135,8 @@ function createLaunchdManager(scope) {
|
|
|
4306
5135
|
|
|
4307
5136
|
// src/core/daemon-service/systemd.ts
|
|
4308
5137
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
4309
|
-
import
|
|
4310
|
-
import
|
|
5138
|
+
import fs28 from "fs";
|
|
5139
|
+
import path27 from "path";
|
|
4311
5140
|
function shellEscape(s) {
|
|
4312
5141
|
if (!/[\s"\\$]/.test(s)) return s;
|
|
4313
5142
|
return `"${s.replace(/[\\"]/g, (m) => `\\${m}`)}"`;
|
|
@@ -4346,8 +5175,8 @@ function createSystemdManager(scope) {
|
|
|
4346
5175
|
const unitName = `${SERVICE_LABEL}.service`;
|
|
4347
5176
|
async function install() {
|
|
4348
5177
|
const inv = resolveTask0Invocation();
|
|
4349
|
-
|
|
4350
|
-
|
|
5178
|
+
fs28.mkdirSync(logDir(), { recursive: true });
|
|
5179
|
+
fs28.mkdirSync(path27.dirname(file), { recursive: true });
|
|
4351
5180
|
const body = renderUnit({
|
|
4352
5181
|
node: inv.node,
|
|
4353
5182
|
main: inv.main,
|
|
@@ -4356,7 +5185,7 @@ function createSystemdManager(scope) {
|
|
|
4356
5185
|
err: logs.err,
|
|
4357
5186
|
scope
|
|
4358
5187
|
});
|
|
4359
|
-
|
|
5188
|
+
fs28.writeFileSync(file, body, { mode: 420 });
|
|
4360
5189
|
const reload = run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4361
5190
|
if (reload.code !== 0) {
|
|
4362
5191
|
throw new Error(`systemctl daemon-reload failed: ${reload.stderr}`);
|
|
@@ -4365,8 +5194,8 @@ function createSystemdManager(scope) {
|
|
|
4365
5194
|
}
|
|
4366
5195
|
async function uninstall() {
|
|
4367
5196
|
run3("systemctl", [...scopeFlag(scope), "disable", "--now", unitName]);
|
|
4368
|
-
if (
|
|
4369
|
-
|
|
5197
|
+
if (fs28.existsSync(file)) {
|
|
5198
|
+
fs28.unlinkSync(file);
|
|
4370
5199
|
}
|
|
4371
5200
|
run3("systemctl", [...scopeFlag(scope), "daemon-reload"]);
|
|
4372
5201
|
}
|
|
@@ -4383,7 +5212,7 @@ function createSystemdManager(scope) {
|
|
|
4383
5212
|
}
|
|
4384
5213
|
}
|
|
4385
5214
|
async function status() {
|
|
4386
|
-
if (!
|
|
5215
|
+
if (!fs28.existsSync(file)) return "absent";
|
|
4387
5216
|
const res = run3("systemctl", [...scopeFlag(scope), "is-active", unitName]);
|
|
4388
5217
|
const out = res.stdout.trim();
|
|
4389
5218
|
if (out === "active") return "running";
|
|
@@ -4416,8 +5245,33 @@ function getServiceManager(scope) {
|
|
|
4416
5245
|
);
|
|
4417
5246
|
}
|
|
4418
5247
|
|
|
5248
|
+
// src/core/cli-version.ts
|
|
5249
|
+
import { readFileSync } from "fs";
|
|
5250
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5251
|
+
import path28 from "path";
|
|
5252
|
+
var cached2 = null;
|
|
5253
|
+
function readCliVersion() {
|
|
5254
|
+
if (cached2 !== null) return cached2;
|
|
5255
|
+
const here = path28.dirname(fileURLToPath2(import.meta.url));
|
|
5256
|
+
const candidates = [
|
|
5257
|
+
path28.resolve(here, "..", "package.json"),
|
|
5258
|
+
path28.resolve(here, "..", "..", "package.json")
|
|
5259
|
+
];
|
|
5260
|
+
for (const candidate of candidates) {
|
|
5261
|
+
try {
|
|
5262
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
5263
|
+
if (pkg.name === "@task0/cli" && pkg.version) {
|
|
5264
|
+
cached2 = pkg.version;
|
|
5265
|
+
return cached2;
|
|
5266
|
+
}
|
|
5267
|
+
} catch {
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
cached2 = "unknown";
|
|
5271
|
+
return cached2;
|
|
5272
|
+
}
|
|
5273
|
+
|
|
4419
5274
|
// src/commands/daemon.ts
|
|
4420
|
-
var DAEMON_VERSION = "0.1.0";
|
|
4421
5275
|
async function dispatchRpc(ws, id, method, params) {
|
|
4422
5276
|
const handler = rpcHandlers[method];
|
|
4423
5277
|
if (!handler) {
|
|
@@ -4425,7 +5279,8 @@ async function dispatchRpc(ws, id, method, params) {
|
|
|
4425
5279
|
return;
|
|
4426
5280
|
}
|
|
4427
5281
|
try {
|
|
4428
|
-
const
|
|
5282
|
+
const ctx = { notifyManifestChanged: () => pushManifest(ws) };
|
|
5283
|
+
const result = await handler(params ?? {}, ctx);
|
|
4429
5284
|
sendRpc(ws, { type: "rpc_response", id, result });
|
|
4430
5285
|
} catch (error2) {
|
|
4431
5286
|
const code = error2 && typeof error2 === "object" && "code" in error2 && typeof error2.code === "string" ? error2.code : "handler_error";
|
|
@@ -4438,6 +5293,44 @@ function sendRpc(ws, payload) {
|
|
|
4438
5293
|
ws.send(JSON.stringify(payload));
|
|
4439
5294
|
}
|
|
4440
5295
|
}
|
|
5296
|
+
function summariseTask(projectName, task2) {
|
|
5297
|
+
const { object_id = null, id, status, title, task_dir, ...extra } = task2;
|
|
5298
|
+
return {
|
|
5299
|
+
project: projectName,
|
|
5300
|
+
object_id: typeof object_id === "string" ? object_id : null,
|
|
5301
|
+
id,
|
|
5302
|
+
status,
|
|
5303
|
+
title,
|
|
5304
|
+
task_dir,
|
|
5305
|
+
extra
|
|
5306
|
+
};
|
|
5307
|
+
}
|
|
5308
|
+
function buildManifest() {
|
|
5309
|
+
const projectSources = loadConfig().sources.filter((source2) => source2.type === "project");
|
|
5310
|
+
const projects = projectSources.map((source2) => ({
|
|
5311
|
+
name: source2.name,
|
|
5312
|
+
path: source2.path,
|
|
5313
|
+
enabled: source2.enabled
|
|
5314
|
+
}));
|
|
5315
|
+
const tasks = [];
|
|
5316
|
+
const scanErrors = {};
|
|
5317
|
+
for (const source2 of projectSources) {
|
|
5318
|
+
if (!source2.enabled) continue;
|
|
5319
|
+
try {
|
|
5320
|
+
const result = scanProjectWithRepair(source2.path, source2.name);
|
|
5321
|
+
for (const task2 of result.tasks) tasks.push(summariseTask(source2.name, task2));
|
|
5322
|
+
if (result.errors.length > 0) scanErrors[source2.name] = result.errors;
|
|
5323
|
+
} catch (err) {
|
|
5324
|
+
scanErrors[source2.name] = [err instanceof Error ? err.message : String(err)];
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
return { type: "manifest", projects, tasks, scan_errors: scanErrors };
|
|
5328
|
+
}
|
|
5329
|
+
function pushManifest(ws) {
|
|
5330
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
5331
|
+
const manifest = buildManifest();
|
|
5332
|
+
ws.send(JSON.stringify(manifest));
|
|
5333
|
+
}
|
|
4441
5334
|
function fail6(message, code = 1) {
|
|
4442
5335
|
console.error(chalk19.red(message));
|
|
4443
5336
|
process.exit(code);
|
|
@@ -4508,7 +5401,7 @@ daemonCmd.command("register").description("Register this host with a central ser
|
|
|
4508
5401
|
throw error2;
|
|
4509
5402
|
}
|
|
4510
5403
|
const body = {
|
|
4511
|
-
hostname:
|
|
5404
|
+
hostname: os8.hostname(),
|
|
4512
5405
|
platform: process.platform,
|
|
4513
5406
|
name: opts.name
|
|
4514
5407
|
};
|
|
@@ -4616,15 +5509,20 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4616
5509
|
const hello = {
|
|
4617
5510
|
type: "hello",
|
|
4618
5511
|
daemon_id: identity.daemon_id,
|
|
4619
|
-
version:
|
|
5512
|
+
version: readCliVersion(),
|
|
4620
5513
|
hostname: identity.hostname,
|
|
4621
5514
|
platform: identity.platform
|
|
4622
5515
|
};
|
|
4623
5516
|
ws.send(JSON.stringify(hello));
|
|
4624
|
-
const
|
|
4625
|
-
const manifest = { type: "manifest", projects };
|
|
5517
|
+
const manifest = buildManifest();
|
|
4626
5518
|
ws.send(JSON.stringify(manifest));
|
|
4627
|
-
console.log(chalk19.dim(`pushed manifest: ${projects.length} project(s)`));
|
|
5519
|
+
console.log(chalk19.dim(`pushed manifest: ${manifest.projects.length} project(s)`));
|
|
5520
|
+
const sink = {
|
|
5521
|
+
send: (frame) => {
|
|
5522
|
+
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame));
|
|
5523
|
+
}
|
|
5524
|
+
};
|
|
5525
|
+
bindAgentRunFrameSink(sink);
|
|
4628
5526
|
});
|
|
4629
5527
|
ws.on("message", (raw) => {
|
|
4630
5528
|
let msg;
|
|
@@ -4642,10 +5540,16 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
4642
5540
|
console.error(chalk19.yellow(`server error: ${msg.message}`));
|
|
4643
5541
|
} else if (msg.type === "rpc_request") {
|
|
4644
5542
|
void dispatchRpc(ws, msg.id, msg.method, msg.params);
|
|
5543
|
+
} else if (msg.type === "agent_run_resume_request") {
|
|
5544
|
+
const sent = replayAfterRanges(msg.ranges);
|
|
5545
|
+
if (sent > 0) {
|
|
5546
|
+
console.log(chalk19.dim(`replayed ${sent} agent-run log frame(s) for hub`));
|
|
5547
|
+
}
|
|
4645
5548
|
}
|
|
4646
5549
|
});
|
|
4647
5550
|
ws.on("close", (code, reason) => {
|
|
4648
5551
|
activeWs = null;
|
|
5552
|
+
bindAgentRunFrameSink(null);
|
|
4649
5553
|
const reasonText = reason.toString("utf-8") || "no reason";
|
|
4650
5554
|
console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
|
|
4651
5555
|
if (code === 4001) {
|
|
@@ -4709,6 +5613,55 @@ daemonCmd.command("show [daemonId]").description("Show local daemon identity (no
|
|
|
4709
5613
|
const data = await jsonGet(`${base}/api/daemons/${encodeURIComponent(daemonId)}`);
|
|
4710
5614
|
console.log(JSON.stringify(data.daemon, null, 2));
|
|
4711
5615
|
});
|
|
5616
|
+
daemonCmd.command("status").description("Show this host's daemon status (identity, CLI version, service state)").option("--system", "Inspect the system-layer service status (default: user)").option("--json", "Emit a machine-readable JSON report instead of formatted text").action(async (opts) => {
|
|
5617
|
+
const identity = loadRequiredIdentity();
|
|
5618
|
+
const cliVersion = readCliVersion();
|
|
5619
|
+
const scope = opts.system ? "system" : "user";
|
|
5620
|
+
let serviceState = "unsupported";
|
|
5621
|
+
let unitPath2 = null;
|
|
5622
|
+
let serviceError = null;
|
|
5623
|
+
if (isPlatformSupported()) {
|
|
5624
|
+
try {
|
|
5625
|
+
const svc = getServiceManager(scope);
|
|
5626
|
+
serviceState = await svc.status();
|
|
5627
|
+
unitPath2 = svc.unitPath();
|
|
5628
|
+
} catch (error2) {
|
|
5629
|
+
serviceState = "unknown";
|
|
5630
|
+
serviceError = error2 instanceof Error ? error2.message : String(error2);
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
if (opts.json) {
|
|
5634
|
+
console.log(JSON.stringify({
|
|
5635
|
+
daemon_id: identity.daemon_id,
|
|
5636
|
+
name: identity.name,
|
|
5637
|
+
hostname: identity.hostname,
|
|
5638
|
+
platform: identity.platform,
|
|
5639
|
+
server_url: identity.server_url,
|
|
5640
|
+
registered_at: identity.registered_at,
|
|
5641
|
+
cli_version: cliVersion,
|
|
5642
|
+
service: {
|
|
5643
|
+
scope,
|
|
5644
|
+
state: serviceState,
|
|
5645
|
+
unit_path: unitPath2,
|
|
5646
|
+
error: serviceError
|
|
5647
|
+
}
|
|
5648
|
+
}, null, 2));
|
|
5649
|
+
return;
|
|
5650
|
+
}
|
|
5651
|
+
const stateColor = serviceState === "running" ? chalk19.green : serviceState === "stopped" || serviceState === "absent" ? chalk19.yellow : serviceState === "errored" || serviceState === "unknown" ? chalk19.red : chalk19.dim;
|
|
5652
|
+
const label = (s) => chalk19.bold(s.padEnd(11));
|
|
5653
|
+
console.log(`${label("daemon")}${identity.daemon_id} (${identity.name})`);
|
|
5654
|
+
console.log(`${label("host")}${identity.hostname} \xB7 ${identity.platform}`);
|
|
5655
|
+
console.log(`${label("server")}${identity.server_url}`);
|
|
5656
|
+
console.log(`${label("cli")}v${cliVersion}`);
|
|
5657
|
+
console.log(`${label("registered")}${identity.registered_at}`);
|
|
5658
|
+
if (isPlatformSupported()) {
|
|
5659
|
+
console.log(`${label("service")}${stateColor(serviceState)} (${scope})${unitPath2 ? ` \u2192 ${unitPath2}` : ""}`);
|
|
5660
|
+
if (serviceError) console.log(chalk19.dim(` ${serviceError}`));
|
|
5661
|
+
} else {
|
|
5662
|
+
console.log(`${label("service")}${chalk19.dim(`not supported on ${process.platform}`)}`);
|
|
5663
|
+
}
|
|
5664
|
+
});
|
|
4712
5665
|
daemonCmd.command("logout").description("Stop and uninstall the autostart service, then forget the locally stored daemon identity").option("--system", "Target the system-layer service").option("--keep-service", "Leave the installed service unit in place; only clear the identity file").action(async (opts) => {
|
|
4713
5666
|
const scope = opts.system ? "system" : "user";
|
|
4714
5667
|
if (!opts.keepService) {
|
|
@@ -4735,16 +5688,16 @@ daemonCmd.command("logout").description("Stop and uninstall the autostart servic
|
|
|
4735
5688
|
// src/commands/user.ts
|
|
4736
5689
|
import { Command as Command20 } from "commander";
|
|
4737
5690
|
import chalk20 from "chalk";
|
|
4738
|
-
var
|
|
5691
|
+
var DEFAULT_BASE = (process.env.TASK0_API_URL || "http://127.0.0.1:4318").replace(/\/$/, "");
|
|
4739
5692
|
function serverBase2() {
|
|
4740
|
-
return
|
|
5693
|
+
return DEFAULT_BASE;
|
|
4741
5694
|
}
|
|
4742
5695
|
function fail7(message, code = 1) {
|
|
4743
5696
|
console.error(chalk20.red(message));
|
|
4744
5697
|
process.exit(code);
|
|
4745
5698
|
}
|
|
4746
|
-
async function callServer(
|
|
4747
|
-
const url = `${serverBase2()}${
|
|
5699
|
+
async function callServer(path29, init = {}) {
|
|
5700
|
+
const url = `${serverBase2()}${path29}`;
|
|
4748
5701
|
let auth;
|
|
4749
5702
|
try {
|
|
4750
5703
|
auth = adminAuthHeader();
|
|
@@ -5344,16 +6297,7 @@ function captureTopLevel(err, options) {
|
|
|
5344
6297
|
}
|
|
5345
6298
|
|
|
5346
6299
|
// src/main.ts
|
|
5347
|
-
var TASK0_VERSION =
|
|
5348
|
-
function readVersion() {
|
|
5349
|
-
try {
|
|
5350
|
-
const here = path25.dirname(fileURLToPath2(import.meta.url));
|
|
5351
|
-
const pkg = JSON.parse(readFileSync(path25.resolve(here, "..", "package.json"), "utf-8"));
|
|
5352
|
-
return pkg.version ?? "unknown";
|
|
5353
|
-
} catch {
|
|
5354
|
-
return "unknown";
|
|
5355
|
-
}
|
|
5356
|
-
}
|
|
6300
|
+
var TASK0_VERSION = readCliVersion();
|
|
5357
6301
|
var program = new Command23().name("task0").description("Task-centric control layer for agent workflow").version(TASK0_VERSION);
|
|
5358
6302
|
program.addCommand(source);
|
|
5359
6303
|
program.addCommand(project);
|