@task0/cli 0.10.0 → 0.12.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 +315 -40
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1909,11 +1909,11 @@ async function request(method, pathname, body) {
|
|
|
1909
1909
|
}
|
|
1910
1910
|
}
|
|
1911
1911
|
var api = {
|
|
1912
|
-
get: (
|
|
1913
|
-
post: (
|
|
1914
|
-
put: (
|
|
1915
|
-
patch: (
|
|
1916
|
-
del: (
|
|
1912
|
+
get: (path33) => request("GET", path33),
|
|
1913
|
+
post: (path33, body) => request("POST", path33, body ?? {}),
|
|
1914
|
+
put: (path33, body) => request("PUT", path33, body ?? {}),
|
|
1915
|
+
patch: (path33, body) => request("PATCH", path33, body ?? {}),
|
|
1916
|
+
del: (path33) => request("DELETE", path33)
|
|
1917
1917
|
};
|
|
1918
1918
|
|
|
1919
1919
|
// src/commands/task/triage.ts
|
|
@@ -4763,6 +4763,9 @@ function emitAgentRunStatus(runId, status, opts = {}) {
|
|
|
4763
4763
|
phase: opts.phase ?? null,
|
|
4764
4764
|
exit_code: opts.exitCode ?? null,
|
|
4765
4765
|
error: opts.error ?? null,
|
|
4766
|
+
agent_session_id: opts.agentSessionId ?? null,
|
|
4767
|
+
agent_project: opts.agentProject ?? null,
|
|
4768
|
+
metadata: opts.metadata ?? null,
|
|
4766
4769
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
4767
4770
|
});
|
|
4768
4771
|
if (status === "completed" || status === "failed" || status === "killed") {
|
|
@@ -4872,6 +4875,28 @@ function checkTmuxAvailable() {
|
|
|
4872
4875
|
return { ok: false, reason: `tmux not available: ${err instanceof Error ? err.message : String(err)}` };
|
|
4873
4876
|
}
|
|
4874
4877
|
}
|
|
4878
|
+
function injectNodeBin(scriptContent) {
|
|
4879
|
+
const nodeBin = process.execPath;
|
|
4880
|
+
const nodeDir = path25.dirname(nodeBin);
|
|
4881
|
+
const home = process.env.HOME ?? "";
|
|
4882
|
+
const extraPath = [
|
|
4883
|
+
nodeDir,
|
|
4884
|
+
home ? `${home}/.local/share/mise/shims` : "",
|
|
4885
|
+
home ? `${home}/.local/bin` : "",
|
|
4886
|
+
home ? `${home}/bin` : "",
|
|
4887
|
+
home ? `${home}/.cargo/bin` : ""
|
|
4888
|
+
].filter(Boolean).join(":");
|
|
4889
|
+
const injected = [
|
|
4890
|
+
`NODE_BIN=${JSON.stringify(nodeBin)}`,
|
|
4891
|
+
`export PATH=${JSON.stringify(extraPath)}:"$PATH"`,
|
|
4892
|
+
""
|
|
4893
|
+
].join("\n");
|
|
4894
|
+
const shebangMatch = scriptContent.match(/^(#![^\n]*\n)/);
|
|
4895
|
+
if (shebangMatch) {
|
|
4896
|
+
return shebangMatch[1] + injected + scriptContent.slice(shebangMatch[1].length);
|
|
4897
|
+
}
|
|
4898
|
+
return injected + scriptContent;
|
|
4899
|
+
}
|
|
4875
4900
|
function isSessionAlive2(sessionName) {
|
|
4876
4901
|
try {
|
|
4877
4902
|
execFileSync(getTmuxBin(), ["has-session", "-t", sessionName], {
|
|
@@ -4911,7 +4936,17 @@ function deriveStatus(parsed) {
|
|
|
4911
4936
|
else if (raw === "error" || raw === "failed") status = "failed";
|
|
4912
4937
|
const phase = typeof parsed.phase === "string" ? parsed.phase : null;
|
|
4913
4938
|
const error2 = typeof parsed.error === "string" && parsed.error ? parsed.error : null;
|
|
4914
|
-
|
|
4939
|
+
const agentSessionId = typeof parsed.agentSessionId === "string" && parsed.agentSessionId ? parsed.agentSessionId : null;
|
|
4940
|
+
const agentProject = typeof parsed.agentProject === "string" && parsed.agentProject ? parsed.agentProject : null;
|
|
4941
|
+
let metadata = null;
|
|
4942
|
+
if (parsed.metadata && typeof parsed.metadata === "object") {
|
|
4943
|
+
const out = {};
|
|
4944
|
+
for (const [k, v] of Object.entries(parsed.metadata)) {
|
|
4945
|
+
if (typeof k === "string" && typeof v === "string") out[k] = v;
|
|
4946
|
+
}
|
|
4947
|
+
if (Object.keys(out).length > 0) metadata = out;
|
|
4948
|
+
}
|
|
4949
|
+
return { status, phase, error: error2, agentSessionId, agentProject, metadata };
|
|
4915
4950
|
}
|
|
4916
4951
|
function launchAgentRun(params) {
|
|
4917
4952
|
const tmuxCheck = checkTmuxAvailable();
|
|
@@ -4926,9 +4961,11 @@ function launchAgentRun(params) {
|
|
|
4926
4961
|
}
|
|
4927
4962
|
const runDir = ensureAgentRunDir(params.runId);
|
|
4928
4963
|
const scriptPath = path25.join(runDir, "script.sh");
|
|
4929
|
-
|
|
4964
|
+
const scriptWithNode = injectNodeBin(params.scriptContent);
|
|
4965
|
+
fs27.writeFileSync(scriptPath, scriptWithNode, { mode: 493 });
|
|
4930
4966
|
if (params.promptContent !== void 0) {
|
|
4931
|
-
|
|
4967
|
+
const expanded = params.promptContent.replace(/__TASK0_AGENT_RUN_DIR__/g, runDir);
|
|
4968
|
+
fs27.writeFileSync(path25.join(runDir, "prompt.txt"), expanded, "utf-8");
|
|
4932
4969
|
}
|
|
4933
4970
|
for (const [name, content] of Object.entries(params.auxFiles ?? {})) {
|
|
4934
4971
|
if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
|
|
@@ -4970,7 +5007,20 @@ function launchAgentRun(params) {
|
|
|
4970
5007
|
return;
|
|
4971
5008
|
}
|
|
4972
5009
|
if (!isSessionAlive2(cur.sessionName)) {
|
|
4973
|
-
|
|
5010
|
+
const final = readStatusFile(params.runId);
|
|
5011
|
+
const derived = final ? deriveStatus(final.parsed) : null;
|
|
5012
|
+
if (derived && (derived.status === "completed" || derived.status === "failed")) {
|
|
5013
|
+
emitAgentRunStatus(params.runId, derived.status, {
|
|
5014
|
+
phase: derived.phase,
|
|
5015
|
+
error: derived.error,
|
|
5016
|
+
agentSessionId: derived.agentSessionId,
|
|
5017
|
+
agentProject: derived.agentProject,
|
|
5018
|
+
metadata: derived.metadata
|
|
5019
|
+
});
|
|
5020
|
+
finishRun(params.runId, derived.status, { phase: derived.phase, error: derived.error });
|
|
5021
|
+
} else {
|
|
5022
|
+
finishRun(params.runId, "killed", { error: "tmux session disappeared" });
|
|
5023
|
+
}
|
|
4974
5024
|
clearInterval(sessionProbe);
|
|
4975
5025
|
}
|
|
4976
5026
|
}, SESSION_PROBE_MS);
|
|
@@ -4983,10 +5033,16 @@ function pollOne(runId) {
|
|
|
4983
5033
|
if (!current) return;
|
|
4984
5034
|
if (current.raw === entry.lastStatusJson) return;
|
|
4985
5035
|
entry.lastStatusJson = current.raw;
|
|
4986
|
-
const
|
|
4987
|
-
emitAgentRunStatus(runId, status, {
|
|
4988
|
-
|
|
4989
|
-
|
|
5036
|
+
const derived = deriveStatus(current.parsed);
|
|
5037
|
+
emitAgentRunStatus(runId, derived.status, {
|
|
5038
|
+
phase: derived.phase,
|
|
5039
|
+
error: derived.error,
|
|
5040
|
+
agentSessionId: derived.agentSessionId,
|
|
5041
|
+
agentProject: derived.agentProject,
|
|
5042
|
+
metadata: derived.metadata
|
|
5043
|
+
});
|
|
5044
|
+
if (derived.status === "completed" || derived.status === "failed") {
|
|
5045
|
+
finishRun(runId, derived.status, { phase: derived.phase, error: derived.error });
|
|
4990
5046
|
}
|
|
4991
5047
|
}
|
|
4992
5048
|
function finishRun(runId, status, opts = {}) {
|
|
@@ -6093,6 +6149,55 @@ function run2(cmd, args) {
|
|
|
6093
6149
|
const res = spawnSync6(cmd, args, { encoding: "utf-8" });
|
|
6094
6150
|
return { code: res.status ?? -1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
|
|
6095
6151
|
}
|
|
6152
|
+
function sleep2(ms) {
|
|
6153
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6154
|
+
}
|
|
6155
|
+
function launchdServicePid(scope) {
|
|
6156
|
+
const res = run2("launchctl", ["print", serviceTarget(scope)]);
|
|
6157
|
+
if (res.code !== 0) return null;
|
|
6158
|
+
const m = res.stdout.match(/\bpid\s*=\s*(\d+)/);
|
|
6159
|
+
if (!m) return null;
|
|
6160
|
+
const pid = parseInt(m[1], 10);
|
|
6161
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
6162
|
+
}
|
|
6163
|
+
function pidAlive(pid) {
|
|
6164
|
+
try {
|
|
6165
|
+
process.kill(pid, 0);
|
|
6166
|
+
return true;
|
|
6167
|
+
} catch (err) {
|
|
6168
|
+
return err.code === "EPERM";
|
|
6169
|
+
}
|
|
6170
|
+
}
|
|
6171
|
+
function findOrphanDaemonPids(mainPath, excludePid) {
|
|
6172
|
+
const res = run2("ps", ["-Ewwo", "pid=,command="]);
|
|
6173
|
+
if (res.code !== 0) return [];
|
|
6174
|
+
const expectedHome = process.env.TASK0_HOME ?? "";
|
|
6175
|
+
const pids = [];
|
|
6176
|
+
for (const line of res.stdout.split("\n")) {
|
|
6177
|
+
const m = line.match(/^\s*(\d+)\s+(.*)$/);
|
|
6178
|
+
if (!m) continue;
|
|
6179
|
+
const pid = parseInt(m[1], 10);
|
|
6180
|
+
if (!Number.isFinite(pid) || pid <= 0) continue;
|
|
6181
|
+
if (excludePid !== null && pid === excludePid) continue;
|
|
6182
|
+
if (pid === process.pid) continue;
|
|
6183
|
+
const cmd = m[2];
|
|
6184
|
+
if (!cmd.includes(mainPath)) continue;
|
|
6185
|
+
if (!/\bdaemon\s+run\b/.test(cmd)) continue;
|
|
6186
|
+
const homeMatch = cmd.match(/\bTASK0_HOME=([^\s]*)/);
|
|
6187
|
+
const actualHome = homeMatch ? homeMatch[1] : "";
|
|
6188
|
+
if (actualHome !== expectedHome) continue;
|
|
6189
|
+
pids.push(pid);
|
|
6190
|
+
}
|
|
6191
|
+
return pids;
|
|
6192
|
+
}
|
|
6193
|
+
async function waitForPidExit(pid, timeoutMs) {
|
|
6194
|
+
const start = Date.now();
|
|
6195
|
+
while (Date.now() - start < timeoutMs) {
|
|
6196
|
+
if (!pidAlive(pid)) return true;
|
|
6197
|
+
await sleep2(100);
|
|
6198
|
+
}
|
|
6199
|
+
return false;
|
|
6200
|
+
}
|
|
6096
6201
|
function createLaunchdManager(scope) {
|
|
6097
6202
|
const file = unitPath(scope);
|
|
6098
6203
|
const logs = logPaths();
|
|
@@ -6141,19 +6246,65 @@ function createLaunchdManager(scope) {
|
|
|
6141
6246
|
}
|
|
6142
6247
|
}
|
|
6143
6248
|
async function start() {
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6249
|
+
if (!fs30.existsSync(file)) {
|
|
6250
|
+
throw new Error(
|
|
6251
|
+
`service unit ${file} is missing \u2014 run \`task0 daemon register\` first to install it`
|
|
6252
|
+
);
|
|
6253
|
+
}
|
|
6254
|
+
const kick = run2("launchctl", ["kickstart", "-k", serviceTarget(scope)]);
|
|
6255
|
+
if (kick.code !== 0) {
|
|
6256
|
+
const bootstrap = run2("launchctl", ["bootstrap", domainTarget(scope), file]);
|
|
6257
|
+
if (bootstrap.code !== 0) {
|
|
6258
|
+
const legacy = run2("launchctl", ["load", "-w", file]);
|
|
6259
|
+
if (legacy.code !== 0) {
|
|
6260
|
+
throw new Error(
|
|
6261
|
+
`launchctl start failed: ${kick.stderr || bootstrap.stderr || legacy.stderr}`
|
|
6262
|
+
);
|
|
6263
|
+
}
|
|
6149
6264
|
}
|
|
6150
6265
|
}
|
|
6266
|
+
for (let i = 0; i < 30; i += 1) {
|
|
6267
|
+
if (launchdServicePid(scope) != null) return;
|
|
6268
|
+
await sleep2(100);
|
|
6269
|
+
}
|
|
6270
|
+
throw new Error(
|
|
6271
|
+
`daemon did not start within 3s \u2014 check logs at ${logs.err}`
|
|
6272
|
+
);
|
|
6151
6273
|
}
|
|
6152
6274
|
async function stop() {
|
|
6153
|
-
const
|
|
6154
|
-
|
|
6275
|
+
const launchdPid = launchdServicePid(scope);
|
|
6276
|
+
const bootout = run2("launchctl", ["bootout", serviceTarget(scope)]);
|
|
6277
|
+
if (bootout.code !== 0) {
|
|
6155
6278
|
run2("launchctl", ["unload", file]);
|
|
6156
6279
|
}
|
|
6280
|
+
if (launchdPid !== null) {
|
|
6281
|
+
const exited = await waitForPidExit(launchdPid, 5e3);
|
|
6282
|
+
if (!exited) {
|
|
6283
|
+
try {
|
|
6284
|
+
process.kill(launchdPid, "SIGKILL");
|
|
6285
|
+
} catch {
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
const inv = resolveTask0Invocation();
|
|
6290
|
+
const orphans = findOrphanDaemonPids(inv.main, launchdPid);
|
|
6291
|
+
for (const pid of orphans) {
|
|
6292
|
+
try {
|
|
6293
|
+
process.kill(pid, "SIGTERM");
|
|
6294
|
+
} catch {
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
if (orphans.length > 0) {
|
|
6298
|
+
await sleep2(500);
|
|
6299
|
+
for (const pid of orphans) {
|
|
6300
|
+
if (pidAlive(pid)) {
|
|
6301
|
+
try {
|
|
6302
|
+
process.kill(pid, "SIGKILL");
|
|
6303
|
+
} catch {
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6157
6308
|
}
|
|
6158
6309
|
async function status() {
|
|
6159
6310
|
if (!fs30.existsSync(file)) return "absent";
|
|
@@ -6327,15 +6478,119 @@ function readCliVersion() {
|
|
|
6327
6478
|
return cached2;
|
|
6328
6479
|
}
|
|
6329
6480
|
|
|
6481
|
+
// src/core/project-watcher.ts
|
|
6482
|
+
init_node();
|
|
6483
|
+
import fs32 from "fs";
|
|
6484
|
+
import path31 from "path";
|
|
6485
|
+
var ProjectWatcher = class {
|
|
6486
|
+
perProject = /* @__PURE__ */ new Map();
|
|
6487
|
+
debounceTimers = /* @__PURE__ */ new Map();
|
|
6488
|
+
debounceMs;
|
|
6489
|
+
onChange;
|
|
6490
|
+
constructor(opts) {
|
|
6491
|
+
this.onChange = opts.onChange;
|
|
6492
|
+
this.debounceMs = opts.debounceMs ?? 300;
|
|
6493
|
+
}
|
|
6494
|
+
/**
|
|
6495
|
+
* Reconcile watchers against the desired project list. Idempotent — call it
|
|
6496
|
+
* whenever the daemon's project set changes (project_add / project_remove /
|
|
6497
|
+
* project_set_enabled RPCs, or on reconnect with the latest config).
|
|
6498
|
+
*/
|
|
6499
|
+
setProjects(projects) {
|
|
6500
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
6501
|
+
for (const p of projects) {
|
|
6502
|
+
if (p.enabled) wanted.set(p.name, p);
|
|
6503
|
+
}
|
|
6504
|
+
for (const [name, entry] of this.perProject) {
|
|
6505
|
+
const target = wanted.get(name);
|
|
6506
|
+
if (!target || path31.resolve(target.path) !== entry.rootPath) {
|
|
6507
|
+
this.stopProject(name);
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6510
|
+
for (const [name, proj] of wanted) {
|
|
6511
|
+
if (!this.perProject.has(name)) {
|
|
6512
|
+
this.startProject(proj);
|
|
6513
|
+
}
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
close() {
|
|
6517
|
+
for (const name of [...this.perProject.keys()]) this.stopProject(name);
|
|
6518
|
+
for (const [, t] of this.debounceTimers) clearTimeout(t);
|
|
6519
|
+
this.debounceTimers.clear();
|
|
6520
|
+
}
|
|
6521
|
+
startProject(proj) {
|
|
6522
|
+
const rootPath = path31.resolve(proj.path);
|
|
6523
|
+
const projectYml = path31.join(rootPath, "task0.yml");
|
|
6524
|
+
const cfg = readYaml(projectYml);
|
|
6525
|
+
if (!cfg || typeof cfg.tasks_dir !== "string" || cfg.tasks_dir.length === 0) {
|
|
6526
|
+
return;
|
|
6527
|
+
}
|
|
6528
|
+
const tasksDir = path31.resolve(rootPath, cfg.tasks_dir);
|
|
6529
|
+
const watchers = [];
|
|
6530
|
+
try {
|
|
6531
|
+
const w = fs32.watch(tasksDir, { recursive: true }, () => this.notify(proj.name));
|
|
6532
|
+
w.on("error", () => {
|
|
6533
|
+
});
|
|
6534
|
+
watchers.push(w);
|
|
6535
|
+
} catch {
|
|
6536
|
+
}
|
|
6537
|
+
try {
|
|
6538
|
+
const parentDir = path31.dirname(tasksDir);
|
|
6539
|
+
const tasksDirName = path31.basename(tasksDir);
|
|
6540
|
+
const tarName = tasksDirName + ".tar";
|
|
6541
|
+
const w = fs32.watch(parentDir, (_event, filename) => {
|
|
6542
|
+
if (!filename) return;
|
|
6543
|
+
if (filename === tasksDirName || filename === tarName) {
|
|
6544
|
+
this.notify(proj.name);
|
|
6545
|
+
}
|
|
6546
|
+
});
|
|
6547
|
+
w.on("error", () => {
|
|
6548
|
+
});
|
|
6549
|
+
watchers.push(w);
|
|
6550
|
+
} catch {
|
|
6551
|
+
}
|
|
6552
|
+
this.perProject.set(proj.name, { watchers, rootPath });
|
|
6553
|
+
}
|
|
6554
|
+
stopProject(name) {
|
|
6555
|
+
const entry = this.perProject.get(name);
|
|
6556
|
+
if (!entry) return;
|
|
6557
|
+
for (const w of entry.watchers) {
|
|
6558
|
+
try {
|
|
6559
|
+
w.close();
|
|
6560
|
+
} catch {
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
this.perProject.delete(name);
|
|
6564
|
+
const timer = this.debounceTimers.get(name);
|
|
6565
|
+
if (timer) {
|
|
6566
|
+
clearTimeout(timer);
|
|
6567
|
+
this.debounceTimers.delete(name);
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
notify(projectName) {
|
|
6571
|
+
const prev = this.debounceTimers.get(projectName);
|
|
6572
|
+
if (prev) clearTimeout(prev);
|
|
6573
|
+
const t = setTimeout(() => {
|
|
6574
|
+
this.debounceTimers.delete(projectName);
|
|
6575
|
+
try {
|
|
6576
|
+
this.onChange(projectName);
|
|
6577
|
+
} catch (err) {
|
|
6578
|
+
console.error(`project watcher onChange threw for ${projectName}:`, err);
|
|
6579
|
+
}
|
|
6580
|
+
}, this.debounceMs);
|
|
6581
|
+
this.debounceTimers.set(projectName, t);
|
|
6582
|
+
}
|
|
6583
|
+
};
|
|
6584
|
+
|
|
6330
6585
|
// src/commands/daemon.ts
|
|
6331
|
-
async function dispatchRpc(ws, id, method, params) {
|
|
6586
|
+
async function dispatchRpc(ws, id, method, params, notifyManifestChanged) {
|
|
6332
6587
|
const handler = rpcHandlers[method];
|
|
6333
6588
|
if (!handler) {
|
|
6334
6589
|
sendRpc(ws, { type: "rpc_error", id, error: { code: "unknown_method", message: `unknown method: ${method}` } });
|
|
6335
6590
|
return;
|
|
6336
6591
|
}
|
|
6337
6592
|
try {
|
|
6338
|
-
const ctx = { notifyManifestChanged
|
|
6593
|
+
const ctx = { notifyManifestChanged };
|
|
6339
6594
|
const result = await handler(params ?? {}, ctx);
|
|
6340
6595
|
sendRpc(ws, { type: "rpc_response", id, result });
|
|
6341
6596
|
} catch (error2) {
|
|
@@ -6382,6 +6637,9 @@ function buildManifest() {
|
|
|
6382
6637
|
}
|
|
6383
6638
|
return { type: "manifest", projects, tasks, scan_errors: scanErrors };
|
|
6384
6639
|
}
|
|
6640
|
+
function currentWatchedProjects() {
|
|
6641
|
+
return loadConfig().sources.filter((source2) => source2.type === "project").map((source2) => ({ name: source2.name, path: source2.path, enabled: source2.enabled }));
|
|
6642
|
+
}
|
|
6385
6643
|
function pushManifest(ws) {
|
|
6386
6644
|
if (ws.readyState !== ws.OPEN) return;
|
|
6387
6645
|
const manifest = buildManifest();
|
|
@@ -6599,11 +6857,21 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6599
6857
|
let reconnectDelay = 1e3;
|
|
6600
6858
|
let shouldRun = true;
|
|
6601
6859
|
let activeWs = null;
|
|
6860
|
+
let activeWatcher = null;
|
|
6602
6861
|
function connect() {
|
|
6603
6862
|
const ws = new WebSocket(wsUrl, {
|
|
6604
6863
|
headers: { authorization: `Bearer ${identity.token}` }
|
|
6605
6864
|
});
|
|
6606
6865
|
activeWs = ws;
|
|
6866
|
+
const watcher = new ProjectWatcher({
|
|
6867
|
+
onChange: () => pushManifest(ws),
|
|
6868
|
+
debounceMs: 300
|
|
6869
|
+
});
|
|
6870
|
+
activeWatcher = watcher;
|
|
6871
|
+
const notifyManifestChanged = () => {
|
|
6872
|
+
pushManifest(ws);
|
|
6873
|
+
watcher.setProjects(currentWatchedProjects());
|
|
6874
|
+
};
|
|
6607
6875
|
ws.on("open", () => {
|
|
6608
6876
|
reconnectDelay = 1e3;
|
|
6609
6877
|
console.log(chalk19.green(`[${(/* @__PURE__ */ new Date()).toISOString()}] connected`));
|
|
@@ -6629,6 +6897,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6629
6897
|
const manifest = buildManifest();
|
|
6630
6898
|
ws.send(JSON.stringify(manifest));
|
|
6631
6899
|
console.log(chalk19.dim(`pushed manifest: ${manifest.projects.length} project(s)`));
|
|
6900
|
+
watcher.setProjects(currentWatchedProjects());
|
|
6632
6901
|
const sink = {
|
|
6633
6902
|
send: (frame) => {
|
|
6634
6903
|
if (ws.readyState === ws.OPEN) ws.send(JSON.stringify(frame));
|
|
@@ -6651,7 +6920,7 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6651
6920
|
} else if (msg.type === "error") {
|
|
6652
6921
|
console.error(chalk19.yellow(`server error: ${msg.message}`));
|
|
6653
6922
|
} else if (msg.type === "rpc_request") {
|
|
6654
|
-
void dispatchRpc(ws, msg.id, msg.method, msg.params);
|
|
6923
|
+
void dispatchRpc(ws, msg.id, msg.method, msg.params, notifyManifestChanged);
|
|
6655
6924
|
} else if (msg.type === "agent_run_resume_request") {
|
|
6656
6925
|
const sent = replayAfterRanges(msg.ranges);
|
|
6657
6926
|
if (sent > 0) {
|
|
@@ -6662,6 +6931,8 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6662
6931
|
ws.on("close", (code, reason) => {
|
|
6663
6932
|
activeWs = null;
|
|
6664
6933
|
bindAgentRunFrameSink(null);
|
|
6934
|
+
watcher.close();
|
|
6935
|
+
if (activeWatcher === watcher) activeWatcher = null;
|
|
6665
6936
|
const reasonText = reason.toString("utf-8") || "no reason";
|
|
6666
6937
|
console.log(chalk19.yellow(`[${(/* @__PURE__ */ new Date()).toISOString()}] disconnected (code=${code}, ${reasonText})`));
|
|
6667
6938
|
if (code === 4001) {
|
|
@@ -6679,6 +6950,10 @@ daemonCmd.command("run").description("Run the daemon WebSocket loop in foregroun
|
|
|
6679
6950
|
}
|
|
6680
6951
|
const shutdown = () => {
|
|
6681
6952
|
shouldRun = false;
|
|
6953
|
+
try {
|
|
6954
|
+
activeWatcher?.close();
|
|
6955
|
+
} catch {
|
|
6956
|
+
}
|
|
6682
6957
|
try {
|
|
6683
6958
|
activeWs?.close(1e3, "shutdown");
|
|
6684
6959
|
} catch {
|
|
@@ -6807,8 +7082,8 @@ function fail7(message, code = 1) {
|
|
|
6807
7082
|
console.error(chalk20.red(message));
|
|
6808
7083
|
process.exit(code);
|
|
6809
7084
|
}
|
|
6810
|
-
async function callServer(
|
|
6811
|
-
const url = `${serverBase2()}${
|
|
7085
|
+
async function callServer(path33, init = {}) {
|
|
7086
|
+
const url = `${serverBase2()}${path33}`;
|
|
6812
7087
|
let auth;
|
|
6813
7088
|
try {
|
|
6814
7089
|
auth = adminAuthHeader();
|
|
@@ -7313,8 +7588,8 @@ automation.command("runs <id>").description("List recent runs for an automation"
|
|
|
7313
7588
|
|
|
7314
7589
|
// src/commands/profile.ts
|
|
7315
7590
|
init_node();
|
|
7316
|
-
import
|
|
7317
|
-
import
|
|
7591
|
+
import fs33 from "fs";
|
|
7592
|
+
import path32 from "path";
|
|
7318
7593
|
import { Command as Command23 } from "commander";
|
|
7319
7594
|
import chalk23 from "chalk";
|
|
7320
7595
|
import yaml11 from "js-yaml";
|
|
@@ -7327,19 +7602,19 @@ function fail9(msg) {
|
|
|
7327
7602
|
process.exit(1);
|
|
7328
7603
|
}
|
|
7329
7604
|
function readDaemonAt(dir) {
|
|
7330
|
-
const file =
|
|
7331
|
-
if (!
|
|
7605
|
+
const file = path32.join(dir, "daemon.json");
|
|
7606
|
+
if (!fs33.existsSync(file)) return null;
|
|
7332
7607
|
try {
|
|
7333
|
-
return JSON.parse(
|
|
7608
|
+
return JSON.parse(fs33.readFileSync(file, "utf-8"));
|
|
7334
7609
|
} catch {
|
|
7335
7610
|
return null;
|
|
7336
7611
|
}
|
|
7337
7612
|
}
|
|
7338
7613
|
function readProfileApiUrl(name) {
|
|
7339
|
-
const file =
|
|
7340
|
-
if (!
|
|
7614
|
+
const file = path32.join(profileDir(name), "config.yml");
|
|
7615
|
+
if (!fs33.existsSync(file)) return void 0;
|
|
7341
7616
|
try {
|
|
7342
|
-
const data = yaml11.load(
|
|
7617
|
+
const data = yaml11.load(fs33.readFileSync(file, "utf-8"));
|
|
7343
7618
|
return data?.api_url;
|
|
7344
7619
|
} catch {
|
|
7345
7620
|
return void 0;
|
|
@@ -7368,10 +7643,10 @@ profile2.command("add <name>").description("Create a new profile directory (use
|
|
|
7368
7643
|
fail9(`"${DEFAULT_PROFILE_NAME}" is reserved; it always exists.`);
|
|
7369
7644
|
}
|
|
7370
7645
|
const dir = profileDir(name);
|
|
7371
|
-
if (
|
|
7646
|
+
if (fs33.existsSync(dir)) {
|
|
7372
7647
|
fail9(`Profile "${name}" already exists at ${dir}.`);
|
|
7373
7648
|
}
|
|
7374
|
-
|
|
7649
|
+
fs33.mkdirSync(dir, { recursive: true });
|
|
7375
7650
|
const prev = process.env.TASK0_PROFILE;
|
|
7376
7651
|
process.env.TASK0_PROFILE = name;
|
|
7377
7652
|
try {
|
|
@@ -7395,14 +7670,14 @@ profile2.command("remove <name>").description('Delete a profile directory (refus
|
|
|
7395
7670
|
fail9(`"${DEFAULT_PROFILE_NAME}" cannot be removed.`);
|
|
7396
7671
|
}
|
|
7397
7672
|
const dir = profileDir(name);
|
|
7398
|
-
if (!
|
|
7673
|
+
if (!fs33.existsSync(dir)) {
|
|
7399
7674
|
fail9(`Profile "${name}" not found.`);
|
|
7400
7675
|
}
|
|
7401
7676
|
const current = currentProfileName();
|
|
7402
7677
|
if (current === name && !opts.force) {
|
|
7403
7678
|
fail9(`Profile "${name}" is current. Re-run with --force to remove it.`);
|
|
7404
7679
|
}
|
|
7405
|
-
|
|
7680
|
+
fs33.rmSync(dir, { recursive: true, force: true });
|
|
7406
7681
|
if (current === name) {
|
|
7407
7682
|
writeCurrentProfile(null);
|
|
7408
7683
|
console.log(chalk23.dim('Current profile reset to "default".'));
|
|
@@ -7413,7 +7688,7 @@ profile2.command("use <name>").description("Set the current profile").action((na
|
|
|
7413
7688
|
if (!isValidProfileName(name)) {
|
|
7414
7689
|
fail9(`Invalid profile name "${name}".`);
|
|
7415
7690
|
}
|
|
7416
|
-
if (!
|
|
7691
|
+
if (!fs33.existsSync(profileDir(name))) {
|
|
7417
7692
|
const names = listProfileNames();
|
|
7418
7693
|
fail9(`Profile "${name}" not found. Available: ${names.join(", ")}.`);
|
|
7419
7694
|
}
|
|
@@ -7448,7 +7723,7 @@ profile2.command("show [name]").description("Show a profile's configuration and
|
|
|
7448
7723
|
fail9(`Invalid profile name "${target}".`);
|
|
7449
7724
|
}
|
|
7450
7725
|
const dir = profileDir(target);
|
|
7451
|
-
if (!
|
|
7726
|
+
if (!fs33.existsSync(dir)) {
|
|
7452
7727
|
fail9(`Profile "${target}" not found.`);
|
|
7453
7728
|
}
|
|
7454
7729
|
const current = currentProfileName();
|
|
@@ -7458,7 +7733,7 @@ profile2.command("show [name]").description("Show a profile's configuration and
|
|
|
7458
7733
|
const effective = envOverride ?? apiUrl;
|
|
7459
7734
|
console.log(`${chalk23.bold(target)}${current === target ? chalk23.green(" (current)") : ""}`);
|
|
7460
7735
|
console.log(` dir: ${dir}`);
|
|
7461
|
-
console.log(` config: ${
|
|
7736
|
+
console.log(` config: ${path32.join(dir, "config.yml")}`);
|
|
7462
7737
|
if (envOverride && apiUrl && envOverride !== apiUrl) {
|
|
7463
7738
|
console.log(` api_url: ${envOverride} ${chalk23.dim("(TASK0_API_URL env, overrides profile)")}`);
|
|
7464
7739
|
console.log(` ${chalk23.dim(`profile: ${apiUrl}`)}`);
|