@task0/cli 0.11.0 → 0.13.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 +169 -18
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -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";
|
|
@@ -7291,7 +7442,7 @@ function printAutomation(a) {
|
|
|
7291
7442
|
);
|
|
7292
7443
|
console.log(` ${chalk22.bold(a.title)}`);
|
|
7293
7444
|
console.log(
|
|
7294
|
-
` project=${a.project_name} agent=${a.agent_object_id} skill=${a.skill_file_path}`
|
|
7445
|
+
` project=${a.project_name} agent=${a.agent_object_id} skill=${a.skill_file_path ?? "(prompt-only)"}`
|
|
7295
7446
|
);
|
|
7296
7447
|
console.log(` rrule=${chalk22.dim(a.rrule)} tz=${a.timezone}`);
|
|
7297
7448
|
}
|
|
@@ -7349,7 +7500,7 @@ function safeResolveProject() {
|
|
|
7349
7500
|
return void 0;
|
|
7350
7501
|
}
|
|
7351
7502
|
}
|
|
7352
|
-
automation.command("create").description("Create a new automation").requiredOption("--title <title>", "Display name").option("-p, --project <name>", "Project name (defaults to cwd)").requiredOption("--agent <object_id>", "Agent object id (agt_*)").
|
|
7503
|
+
automation.command("create").description("Create a new automation").requiredOption("--title <title>", "Display name").option("-p, --project <name>", "Project name (defaults to cwd)").requiredOption("--agent <object_id>", "Agent object id (agt_*)").option("--skill <path>", "Absolute path to the skill markdown file (optional \u2014 omit for a prompt-only run)").requiredOption("--prompt <text>", "Prompt sent to the agent (additional context when a skill is set)").requiredOption("--schedule <preset>", "hourly|daily|weekdays|weekly|custom").option("--time <HH:MM>", "Local time for daily/weekdays/weekly presets", "09:00").option("--weekday <0-6>", "Day of week for weekly preset (Sun=0)", "1").option("--minute <0-59>", "Minute for hourly preset", "0").option("--rrule <rrule>", "Custom RRULE string (required when --schedule=custom)").option("--timezone <tz>", "IANA timezone", "UTC").option("--dtstart <iso>", "Schedule anchor (ISO 8601)").option("--model <name>", "Override agent model").option("--paused", "Create in paused status").option("--json", "Output JSON").action(async (opts) => {
|
|
7353
7504
|
const presets = ["hourly", "daily", "weekdays", "weekly", "custom"];
|
|
7354
7505
|
if (!presets.includes(opts.schedule)) {
|
|
7355
7506
|
fail8(`--schedule must be one of ${presets.join("|")}`);
|
|
@@ -7372,7 +7523,7 @@ automation.command("create").description("Create a new automation").requiredOpti
|
|
|
7372
7523
|
title: opts.title,
|
|
7373
7524
|
project_name: projectName,
|
|
7374
7525
|
agent_object_id: opts.agent,
|
|
7375
|
-
skill_file_path: opts.skill,
|
|
7526
|
+
skill_file_path: opts.skill ?? null,
|
|
7376
7527
|
prompt: opts.prompt,
|
|
7377
7528
|
schedule_preset: preset,
|
|
7378
7529
|
rrule,
|