@aiaiai-pt/martha-cli 0.27.1 → 0.30.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/CHANGELOG.md +19 -0
- package/dist/index.js +330 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.30.0] — 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Added — #761 runner heartbeat + poll-based cancel
|
|
10
|
+
- `martha runner` now runs each task's goal **asynchronously with a heartbeat loop** (every `min(lease_timeout_s/3, 60)`s). Heartbeats renew the lease, so a task longer than its lease is no longer reclaimed by the lease reaper while still executing on the host (the Slice-0 double-execution hazard).
|
|
11
|
+
- Cancelling a claimed task (`martha tasks cancel <id>` or the admin route) now actually stops it: the heartbeat reply carries `signal: "cancel"`, the runner kills the goal's **entire process tree** (SIGTERM the group, SIGKILL after 5s), and reports the partial output with `status: "cancelled"` — accepted idempotently server-side. Worst-case kill latency is one heartbeat interval (≤60s).
|
|
12
|
+
- Heartbeat failures (network blips, 409s) never affect the running job — only an explicit `signal: "cancel"` kills.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- The runner no longer "backgrounds" goals after 20s (a Slice-0 artifact that reported long tasks as `completed` while they were still running) — it now waits for real completion, kept alive by the heartbeat loop.
|
|
16
|
+
|
|
17
|
+
<!-- 0.29.0 shipped 2026-07-02 (#751 asks + trigger-tasking) without a CHANGELOG section — see git history. -->
|
|
18
|
+
|
|
19
|
+
## [0.28.0] — 2026-06-30
|
|
20
|
+
|
|
21
|
+
### Added — #714 Slice 1 external-agent `event.emit` binding surfaces
|
|
22
|
+
- `martha agents grant-trigger <agent> <trigger>` binds an agent's emitted events to a trigger (so an agent-emitted event may fire it); `martha agents revoke-trigger <agent> <trigger>` removes the binding; `martha agents triggers <agent>` lists the triggers an agent may fire.
|
|
23
|
+
- `martha triggers allow-agent <trigger> <agentId>` accepts an agent as a source for a trigger; `martha triggers disallow-agent <trigger> <agentId>` removes it; `martha triggers agents <trigger>` lists the accepted agent sources.
|
|
24
|
+
- Both directions mutate the same `agent_trigger_access` binding (#714) and reflect each other. All support `--json`. The trigger is resolved by name → id; the agent side keys on the agent's stored (server-normalized) name and the agent id, respectively. Cross-tenant agent/trigger → 404 (IDOR guard).
|
|
25
|
+
|
|
7
26
|
## [0.20.0] — 2026-06-21
|
|
8
27
|
|
|
9
28
|
### Added — #649 agent→agent delegation surfaces
|
package/dist/index.js
CHANGED
|
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
8
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
9
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
10
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
11
|
for (let key of __getOwnPropNames(mod))
|
|
24
12
|
if (!__hasOwnProp.call(to, key))
|
|
25
13
|
__defProp(to, key, {
|
|
26
|
-
get:
|
|
14
|
+
get: () => mod[key],
|
|
27
15
|
enumerable: true
|
|
28
16
|
});
|
|
29
|
-
if (canCache)
|
|
30
|
-
cache.set(mod, to);
|
|
31
17
|
return to;
|
|
32
18
|
};
|
|
33
19
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
-
var __returnValue = (v) => v;
|
|
35
|
-
function __exportSetter(name, newValue) {
|
|
36
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
-
}
|
|
38
20
|
var __export = (target, all) => {
|
|
39
21
|
for (var name in all)
|
|
40
22
|
__defProp(target, name, {
|
|
41
23
|
get: all[name],
|
|
42
24
|
enumerable: true,
|
|
43
25
|
configurable: true,
|
|
44
|
-
set:
|
|
26
|
+
set: (newValue) => all[name] = () => newValue
|
|
45
27
|
});
|
|
46
28
|
};
|
|
47
29
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -1019,7 +1001,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1019
1001
|
this._exitCallback = (err) => {
|
|
1020
1002
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1021
1003
|
throw err;
|
|
1022
|
-
}
|
|
1004
|
+
} else {}
|
|
1023
1005
|
};
|
|
1024
1006
|
}
|
|
1025
1007
|
return this;
|
|
@@ -11081,7 +11063,7 @@ init_config();
|
|
|
11081
11063
|
init_errors();
|
|
11082
11064
|
|
|
11083
11065
|
// src/version.ts
|
|
11084
|
-
var CLI_VERSION = "0.
|
|
11066
|
+
var CLI_VERSION = "0.30.0";
|
|
11085
11067
|
|
|
11086
11068
|
// src/commands/sessions.ts
|
|
11087
11069
|
function relativeTime(iso) {
|
|
@@ -11439,6 +11421,68 @@ async function runHostCommand(command, toolCallId, opts) {
|
|
|
11439
11421
|
function safeName(s) {
|
|
11440
11422
|
return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
|
|
11441
11423
|
}
|
|
11424
|
+
async function startHostCommand(command, toolCallId, opts) {
|
|
11425
|
+
const dir = runnerDir(opts.cwd);
|
|
11426
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
11427
|
+
const logPath = path4.join(dir, `${safeName(toolCallId)}.log`);
|
|
11428
|
+
await fs5.writeFile(logPath, "");
|
|
11429
|
+
const fd = openSync(logPath, "a");
|
|
11430
|
+
const cap = opts.maxOutputBytes ?? MAX_OUTPUT;
|
|
11431
|
+
const readLog = async () => {
|
|
11432
|
+
try {
|
|
11433
|
+
const buf = await fs5.readFile(logPath);
|
|
11434
|
+
return buf.subarray(-cap).toString("utf8");
|
|
11435
|
+
} catch {
|
|
11436
|
+
return "";
|
|
11437
|
+
}
|
|
11438
|
+
};
|
|
11439
|
+
const child = spawn(command, {
|
|
11440
|
+
shell: true,
|
|
11441
|
+
cwd: opts.cwd,
|
|
11442
|
+
detached: true,
|
|
11443
|
+
stdio: ["ignore", fd, fd]
|
|
11444
|
+
});
|
|
11445
|
+
closeSync(fd);
|
|
11446
|
+
let exited = false;
|
|
11447
|
+
const done = new Promise((resolve, reject) => {
|
|
11448
|
+
let settled = false;
|
|
11449
|
+
child.on("error", (e) => {
|
|
11450
|
+
if (settled)
|
|
11451
|
+
return;
|
|
11452
|
+
settled = true;
|
|
11453
|
+
exited = true;
|
|
11454
|
+
reject(e);
|
|
11455
|
+
});
|
|
11456
|
+
child.on("close", async (code) => {
|
|
11457
|
+
if (settled)
|
|
11458
|
+
return;
|
|
11459
|
+
settled = true;
|
|
11460
|
+
exited = true;
|
|
11461
|
+
resolve({
|
|
11462
|
+
stdout: await readLog(),
|
|
11463
|
+
stderr: "",
|
|
11464
|
+
exit_code: code ?? -1,
|
|
11465
|
+
log_path: logPath
|
|
11466
|
+
});
|
|
11467
|
+
});
|
|
11468
|
+
});
|
|
11469
|
+
const kill = (graceMs = 5000) => {
|
|
11470
|
+
if (exited || !child.pid)
|
|
11471
|
+
return;
|
|
11472
|
+
try {
|
|
11473
|
+
process.kill(-child.pid, "SIGTERM");
|
|
11474
|
+
} catch {}
|
|
11475
|
+
const escalate = setTimeout(() => {
|
|
11476
|
+
if (exited || !child.pid)
|
|
11477
|
+
return;
|
|
11478
|
+
try {
|
|
11479
|
+
process.kill(-child.pid, "SIGKILL");
|
|
11480
|
+
} catch {}
|
|
11481
|
+
}, graceMs);
|
|
11482
|
+
escalate.unref?.();
|
|
11483
|
+
};
|
|
11484
|
+
return { pid: child.pid, done, kill };
|
|
11485
|
+
}
|
|
11442
11486
|
function mintExecSecret() {
|
|
11443
11487
|
return randomBytes2(32).toString("hex");
|
|
11444
11488
|
}
|
|
@@ -13512,7 +13556,7 @@ function registerConfigCommand(program2) {
|
|
|
13512
13556
|
config.current_profile = name;
|
|
13513
13557
|
}
|
|
13514
13558
|
saveConfig(config);
|
|
13515
|
-
if (
|
|
13559
|
+
if (program2.opts().json) {
|
|
13516
13560
|
console.log(JSON.stringify({
|
|
13517
13561
|
name,
|
|
13518
13562
|
profile,
|
|
@@ -13556,7 +13600,7 @@ function registerConfigCommand(program2) {
|
|
|
13556
13600
|
config.current_profile = remaining[0] ?? "default";
|
|
13557
13601
|
}
|
|
13558
13602
|
saveConfig(config);
|
|
13559
|
-
if (
|
|
13603
|
+
if (program2.opts().json) {
|
|
13560
13604
|
console.log(JSON.stringify({ name, deleted: true }));
|
|
13561
13605
|
return;
|
|
13562
13606
|
}
|
|
@@ -15066,6 +15110,50 @@ var agentsConfig = {
|
|
|
15066
15110
|
}
|
|
15067
15111
|
console.log(`Removed function '${functionName}' from agent '${agent}'`);
|
|
15068
15112
|
});
|
|
15113
|
+
parentCmd.command("grant-trigger <agent> <trigger>").description("Allow an agent's emitted events to fire a trigger").action(async (agent, trigger) => {
|
|
15114
|
+
const ctx = getCtx();
|
|
15115
|
+
const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
|
|
15116
|
+
const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(agent)}/triggers`, { trigger_id: t.id });
|
|
15117
|
+
if (isJson()) {
|
|
15118
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15119
|
+
return;
|
|
15120
|
+
}
|
|
15121
|
+
console.log(`Granted trigger '${trigger}' to agent '${agent}'`);
|
|
15122
|
+
});
|
|
15123
|
+
parentCmd.command("revoke-trigger <agent> <trigger>").description("Revoke an agent's binding to a trigger").action(async (agent, trigger) => {
|
|
15124
|
+
const ctx = getCtx();
|
|
15125
|
+
const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
|
|
15126
|
+
await ctx.api.del(`${API_PATH}/${encodeURIComponent(agent)}/triggers/${encodeURIComponent(String(t.id))}`);
|
|
15127
|
+
if (isJson()) {
|
|
15128
|
+
console.log(JSON.stringify({ agent, trigger, removed: true }));
|
|
15129
|
+
return;
|
|
15130
|
+
}
|
|
15131
|
+
console.log(`Revoked trigger '${trigger}' from agent '${agent}'`);
|
|
15132
|
+
});
|
|
15133
|
+
parentCmd.command("triggers <agent>").description("List triggers an agent may fire").action(async (agent) => {
|
|
15134
|
+
const ctx = getCtx();
|
|
15135
|
+
const items = await ctx.api.get(`${API_PATH}/${encodeURIComponent(agent)}/triggers`);
|
|
15136
|
+
if (isJson()) {
|
|
15137
|
+
console.log(JSON.stringify(items, null, 2));
|
|
15138
|
+
return;
|
|
15139
|
+
}
|
|
15140
|
+
if (items.length === 0) {
|
|
15141
|
+
console.log(source_default.dim("No triggers bound to this agent."));
|
|
15142
|
+
return;
|
|
15143
|
+
}
|
|
15144
|
+
const nameW = Math.max(7, ...items.map((b) => String(b.trigger_name ?? "").length));
|
|
15145
|
+
const header = ["TRIGGER", "GRANTED BY", "GRANTED AT"].map((h, i) => h.padEnd([nameW, 16, 20][i])).join(" ");
|
|
15146
|
+
console.log(source_default.bold(header));
|
|
15147
|
+
console.log(source_default.dim("-".repeat(header.length)));
|
|
15148
|
+
for (const b of items) {
|
|
15149
|
+
const date = b.granted_at ? new Date(b.granted_at).toISOString().slice(0, 16).replace("T", " ") : "-";
|
|
15150
|
+
console.log([
|
|
15151
|
+
String(b.trigger_name ?? "").padEnd(nameW),
|
|
15152
|
+
String(b.granted_by ?? "-").padEnd(16),
|
|
15153
|
+
date.padEnd(20)
|
|
15154
|
+
].join(" "));
|
|
15155
|
+
}
|
|
15156
|
+
});
|
|
15069
15157
|
parentCmd.command("provision-auth <agent>").description("Provision, switch, or rotate agent auth credentials").requiredOption("--method <method>", "Auth method: service-account or api-key").action(async (agent, opts) => {
|
|
15070
15158
|
const ctx = getCtx();
|
|
15071
15159
|
const methodMap = {
|
|
@@ -15495,6 +15583,51 @@ var triggersConfig = {
|
|
|
15495
15583
|
}
|
|
15496
15584
|
}
|
|
15497
15585
|
});
|
|
15586
|
+
parentCmd.command("allow-agent <trigger> <agentId>").description("Accept an agent's emitted events as a source for a trigger").action(async (trigger, agentId) => {
|
|
15587
|
+
const ctx = getCtx();
|
|
15588
|
+
const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
|
|
15589
|
+
const result = await ctx.api.post(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents`, { agent_id: agentId });
|
|
15590
|
+
if (isJson()) {
|
|
15591
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15592
|
+
return;
|
|
15593
|
+
}
|
|
15594
|
+
console.log(`Accepted agent '${agentId}' as a source for '${trigger}'`);
|
|
15595
|
+
});
|
|
15596
|
+
parentCmd.command("disallow-agent <trigger> <agentId>").description("Remove an agent as a source for a trigger").action(async (trigger, agentId) => {
|
|
15597
|
+
const ctx = getCtx();
|
|
15598
|
+
const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
|
|
15599
|
+
await ctx.api.del(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents/${encodeURIComponent(agentId)}`);
|
|
15600
|
+
if (isJson()) {
|
|
15601
|
+
console.log(JSON.stringify({ trigger, agent_id: agentId, removed: true }));
|
|
15602
|
+
return;
|
|
15603
|
+
}
|
|
15604
|
+
console.log(`Removed agent '${agentId}' as a source for '${trigger}'`);
|
|
15605
|
+
});
|
|
15606
|
+
parentCmd.command("agents <trigger>").description("List the agents accepted as sources for a trigger").action(async (trigger) => {
|
|
15607
|
+
const ctx = getCtx();
|
|
15608
|
+
const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
|
|
15609
|
+
const items = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents`);
|
|
15610
|
+
if (isJson()) {
|
|
15611
|
+
console.log(JSON.stringify(items, null, 2));
|
|
15612
|
+
return;
|
|
15613
|
+
}
|
|
15614
|
+
if (items.length === 0) {
|
|
15615
|
+
console.log(source_default.dim("No agent sources accepted for this trigger."));
|
|
15616
|
+
return;
|
|
15617
|
+
}
|
|
15618
|
+
const nameW = Math.max(5, ...items.map((b) => String(b.agent_name ?? "").length));
|
|
15619
|
+
const header = ["AGENT", "AGENT ID", "GRANTED AT"].map((h, i) => h.padEnd([nameW, 36, 20][i])).join(" ");
|
|
15620
|
+
console.log(source_default.bold(header));
|
|
15621
|
+
console.log(source_default.dim("-".repeat(header.length)));
|
|
15622
|
+
for (const b of items) {
|
|
15623
|
+
const date = b.granted_at ? new Date(b.granted_at).toISOString().slice(0, 16).replace("T", " ") : "-";
|
|
15624
|
+
console.log([
|
|
15625
|
+
String(b.agent_name ?? "-").padEnd(nameW),
|
|
15626
|
+
String(b.agent_id ?? "-").padEnd(36),
|
|
15627
|
+
date.padEnd(20)
|
|
15628
|
+
].join(" "));
|
|
15629
|
+
}
|
|
15630
|
+
});
|
|
15498
15631
|
}
|
|
15499
15632
|
};
|
|
15500
15633
|
|
|
@@ -16488,6 +16621,119 @@ function registerApprovalCommands(program2) {
|
|
|
16488
16621
|
});
|
|
16489
16622
|
}
|
|
16490
16623
|
|
|
16624
|
+
// src/commands/asks.ts
|
|
16625
|
+
init_errors();
|
|
16626
|
+
var KINDS = ["agent_question", "agent_approval_request"];
|
|
16627
|
+
function statusColor(status) {
|
|
16628
|
+
if (status === "approved")
|
|
16629
|
+
return source_default.green(status);
|
|
16630
|
+
if (status === "rejected")
|
|
16631
|
+
return source_default.red(status);
|
|
16632
|
+
if (status === "expired")
|
|
16633
|
+
return source_default.dim(status);
|
|
16634
|
+
return source_default.yellow(status);
|
|
16635
|
+
}
|
|
16636
|
+
function renderAsk(a) {
|
|
16637
|
+
const lines = [
|
|
16638
|
+
`${source_default.bold(a.case_id ?? "?")} ${statusColor(a.status ?? "?")} ${source_default.dim(a.kind ?? "")}`,
|
|
16639
|
+
` ${a.reason ?? ""}`
|
|
16640
|
+
];
|
|
16641
|
+
if (a.context_summary)
|
|
16642
|
+
lines.push(` ${source_default.dim(a.context_summary)}`);
|
|
16643
|
+
if (a.decision) {
|
|
16644
|
+
lines.push(` ${source_default.bold("decision:")} ${statusColor(a.decision)}`);
|
|
16645
|
+
if (a.comment)
|
|
16646
|
+
lines.push(` ${source_default.bold("answer:")} ${a.comment}`);
|
|
16647
|
+
}
|
|
16648
|
+
if (a.expires_at)
|
|
16649
|
+
lines.push(` ${source_default.dim(`expires ${a.expires_at}`)}`);
|
|
16650
|
+
return lines.join(`
|
|
16651
|
+
`);
|
|
16652
|
+
}
|
|
16653
|
+
function registerAskCommands(program2) {
|
|
16654
|
+
const cmd = program2.command("asks").description("Agent asks — questions/approval requests to the human queue (agent credentials)");
|
|
16655
|
+
function getCtx() {
|
|
16656
|
+
const ctx = createContext({
|
|
16657
|
+
profileOverride: program2.opts().profile,
|
|
16658
|
+
verbose: program2.opts().verbose
|
|
16659
|
+
});
|
|
16660
|
+
if (program2.opts().apiUrl)
|
|
16661
|
+
ctx.profile.api_url = program2.opts().apiUrl;
|
|
16662
|
+
return ctx;
|
|
16663
|
+
}
|
|
16664
|
+
function isJson() {
|
|
16665
|
+
return !!program2.opts().json;
|
|
16666
|
+
}
|
|
16667
|
+
cmd.command("create").description("File an ask (the server attributes it to the token's agent)").requiredOption("--reason <text>", "The question / what approval is requested").option("--kind <kind>", `${KINDS.join(" | ")} (default agent_question)`, "agent_question").option("--context <text>", "Context summary for the reviewer").option("--questions <json>", "Structured questions JSON array (ask_user shape: question/header/options/multiSelect)").option("--expires-in <seconds>", "Auto-expire the ask after N seconds").option("--idempotency-key <key>", "Dedup key — retries with the same key do not double-file").action(async (opts) => {
|
|
16668
|
+
if (!KINDS.includes(opts.kind)) {
|
|
16669
|
+
throw new CLIError(`--kind must be one of: ${KINDS.join(", ")}`, 4 /* Validation */);
|
|
16670
|
+
}
|
|
16671
|
+
let questions;
|
|
16672
|
+
if (opts.questions) {
|
|
16673
|
+
try {
|
|
16674
|
+
questions = JSON.parse(opts.questions);
|
|
16675
|
+
} catch {
|
|
16676
|
+
throw new CLIError("--questions must be valid JSON", 4 /* Validation */);
|
|
16677
|
+
}
|
|
16678
|
+
}
|
|
16679
|
+
let expiresIn;
|
|
16680
|
+
if (opts.expiresIn) {
|
|
16681
|
+
expiresIn = parseInt(opts.expiresIn, 10);
|
|
16682
|
+
if (isNaN(expiresIn) || expiresIn < 60) {
|
|
16683
|
+
throw new CLIError("--expires-in must be an integer >= 60 (seconds)", 4 /* Validation */);
|
|
16684
|
+
}
|
|
16685
|
+
}
|
|
16686
|
+
const ctx = getCtx();
|
|
16687
|
+
const ask = await ctx.api.post("/api/agent/asks", {
|
|
16688
|
+
kind: opts.kind,
|
|
16689
|
+
reason: opts.reason,
|
|
16690
|
+
context_summary: opts.context,
|
|
16691
|
+
questions,
|
|
16692
|
+
expires_in_s: expiresIn,
|
|
16693
|
+
idempotency_key: opts.idempotencyKey
|
|
16694
|
+
});
|
|
16695
|
+
if (isJson()) {
|
|
16696
|
+
console.log(JSON.stringify(ask, null, 2));
|
|
16697
|
+
return;
|
|
16698
|
+
}
|
|
16699
|
+
console.log(`Filed ask ${source_default.bold(ask.case_id)} (${ask.status})`);
|
|
16700
|
+
console.log(source_default.dim(`Poll the answer with: martha asks get ${ask.case_id}`));
|
|
16701
|
+
});
|
|
16702
|
+
cmd.command("list").description("List this agent's own asks (newest first)").option("--status <status>", "Filter by status (pending/approved/rejected/expired)").option("--limit <n>", "Max results", "50").action(async (opts) => {
|
|
16703
|
+
const limitN = parseInt(opts.limit, 10);
|
|
16704
|
+
if (isNaN(limitN) || limitN < 1) {
|
|
16705
|
+
throw new CLIError("--limit must be a positive integer", 4 /* Validation */);
|
|
16706
|
+
}
|
|
16707
|
+
const ctx = getCtx();
|
|
16708
|
+
const params = { limit: String(limitN) };
|
|
16709
|
+
if (opts.status)
|
|
16710
|
+
params.status = opts.status;
|
|
16711
|
+
const items = await ctx.api.get("/api/agent/asks", {
|
|
16712
|
+
params
|
|
16713
|
+
});
|
|
16714
|
+
if (isJson()) {
|
|
16715
|
+
console.log(JSON.stringify(items, null, 2));
|
|
16716
|
+
return;
|
|
16717
|
+
}
|
|
16718
|
+
if (items.length === 0) {
|
|
16719
|
+
console.log(source_default.dim("No asks."));
|
|
16720
|
+
return;
|
|
16721
|
+
}
|
|
16722
|
+
console.log(items.map(renderAsk).join(`
|
|
16723
|
+
|
|
16724
|
+
`));
|
|
16725
|
+
});
|
|
16726
|
+
cmd.command("get <caseId>").description("Show one own ask — status, and decision+answer once resolved").action(async (caseId) => {
|
|
16727
|
+
const ctx = getCtx();
|
|
16728
|
+
const ask = await ctx.api.get(`/api/agent/asks/${caseId}`);
|
|
16729
|
+
if (isJson()) {
|
|
16730
|
+
console.log(JSON.stringify(ask, null, 2));
|
|
16731
|
+
return;
|
|
16732
|
+
}
|
|
16733
|
+
console.log(renderAsk(ask));
|
|
16734
|
+
});
|
|
16735
|
+
}
|
|
16736
|
+
|
|
16491
16737
|
// src/commands/tasks.ts
|
|
16492
16738
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
16493
16739
|
init_errors();
|
|
@@ -16508,7 +16754,7 @@ var sleep4 = (ms, signal) => new Promise((resolve2) => {
|
|
|
16508
16754
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
16509
16755
|
});
|
|
16510
16756
|
var fmtDate2 = (v) => typeof v === "string" ? new Date(v).toISOString().slice(0, 16).replace("T", " ") : "-";
|
|
16511
|
-
var
|
|
16757
|
+
var statusColor2 = (status) => {
|
|
16512
16758
|
switch (status) {
|
|
16513
16759
|
case "open":
|
|
16514
16760
|
return source_default.yellow(status);
|
|
@@ -16611,7 +16857,7 @@ function registerTaskCommands(program2) {
|
|
|
16611
16857
|
const row = columns.map((col, i) => {
|
|
16612
16858
|
let val = col.accessor(item);
|
|
16613
16859
|
if (col.header === "STATUS")
|
|
16614
|
-
val =
|
|
16860
|
+
val = statusColor2(val);
|
|
16615
16861
|
if (col.header === "PRIORITY")
|
|
16616
16862
|
val = priorityColor(val);
|
|
16617
16863
|
return col.header === "STATUS" || col.header === "PRIORITY" ? val.padEnd(widths[i] + (val.length - col.accessor(item).length)) : val.padEnd(widths[i]);
|
|
@@ -16660,7 +16906,7 @@ ${items.length} task(s)`));
|
|
|
16660
16906
|
return;
|
|
16661
16907
|
}
|
|
16662
16908
|
console.log(`Created task ${source_default.cyan(String(result.id ?? ""))}`);
|
|
16663
|
-
console.log(` Status: ${
|
|
16909
|
+
console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
|
|
16664
16910
|
console.log(` Priority: ${priorityColor(String(result.priority ?? "-"))}`);
|
|
16665
16911
|
console.log(` Goal: ${truncate2(String(result.goal ?? "-"), 60)}`);
|
|
16666
16912
|
});
|
|
@@ -16674,7 +16920,7 @@ ${items.length} task(s)`));
|
|
|
16674
16920
|
console.log(source_default.bold("Task Details"));
|
|
16675
16921
|
console.log(source_default.dim("-".repeat(50)));
|
|
16676
16922
|
console.log(`${source_default.cyan("ID:")} ${item.id ?? "-"}`);
|
|
16677
|
-
console.log(`${source_default.cyan("Status:")} ${
|
|
16923
|
+
console.log(`${source_default.cyan("Status:")} ${statusColor2(String(item.status ?? "-"))}`);
|
|
16678
16924
|
console.log(`${source_default.cyan("Priority:")} ${priorityColor(String(item.priority ?? "-"))}`);
|
|
16679
16925
|
console.log(`${source_default.cyan("Title:")} ${item.title ?? "-"}`);
|
|
16680
16926
|
console.log(`${source_default.cyan("Agent:")} ${item.agent_definition_id ?? "-"}`);
|
|
@@ -16785,7 +17031,7 @@ ${items.length} task(s) available`));
|
|
|
16785
17031
|
return;
|
|
16786
17032
|
}
|
|
16787
17033
|
console.log(`Claimed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
|
|
16788
|
-
console.log(` Status: ${
|
|
17034
|
+
console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
|
|
16789
17035
|
console.log(` Executor: ${result.executor_id ?? "-"}`);
|
|
16790
17036
|
console.log(` Lease: expires ${fmtDate2(result.lease_expires_at)}`);
|
|
16791
17037
|
});
|
|
@@ -16958,7 +17204,7 @@ ${items.length} task(s) available`));
|
|
|
16958
17204
|
return;
|
|
16959
17205
|
}
|
|
16960
17206
|
console.log(`Completed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
|
|
16961
|
-
console.log(` Status: ${
|
|
17207
|
+
console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
|
|
16962
17208
|
console.log(` Completed at: ${fmtDate2(result.completed_at)}`);
|
|
16963
17209
|
});
|
|
16964
17210
|
}
|
|
@@ -16968,6 +17214,17 @@ init_errors();
|
|
|
16968
17214
|
function sleep5(ms) {
|
|
16969
17215
|
return new Promise((r) => setTimeout(r, ms));
|
|
16970
17216
|
}
|
|
17217
|
+
function sleepUnref(ms) {
|
|
17218
|
+
return new Promise((r) => {
|
|
17219
|
+
const t = setTimeout(r, ms);
|
|
17220
|
+
t.unref?.();
|
|
17221
|
+
});
|
|
17222
|
+
}
|
|
17223
|
+
var DEFAULT_LEASE_S = 300;
|
|
17224
|
+
function heartbeatIntervalMs(leaseTimeoutS) {
|
|
17225
|
+
const lease = typeof leaseTimeoutS === "number" && leaseTimeoutS > 0 ? leaseTimeoutS : DEFAULT_LEASE_S;
|
|
17226
|
+
return Math.min(lease / 3, 60) * 1000;
|
|
17227
|
+
}
|
|
16971
17228
|
async function runOnce(ctx, opts, handled) {
|
|
16972
17229
|
const tasks = await ctx.api.get("/api/tasks/poll", {
|
|
16973
17230
|
params: { agent_id: opts.agent, limit: String(opts.pollLimit ?? 5) }
|
|
@@ -16985,20 +17242,55 @@ async function runOnce(ctx, opts, handled) {
|
|
|
16985
17242
|
handled?.add(task.id);
|
|
16986
17243
|
const claimed = await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/claim`, {});
|
|
16987
17244
|
const goal = claimed.goal ?? task.goal ?? "";
|
|
17245
|
+
const intervalMs = heartbeatIntervalMs(claimed.lease_timeout_s ?? DEFAULT_LEASE_S);
|
|
16988
17246
|
process.stderr.write(source_default.dim(`
|
|
16989
|
-
[runner] claimed ${task.id} — running: ${goal}
|
|
17247
|
+
[runner] claimed ${task.id} — running: ${goal} (heartbeat every ${Math.round(intervalMs / 1000)}s)
|
|
16990
17248
|
`));
|
|
16991
17249
|
let result;
|
|
17250
|
+
let cancelled = false;
|
|
16992
17251
|
try {
|
|
16993
|
-
|
|
17252
|
+
const handle = await startHostCommand(goal, `task-${task.id}`, {
|
|
17253
|
+
cwd: opts.cwd
|
|
17254
|
+
});
|
|
17255
|
+
const done = handle.done.catch((e) => ({
|
|
17256
|
+
stdout: "",
|
|
17257
|
+
stderr: String(e),
|
|
17258
|
+
exit_code: -1
|
|
17259
|
+
}));
|
|
17260
|
+
for (;; ) {
|
|
17261
|
+
const raced = await Promise.race([
|
|
17262
|
+
done,
|
|
17263
|
+
sleepUnref(intervalMs).then(() => null)
|
|
17264
|
+
]);
|
|
17265
|
+
if (raced !== null) {
|
|
17266
|
+
result = raced;
|
|
17267
|
+
break;
|
|
17268
|
+
}
|
|
17269
|
+
let reply;
|
|
17270
|
+
try {
|
|
17271
|
+
reply = await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/heartbeat`, {});
|
|
17272
|
+
} catch (e) {
|
|
17273
|
+
process.stderr.write(source_default.yellow(`[runner] heartbeat failed for ${task.id} (job unaffected): ${String(e)}
|
|
17274
|
+
`));
|
|
17275
|
+
continue;
|
|
17276
|
+
}
|
|
17277
|
+
if (reply && reply.signal === "cancel") {
|
|
17278
|
+
process.stderr.write(source_default.yellow(`[runner] cancel signal for ${task.id} — killing process tree
|
|
17279
|
+
`));
|
|
17280
|
+
handle.kill();
|
|
17281
|
+
result = await done;
|
|
17282
|
+
cancelled = true;
|
|
17283
|
+
break;
|
|
17284
|
+
}
|
|
17285
|
+
}
|
|
16994
17286
|
} catch (e) {
|
|
16995
17287
|
result = { stdout: "", stderr: String(e), exit_code: -1 };
|
|
16996
17288
|
}
|
|
16997
17289
|
const exit_code = result.exit_code;
|
|
16998
17290
|
const stdout = result.stdout || result.stderr || "";
|
|
16999
|
-
const status = exit_code === 0 ? "completed" : "failed";
|
|
17291
|
+
const status = cancelled ? "cancelled" : exit_code === 0 ? "completed" : "failed";
|
|
17000
17292
|
await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/complete`, {
|
|
17001
|
-
outcome: { stdout, exit_code },
|
|
17293
|
+
outcome: cancelled ? { stdout, exit_code, cancelled: true } : { stdout, exit_code },
|
|
17002
17294
|
status
|
|
17003
17295
|
});
|
|
17004
17296
|
process.stderr.write(source_default.dim(`[runner] completed ${task.id} — ${status} (exit ${exit_code})
|
|
@@ -19970,7 +20262,7 @@ async function resolveCredentialFlag(value) {
|
|
|
19970
20262
|
// src/commands/policy.ts
|
|
19971
20263
|
init_errors();
|
|
19972
20264
|
var ACTIONS = ["allow", "require_approval", "block"];
|
|
19973
|
-
var
|
|
20265
|
+
var KINDS2 = ["risk_tag", "risk_level", "capability"];
|
|
19974
20266
|
var SCOPES = ["any", "agent", "client"];
|
|
19975
20267
|
var truncate4 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
19976
20268
|
function assertOneOf(value, allowed, flag) {
|
|
@@ -20066,8 +20358,8 @@ function registerPolicyCommands(program2) {
|
|
|
20066
20358
|
console.log(`Safe-default bundle ${enabled ? source_default.yellow("enabled") : source_default.dim("disabled")}.`);
|
|
20067
20359
|
});
|
|
20068
20360
|
const rule = cmd.command("rule").description("Manage explicit policy rules");
|
|
20069
|
-
rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${
|
|
20070
|
-
assertOneOf(opts.kind,
|
|
20361
|
+
rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS2.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${ACTIONS.join("|")})`).option(`--scope <scope>`, `Principal scope (${SCOPES.join("|")})`, "any").option("--note <text>", "Optional human note").action(async (opts) => {
|
|
20362
|
+
assertOneOf(opts.kind, KINDS2, "--kind");
|
|
20071
20363
|
assertOneOf(opts.action, ACTIONS, "--action");
|
|
20072
20364
|
assertOneOf(opts.scope, SCOPES, "--scope");
|
|
20073
20365
|
const ctx = getCtx();
|
|
@@ -21363,6 +21655,7 @@ registerDocumentCommands(program2);
|
|
|
21363
21655
|
registerDocumentSyncCommands(program2);
|
|
21364
21656
|
registerWikiCommands(program2);
|
|
21365
21657
|
registerApprovalCommands(program2);
|
|
21658
|
+
registerAskCommands(program2);
|
|
21366
21659
|
registerTaskCommands(program2);
|
|
21367
21660
|
registerRunnerCommand(program2);
|
|
21368
21661
|
registerTeamCommands(program2);
|