@aiaiai-pt/martha-cli 0.25.0 → 0.27.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/index.js +529 -85
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2158,6 +2158,8 @@ function extractDetail(body) {
|
|
|
2158
2158
|
const nested = obj.detail;
|
|
2159
2159
|
if (typeof nested.message === "string")
|
|
2160
2160
|
return nested.message;
|
|
2161
|
+
if (typeof nested.code === "string")
|
|
2162
|
+
return nested.code;
|
|
2161
2163
|
if (nested.code === "mcp_oauth_authorization_required" && typeof nested.authorization_url === "string") {
|
|
2162
2164
|
return "MCP OAuth authorization required";
|
|
2163
2165
|
}
|
|
@@ -11073,12 +11075,13 @@ ${items.length} ${config.typeNamePlural}`));
|
|
|
11073
11075
|
}
|
|
11074
11076
|
|
|
11075
11077
|
// src/commands/chat.ts
|
|
11078
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
11076
11079
|
import { createInterface as createInterface2 } from "node:readline";
|
|
11077
11080
|
init_config();
|
|
11078
11081
|
init_errors();
|
|
11079
11082
|
|
|
11080
11083
|
// src/version.ts
|
|
11081
|
-
var CLI_VERSION = "0.
|
|
11084
|
+
var CLI_VERSION = "0.27.0";
|
|
11082
11085
|
|
|
11083
11086
|
// src/commands/sessions.ts
|
|
11084
11087
|
function relativeTime(iso) {
|
|
@@ -11352,8 +11355,12 @@ function createApprovalTagStripper() {
|
|
|
11352
11355
|
|
|
11353
11356
|
// src/lib/local-exec.ts
|
|
11354
11357
|
import { spawn } from "node:child_process";
|
|
11358
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
11355
11359
|
import { closeSync, openSync, promises as fs5 } from "node:fs";
|
|
11356
11360
|
import * as path4 from "node:path";
|
|
11361
|
+
function sleep(ms) {
|
|
11362
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
11363
|
+
}
|
|
11357
11364
|
var DEFAULT_TIMEOUT_MS = 20000;
|
|
11358
11365
|
var MAX_OUTPUT = 256 * 1024;
|
|
11359
11366
|
function runnerDir(cwd) {
|
|
@@ -11432,6 +11439,9 @@ async function runHostCommand(command, toolCallId, opts) {
|
|
|
11432
11439
|
function safeName(s) {
|
|
11433
11440
|
return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
|
|
11434
11441
|
}
|
|
11442
|
+
function mintExecSecret() {
|
|
11443
|
+
return randomBytes2(32).toString("hex");
|
|
11444
|
+
}
|
|
11435
11445
|
|
|
11436
11446
|
class Journal {
|
|
11437
11447
|
cwd;
|
|
@@ -11443,24 +11453,99 @@ class Journal {
|
|
|
11443
11453
|
}
|
|
11444
11454
|
async load() {
|
|
11445
11455
|
try {
|
|
11446
|
-
|
|
11456
|
+
const raw = JSON.parse(await fs5.readFile(this.file(), "utf8"));
|
|
11457
|
+
if (raw["entries"] !== undefined) {
|
|
11458
|
+
const f = raw;
|
|
11459
|
+
if (!f.secrets)
|
|
11460
|
+
f.secrets = {};
|
|
11461
|
+
if (!f.jobs)
|
|
11462
|
+
f.jobs = {};
|
|
11463
|
+
return f;
|
|
11464
|
+
}
|
|
11465
|
+
return {
|
|
11466
|
+
entries: raw,
|
|
11467
|
+
secrets: {},
|
|
11468
|
+
jobs: {}
|
|
11469
|
+
};
|
|
11447
11470
|
} catch {
|
|
11448
|
-
return {};
|
|
11471
|
+
return { entries: {}, secrets: {}, jobs: {} };
|
|
11449
11472
|
}
|
|
11450
11473
|
}
|
|
11451
11474
|
async save(data) {
|
|
11452
11475
|
await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
|
|
11453
|
-
|
|
11476
|
+
const tmp = `${this.file()}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
11477
|
+
await fs5.writeFile(tmp, JSON.stringify(data, null, 2));
|
|
11478
|
+
await fs5.rename(tmp, this.file());
|
|
11479
|
+
}
|
|
11480
|
+
chain = Promise.resolve();
|
|
11481
|
+
async acquireFileLock() {
|
|
11482
|
+
const lockPath = path4.join(runnerDir(this.cwd), "journal.lock");
|
|
11483
|
+
await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
|
|
11484
|
+
const deadline = Date.now() + 5000;
|
|
11485
|
+
for (;; ) {
|
|
11486
|
+
try {
|
|
11487
|
+
const fh = await fs5.open(lockPath, "wx");
|
|
11488
|
+
await fh.write(String(process.pid));
|
|
11489
|
+
await fh.close();
|
|
11490
|
+
return async () => {
|
|
11491
|
+
await fs5.rm(lockPath, { force: true });
|
|
11492
|
+
};
|
|
11493
|
+
} catch (e) {
|
|
11494
|
+
if (e.code !== "EEXIST")
|
|
11495
|
+
throw e;
|
|
11496
|
+
try {
|
|
11497
|
+
const st = await fs5.stat(lockPath);
|
|
11498
|
+
if (Date.now() - st.mtimeMs > 1e4) {
|
|
11499
|
+
await fs5.rm(lockPath, { force: true });
|
|
11500
|
+
continue;
|
|
11501
|
+
}
|
|
11502
|
+
} catch {
|
|
11503
|
+
continue;
|
|
11504
|
+
}
|
|
11505
|
+
if (Date.now() > deadline) {
|
|
11506
|
+
return async () => {};
|
|
11507
|
+
}
|
|
11508
|
+
await sleep(15 + Math.floor(Math.random() * 25));
|
|
11509
|
+
}
|
|
11510
|
+
}
|
|
11511
|
+
}
|
|
11512
|
+
async transact(fn) {
|
|
11513
|
+
const run = async () => {
|
|
11514
|
+
const release = await this.acquireFileLock();
|
|
11515
|
+
try {
|
|
11516
|
+
const data = await this.load();
|
|
11517
|
+
const out = await fn(data);
|
|
11518
|
+
await this.save(data);
|
|
11519
|
+
return out;
|
|
11520
|
+
} finally {
|
|
11521
|
+
await release();
|
|
11522
|
+
}
|
|
11523
|
+
};
|
|
11524
|
+
const result = this.chain.then(run, run);
|
|
11525
|
+
this.chain = result.then(() => {
|
|
11526
|
+
return;
|
|
11527
|
+
}, () => {
|
|
11528
|
+
return;
|
|
11529
|
+
});
|
|
11530
|
+
return result;
|
|
11531
|
+
}
|
|
11532
|
+
async recordJob(job) {
|
|
11533
|
+
await this.transact((data) => {
|
|
11534
|
+
data.jobs[job.job_id] = job;
|
|
11535
|
+
});
|
|
11536
|
+
}
|
|
11537
|
+
async lookupJob(jobId) {
|
|
11538
|
+
return this.transact((data) => data.jobs[jobId]);
|
|
11454
11539
|
}
|
|
11455
11540
|
static key(sessionId, toolCallId) {
|
|
11456
11541
|
return `${sessionId}:${toolCallId}`;
|
|
11457
11542
|
}
|
|
11458
11543
|
async lookup(sessionId, toolCallId) {
|
|
11459
|
-
return (await this.load())[Journal.key(sessionId, toolCallId)];
|
|
11544
|
+
return (await this.load()).entries[Journal.key(sessionId, toolCallId)];
|
|
11460
11545
|
}
|
|
11461
11546
|
async recordRunning(sessionId, toolCallId, command, now) {
|
|
11462
11547
|
const data = await this.load();
|
|
11463
|
-
data[Journal.key(sessionId, toolCallId)] = {
|
|
11548
|
+
data.entries[Journal.key(sessionId, toolCallId)] = {
|
|
11464
11549
|
state: "running",
|
|
11465
11550
|
command,
|
|
11466
11551
|
started_at: now
|
|
@@ -11470,8 +11555,8 @@ class Journal {
|
|
|
11470
11555
|
async recordDone(sessionId, toolCallId, result) {
|
|
11471
11556
|
const data = await this.load();
|
|
11472
11557
|
const k = Journal.key(sessionId, toolCallId);
|
|
11473
|
-
const prev = data[k];
|
|
11474
|
-
data[k] = {
|
|
11558
|
+
const prev = data.entries[k];
|
|
11559
|
+
data.entries[k] = {
|
|
11475
11560
|
state: "done",
|
|
11476
11561
|
command: prev?.command ?? "",
|
|
11477
11562
|
started_at: prev?.started_at ?? "",
|
|
@@ -11479,6 +11564,14 @@ class Journal {
|
|
|
11479
11564
|
};
|
|
11480
11565
|
await this.save(data);
|
|
11481
11566
|
}
|
|
11567
|
+
async recordSecret(sessionId, toolCallId, secret) {
|
|
11568
|
+
const data = await this.load();
|
|
11569
|
+
data.secrets[Journal.key(sessionId, toolCallId)] = secret;
|
|
11570
|
+
await this.save(data);
|
|
11571
|
+
}
|
|
11572
|
+
async lookupSecret(sessionId, toolCallId) {
|
|
11573
|
+
return (await this.load()).secrets[Journal.key(sessionId, toolCallId)];
|
|
11574
|
+
}
|
|
11482
11575
|
}
|
|
11483
11576
|
function decideFromJournal(entry) {
|
|
11484
11577
|
if (!entry)
|
|
@@ -11502,7 +11595,12 @@ function parseHostExecRequests(data) {
|
|
|
11502
11595
|
continue;
|
|
11503
11596
|
if (!t.tool_call_id)
|
|
11504
11597
|
continue;
|
|
11505
|
-
out.push({
|
|
11598
|
+
out.push({
|
|
11599
|
+
toolCallId: t.tool_call_id,
|
|
11600
|
+
command: t.args?.command ?? "",
|
|
11601
|
+
background: t.args?.background === true,
|
|
11602
|
+
cwd: typeof t.args?.cwd === "string" ? t.args.cwd : undefined
|
|
11603
|
+
});
|
|
11506
11604
|
}
|
|
11507
11605
|
return out;
|
|
11508
11606
|
}
|
|
@@ -11898,12 +11996,247 @@ function parseLocalAgentRequests(data) {
|
|
|
11898
11996
|
out.push({
|
|
11899
11997
|
toolCallId: t.tool_call_id,
|
|
11900
11998
|
task: t.args.task,
|
|
11901
|
-
cwd: typeof t.args.cwd === "string" ? t.args.cwd : undefined
|
|
11999
|
+
cwd: typeof t.args.cwd === "string" ? t.args.cwd : undefined,
|
|
12000
|
+
background: t.args.background === true
|
|
11902
12001
|
});
|
|
11903
12002
|
}
|
|
11904
12003
|
return out;
|
|
11905
12004
|
}
|
|
11906
12005
|
|
|
12006
|
+
// src/lib/local-jobs.ts
|
|
12007
|
+
import { execFileSync, spawn as spawn2 } from "node:child_process";
|
|
12008
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
12009
|
+
import { closeSync as closeSync2, openSync as openSync2, readFileSync, promises as fs7 } from "node:fs";
|
|
12010
|
+
import * as path6 from "node:path";
|
|
12011
|
+
var POLL_CAP = 64 * 1024;
|
|
12012
|
+
function mintJobId() {
|
|
12013
|
+
return `job_${randomBytes3(6).toString("hex")}`;
|
|
12014
|
+
}
|
|
12015
|
+
var SENTINEL_TAIL = `
|
|
12016
|
+
__martha_ec=$?
|
|
12017
|
+
printf '%s' "$__martha_ec" > "$MARTHA_JOB_SENTINEL.tmp" 2>/dev/null
|
|
12018
|
+
mv -f "$MARTHA_JOB_SENTINEL.tmp" "$MARTHA_JOB_SENTINEL" 2>/dev/null
|
|
12019
|
+
exit $__martha_ec`;
|
|
12020
|
+
function runnerDir2(cwd) {
|
|
12021
|
+
return path6.join(cwd, ".martha-runner");
|
|
12022
|
+
}
|
|
12023
|
+
function getProcessStartTime(pid) {
|
|
12024
|
+
try {
|
|
12025
|
+
if (process.platform === "linux") {
|
|
12026
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
12027
|
+
const tail = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
|
|
12028
|
+
return tail[19] ?? null;
|
|
12029
|
+
}
|
|
12030
|
+
const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
12031
|
+
encoding: "utf8"
|
|
12032
|
+
}).trim();
|
|
12033
|
+
return out || null;
|
|
12034
|
+
} catch {
|
|
12035
|
+
return null;
|
|
12036
|
+
}
|
|
12037
|
+
}
|
|
12038
|
+
function isProcessAlive(pid) {
|
|
12039
|
+
if (pid <= 0)
|
|
12040
|
+
return false;
|
|
12041
|
+
try {
|
|
12042
|
+
process.kill(pid, 0);
|
|
12043
|
+
return true;
|
|
12044
|
+
} catch (e) {
|
|
12045
|
+
return e.code === "EPERM";
|
|
12046
|
+
}
|
|
12047
|
+
}
|
|
12048
|
+
async function readSentinel(p) {
|
|
12049
|
+
try {
|
|
12050
|
+
const raw = (await fs7.readFile(p, "utf8")).trim();
|
|
12051
|
+
if (raw === "")
|
|
12052
|
+
return { present: true };
|
|
12053
|
+
const n = parseInt(raw, 10);
|
|
12054
|
+
return { present: true, code: Number.isNaN(n) ? undefined : n };
|
|
12055
|
+
} catch {
|
|
12056
|
+
return { present: false };
|
|
12057
|
+
}
|
|
12058
|
+
}
|
|
12059
|
+
async function readLogOrNull(p) {
|
|
12060
|
+
try {
|
|
12061
|
+
return await fs7.readFile(p);
|
|
12062
|
+
} catch {
|
|
12063
|
+
return null;
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
async function writeSentinelAtomic(p, content) {
|
|
12067
|
+
const tmp = `${p}.${randomBytes3(4).toString("hex")}.tmp`;
|
|
12068
|
+
await fs7.writeFile(tmp, content);
|
|
12069
|
+
await fs7.rename(tmp, p);
|
|
12070
|
+
}
|
|
12071
|
+
async function dispatchBackgroundJob(command, jobId, journal, opts) {
|
|
12072
|
+
if (process.platform === "win32") {
|
|
12073
|
+
throw new Error("background jobs are not supported on Windows (POSIX-only: /bin/sh + " + "process groups). Run this command in the foreground instead.");
|
|
12074
|
+
}
|
|
12075
|
+
const dir = runnerDir2(opts.cwd);
|
|
12076
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
12077
|
+
const logPath = path6.join(dir, `${jobId}.log`);
|
|
12078
|
+
const sentinelPath = path6.join(dir, `${jobId}.exit`);
|
|
12079
|
+
await fs7.writeFile(logPath, "");
|
|
12080
|
+
const fd = openSync2(logPath, "a");
|
|
12081
|
+
const env2 = { ...process.env, MARTHA_JOB_SENTINEL: sentinelPath };
|
|
12082
|
+
const stdin = opts.stdinData != null ? "pipe" : "ignore";
|
|
12083
|
+
const child = opts.argv ? spawn2("/bin/sh", ["-c", `"$@"${SENTINEL_TAIL}`, "martha-job", ...opts.argv], {
|
|
12084
|
+
shell: false,
|
|
12085
|
+
cwd: opts.cwd,
|
|
12086
|
+
detached: true,
|
|
12087
|
+
env: env2,
|
|
12088
|
+
stdio: [stdin, fd, fd]
|
|
12089
|
+
}) : spawn2("/bin/sh", ["-c", `( ${command} )${SENTINEL_TAIL}`], {
|
|
12090
|
+
shell: false,
|
|
12091
|
+
cwd: opts.cwd,
|
|
12092
|
+
detached: true,
|
|
12093
|
+
env: env2,
|
|
12094
|
+
stdio: [stdin, fd, fd]
|
|
12095
|
+
});
|
|
12096
|
+
closeSync2(fd);
|
|
12097
|
+
if (opts.stdinData != null && child.stdin) {
|
|
12098
|
+
child.stdin.on("error", () => {});
|
|
12099
|
+
child.stdin.write(opts.stdinData);
|
|
12100
|
+
child.stdin.end();
|
|
12101
|
+
}
|
|
12102
|
+
const pid = child.pid ?? -1;
|
|
12103
|
+
const start_time = pid > 0 ? getProcessStartTime(pid) : null;
|
|
12104
|
+
child.unref();
|
|
12105
|
+
const record = {
|
|
12106
|
+
job_id: jobId,
|
|
12107
|
+
command,
|
|
12108
|
+
pid,
|
|
12109
|
+
start_time,
|
|
12110
|
+
started_at: new Date().toISOString(),
|
|
12111
|
+
log_path: logPath,
|
|
12112
|
+
sentinel_path: sentinelPath,
|
|
12113
|
+
offset: 0,
|
|
12114
|
+
status: "running"
|
|
12115
|
+
};
|
|
12116
|
+
await journal.recordJob(record);
|
|
12117
|
+
return { job_id: jobId, status: "running", log_path: logPath, pid };
|
|
12118
|
+
}
|
|
12119
|
+
function startTimeMatches(job) {
|
|
12120
|
+
if (!job.start_time)
|
|
12121
|
+
return true;
|
|
12122
|
+
const live = getProcessStartTime(job.pid);
|
|
12123
|
+
if (live == null)
|
|
12124
|
+
return true;
|
|
12125
|
+
return live === job.start_time;
|
|
12126
|
+
}
|
|
12127
|
+
async function classifyAndRead(job) {
|
|
12128
|
+
const sentinel = await readSentinel(job.sentinel_path);
|
|
12129
|
+
const logBuf = await readLogOrNull(job.log_path);
|
|
12130
|
+
let status;
|
|
12131
|
+
let done;
|
|
12132
|
+
let exit_code;
|
|
12133
|
+
if (sentinel.present) {
|
|
12134
|
+
done = true;
|
|
12135
|
+
exit_code = sentinel.code;
|
|
12136
|
+
status = job.status === "killed" ? "killed" : "done";
|
|
12137
|
+
job.status = status;
|
|
12138
|
+
} else if (isProcessAlive(job.pid) && startTimeMatches(job)) {
|
|
12139
|
+
status = "running";
|
|
12140
|
+
done = false;
|
|
12141
|
+
} else if (logBuf == null) {
|
|
12142
|
+
status = "expired";
|
|
12143
|
+
done = true;
|
|
12144
|
+
} else {
|
|
12145
|
+
status = "crashed";
|
|
12146
|
+
done = true;
|
|
12147
|
+
job.status = "crashed";
|
|
12148
|
+
}
|
|
12149
|
+
let new_output = "";
|
|
12150
|
+
let bytes_remaining = 0;
|
|
12151
|
+
if (logBuf != null) {
|
|
12152
|
+
const slice = logBuf.subarray(job.offset, job.offset + POLL_CAP);
|
|
12153
|
+
new_output = slice.toString("utf8");
|
|
12154
|
+
job.offset += slice.length;
|
|
12155
|
+
bytes_remaining = Math.max(0, logBuf.length - job.offset);
|
|
12156
|
+
}
|
|
12157
|
+
return {
|
|
12158
|
+
job_id: job.job_id,
|
|
12159
|
+
status,
|
|
12160
|
+
exit_code,
|
|
12161
|
+
new_output,
|
|
12162
|
+
done,
|
|
12163
|
+
bytes_remaining
|
|
12164
|
+
};
|
|
12165
|
+
}
|
|
12166
|
+
async function pollJob(journal, jobId) {
|
|
12167
|
+
return journal.transact(async (data) => {
|
|
12168
|
+
const job = data.jobs[jobId];
|
|
12169
|
+
if (!job) {
|
|
12170
|
+
return {
|
|
12171
|
+
job_id: jobId,
|
|
12172
|
+
status: "unknown",
|
|
12173
|
+
new_output: "",
|
|
12174
|
+
done: true,
|
|
12175
|
+
bytes_remaining: 0,
|
|
12176
|
+
message: `No background job with id ${jobId} is known on this machine.`
|
|
12177
|
+
};
|
|
12178
|
+
}
|
|
12179
|
+
return classifyAndRead(job);
|
|
12180
|
+
});
|
|
12181
|
+
}
|
|
12182
|
+
async function killJob(journal, jobId) {
|
|
12183
|
+
return journal.transact(async (data) => {
|
|
12184
|
+
const job = data.jobs[jobId];
|
|
12185
|
+
if (!job) {
|
|
12186
|
+
return {
|
|
12187
|
+
job_id: jobId,
|
|
12188
|
+
status: "unknown",
|
|
12189
|
+
new_output: "",
|
|
12190
|
+
done: true,
|
|
12191
|
+
bytes_remaining: 0,
|
|
12192
|
+
message: `No background job with id ${jobId} is known on this machine.`
|
|
12193
|
+
};
|
|
12194
|
+
}
|
|
12195
|
+
const sentinel = await readSentinel(job.sentinel_path);
|
|
12196
|
+
if (!sentinel.present) {
|
|
12197
|
+
for (const sig of ["SIGTERM", "SIGKILL"]) {
|
|
12198
|
+
try {
|
|
12199
|
+
if (job.pid > 0)
|
|
12200
|
+
process.kill(-job.pid, sig);
|
|
12201
|
+
} catch {}
|
|
12202
|
+
}
|
|
12203
|
+
job.status = "killed";
|
|
12204
|
+
await writeSentinelAtomic(job.sentinel_path, "137");
|
|
12205
|
+
}
|
|
12206
|
+
const result = await classifyAndRead(job);
|
|
12207
|
+
if (result.status === "done" && job.status === "killed") {
|
|
12208
|
+
result.status = "killed";
|
|
12209
|
+
}
|
|
12210
|
+
result.message = "Background job stopped.";
|
|
12211
|
+
return result;
|
|
12212
|
+
});
|
|
12213
|
+
}
|
|
12214
|
+
function parseHostOutputRequests(data) {
|
|
12215
|
+
return parseJobToolRequests(data, "host_output");
|
|
12216
|
+
}
|
|
12217
|
+
function parseHostKillRequests(data) {
|
|
12218
|
+
return parseJobToolRequests(data, "host_kill");
|
|
12219
|
+
}
|
|
12220
|
+
function parseJobToolRequests(data, toolName) {
|
|
12221
|
+
let tools;
|
|
12222
|
+
try {
|
|
12223
|
+
tools = JSON.parse(data);
|
|
12224
|
+
} catch {
|
|
12225
|
+
return [];
|
|
12226
|
+
}
|
|
12227
|
+
if (!Array.isArray(tools))
|
|
12228
|
+
return [];
|
|
12229
|
+
const out = [];
|
|
12230
|
+
for (const t of tools) {
|
|
12231
|
+
if (t.tool_name !== toolName || t.status !== "awaiting_input")
|
|
12232
|
+
continue;
|
|
12233
|
+
if (!t.tool_call_id || !t.args?.job_id)
|
|
12234
|
+
continue;
|
|
12235
|
+
out.push({ toolCallId: t.tool_call_id, jobId: t.args.job_id });
|
|
12236
|
+
}
|
|
12237
|
+
return out;
|
|
12238
|
+
}
|
|
12239
|
+
|
|
11907
12240
|
// src/commands/chat.ts
|
|
11908
12241
|
async function uploadImageArtifact(ctx, sessionId, png) {
|
|
11909
12242
|
try {
|
|
@@ -11922,7 +12255,8 @@ async function uploadImageArtifact(ctx, sessionId, png) {
|
|
|
11922
12255
|
return { error: `image upload failed: ${String(e)}` };
|
|
11923
12256
|
}
|
|
11924
12257
|
}
|
|
11925
|
-
async function resolveHostExec(sessionId, toolCallId,
|
|
12258
|
+
async function resolveHostExec(sessionId, toolCallId, req, state) {
|
|
12259
|
+
const command = req.command;
|
|
11926
12260
|
const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
|
|
11927
12261
|
if (decision.kind === "replay")
|
|
11928
12262
|
return decision.result;
|
|
@@ -11943,20 +12277,65 @@ async function resolveHostExec(sessionId, toolCallId, command, state) {
|
|
|
11943
12277
|
message: "Host execution declined; re-run with --yes to allow."
|
|
11944
12278
|
};
|
|
11945
12279
|
}
|
|
12280
|
+
const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
|
|
12281
|
+
if (req.background) {
|
|
12282
|
+
const jobId = mintJobId();
|
|
12283
|
+
process.stderr.write(source_default.dim(`
|
|
12284
|
+
[host_exec:bg] $ ${command}
|
|
12285
|
+
(job ${jobId}, cwd: ${cwd})
|
|
12286
|
+
`));
|
|
12287
|
+
try {
|
|
12288
|
+
const handle = await dispatchBackgroundJob(command, jobId, state.journal, {
|
|
12289
|
+
cwd
|
|
12290
|
+
});
|
|
12291
|
+
return {
|
|
12292
|
+
job_id: handle.job_id,
|
|
12293
|
+
status: "running",
|
|
12294
|
+
pid: handle.pid,
|
|
12295
|
+
log_path: handle.log_path,
|
|
12296
|
+
message: `Dispatched in the background as ${handle.job_id}. Poll host_output(` + `{job_id:"${handle.job_id}"}) for new output + status; host_kill to stop it.`
|
|
12297
|
+
};
|
|
12298
|
+
} catch (e) {
|
|
12299
|
+
return {
|
|
12300
|
+
error: true,
|
|
12301
|
+
message: `failed to dispatch background job: ${String(e)}`
|
|
12302
|
+
};
|
|
12303
|
+
}
|
|
12304
|
+
}
|
|
11946
12305
|
process.stderr.write(source_default.dim(`
|
|
11947
12306
|
[host_exec] $ ${command}
|
|
11948
|
-
(cwd: ${
|
|
12307
|
+
(cwd: ${cwd})
|
|
11949
12308
|
`));
|
|
11950
12309
|
await state.journal.recordRunning(sessionId, toolCallId, command, new Date().toISOString());
|
|
11951
12310
|
let result;
|
|
11952
12311
|
try {
|
|
11953
|
-
result = await runHostCommand(command, toolCallId, { cwd
|
|
12312
|
+
result = await runHostCommand(command, toolCallId, { cwd });
|
|
11954
12313
|
} catch (e) {
|
|
11955
12314
|
result = { stdout: "", stderr: String(e), exit_code: -1 };
|
|
11956
12315
|
}
|
|
11957
12316
|
await state.journal.recordDone(sessionId, toolCallId, result);
|
|
11958
12317
|
return result;
|
|
11959
12318
|
}
|
|
12319
|
+
async function resolveHostOutput(req, state) {
|
|
12320
|
+
process.stderr.write(source_default.dim(`
|
|
12321
|
+
[host_output] ${req.jobId}
|
|
12322
|
+
`));
|
|
12323
|
+
try {
|
|
12324
|
+
return await pollJob(state.journal, req.jobId);
|
|
12325
|
+
} catch (e) {
|
|
12326
|
+
return { error: true, message: `host_output failed: ${String(e)}` };
|
|
12327
|
+
}
|
|
12328
|
+
}
|
|
12329
|
+
async function resolveHostKill(req, state) {
|
|
12330
|
+
process.stderr.write(source_default.dim(`
|
|
12331
|
+
[host_kill] ${req.jobId}
|
|
12332
|
+
`));
|
|
12333
|
+
try {
|
|
12334
|
+
return await killJob(state.journal, req.jobId);
|
|
12335
|
+
} catch (e) {
|
|
12336
|
+
return { error: true, message: `host_kill failed: ${String(e)}` };
|
|
12337
|
+
}
|
|
12338
|
+
}
|
|
11960
12339
|
async function resolveHostScreenshot(ctx, sessionId, req, state) {
|
|
11961
12340
|
if (!state.autoYes) {
|
|
11962
12341
|
process.stderr.write(source_default.yellow(`
|
|
@@ -12063,6 +12442,27 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
|
|
|
12063
12442
|
}
|
|
12064
12443
|
const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
|
|
12065
12444
|
const { bin, args, stdin } = buildSpawn(invocation, req.task);
|
|
12445
|
+
if (req.background) {
|
|
12446
|
+
const jobId = mintJobId();
|
|
12447
|
+
process.stderr.write(source_default.dim(`
|
|
12448
|
+
[local_agent:bg] (${invocation.source}) ${req.task.slice(0, 80)} (job ${jobId})
|
|
12449
|
+
`));
|
|
12450
|
+
try {
|
|
12451
|
+
const handle = await dispatchBackgroundJob(`local_agent:${invocation.source}`, jobId, state.journal, { cwd, argv: [bin, ...args], stdinData: stdin ?? undefined });
|
|
12452
|
+
return {
|
|
12453
|
+
job_id: handle.job_id,
|
|
12454
|
+
status: "running",
|
|
12455
|
+
pid: handle.pid,
|
|
12456
|
+
log_path: handle.log_path,
|
|
12457
|
+
message: `Delegated to ${invocation.source} in the background as ${handle.job_id}. ` + `Poll host_output({job_id:"${handle.job_id}"}) for its progress + final ` + `result; host_kill to stop it.`
|
|
12458
|
+
};
|
|
12459
|
+
} catch (e) {
|
|
12460
|
+
return {
|
|
12461
|
+
error: true,
|
|
12462
|
+
message: `local agent failed to start (${invocation.source}): ${String(e)}. ` + `Pick an installed agent with --local-agent <claude|codex|cursor-agent>.`
|
|
12463
|
+
};
|
|
12464
|
+
}
|
|
12465
|
+
}
|
|
12066
12466
|
process.stderr.write(source_default.dim(`
|
|
12067
12467
|
[local_agent] (${invocation.source}) ${req.task.slice(0, 100)}
|
|
12068
12468
|
`));
|
|
@@ -12104,9 +12504,28 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
|
|
|
12104
12504
|
log_path: result.log_path
|
|
12105
12505
|
};
|
|
12106
12506
|
}
|
|
12107
|
-
async function
|
|
12507
|
+
async function claimGate(ctx, sessionId, toolCallId, state) {
|
|
12508
|
+
if (state.claimed.has(toolCallId))
|
|
12509
|
+
return;
|
|
12510
|
+
const secret = state.secrets.get(toolCallId) ?? mintExecSecret();
|
|
12511
|
+
const secretHash = createHash2("sha256").update(secret).digest("hex");
|
|
12512
|
+
state.secrets.set(toolCallId, secret);
|
|
12513
|
+
await state.journal.recordSecret(sessionId, toolCallId, secret);
|
|
12108
12514
|
try {
|
|
12109
|
-
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-
|
|
12515
|
+
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-claim`, { tool_call_id: toolCallId, secret_hash: secretHash });
|
|
12516
|
+
state.claimed.add(toolCallId);
|
|
12517
|
+
} catch (e) {
|
|
12518
|
+
process.stderr.write(source_default.yellow(`[local-exec] failed to claim gate for ${toolCallId}: ${String(e)}
|
|
12519
|
+
`));
|
|
12520
|
+
}
|
|
12521
|
+
}
|
|
12522
|
+
async function _postToolResult(ctx, sessionId, toolCallId, result, label, execSecret) {
|
|
12523
|
+
try {
|
|
12524
|
+
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, {
|
|
12525
|
+
tool_call_id: toolCallId,
|
|
12526
|
+
result,
|
|
12527
|
+
...execSecret !== undefined ? { exec_secret: execSecret } : {}
|
|
12528
|
+
});
|
|
12110
12529
|
} catch (e) {
|
|
12111
12530
|
process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
|
|
12112
12531
|
`));
|
|
@@ -12118,40 +12537,63 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
|
|
|
12118
12537
|
if (state.handled.has(id))
|
|
12119
12538
|
continue;
|
|
12120
12539
|
state.handled.add(id);
|
|
12121
|
-
|
|
12122
|
-
await
|
|
12540
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12541
|
+
const result = await resolveHostExec(sessionId, id, req, state);
|
|
12542
|
+
await _postToolResult(ctx, sessionId, id, result, "host_exec", state.secrets.get(id));
|
|
12543
|
+
}
|
|
12544
|
+
for (const req of parseHostOutputRequests(data)) {
|
|
12545
|
+
const id = req.toolCallId;
|
|
12546
|
+
if (state.handled.has(id))
|
|
12547
|
+
continue;
|
|
12548
|
+
state.handled.add(id);
|
|
12549
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12550
|
+
const result = await resolveHostOutput(req, state);
|
|
12551
|
+
await _postToolResult(ctx, sessionId, id, result, "host_output", state.secrets.get(id));
|
|
12552
|
+
}
|
|
12553
|
+
for (const req of parseHostKillRequests(data)) {
|
|
12554
|
+
const id = req.toolCallId;
|
|
12555
|
+
if (state.handled.has(id))
|
|
12556
|
+
continue;
|
|
12557
|
+
state.handled.add(id);
|
|
12558
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12559
|
+
const result = await resolveHostKill(req, state);
|
|
12560
|
+
await _postToolResult(ctx, sessionId, id, result, "host_kill", state.secrets.get(id));
|
|
12123
12561
|
}
|
|
12124
12562
|
for (const req of parseHostScreenshotRequests(data)) {
|
|
12125
12563
|
const id = req.toolCallId;
|
|
12126
12564
|
if (state.handled.has(id))
|
|
12127
12565
|
continue;
|
|
12128
12566
|
state.handled.add(id);
|
|
12567
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12129
12568
|
const result = await resolveHostScreenshot(ctx, sessionId, req, state);
|
|
12130
|
-
await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
|
|
12569
|
+
await _postToolResult(ctx, sessionId, id, result, "host_screenshot", state.secrets.get(id));
|
|
12131
12570
|
}
|
|
12132
12571
|
for (const req of parseHostBrowserRequests(data)) {
|
|
12133
12572
|
const id = req.toolCallId;
|
|
12134
12573
|
if (state.handled.has(id))
|
|
12135
12574
|
continue;
|
|
12136
12575
|
state.handled.add(id);
|
|
12576
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12137
12577
|
const result = await resolveHostBrowser(ctx, sessionId, req, state);
|
|
12138
|
-
await _postToolResult(ctx, sessionId, id, result, "host_browser");
|
|
12578
|
+
await _postToolResult(ctx, sessionId, id, result, "host_browser", state.secrets.get(id));
|
|
12139
12579
|
}
|
|
12140
12580
|
for (const req of parseHostFileRequests(data)) {
|
|
12141
12581
|
const id = req.toolCallId;
|
|
12142
12582
|
if (state.handled.has(id))
|
|
12143
12583
|
continue;
|
|
12144
12584
|
state.handled.add(id);
|
|
12585
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12145
12586
|
const result = await resolveHostFile(req, state);
|
|
12146
|
-
await _postToolResult(ctx, sessionId, id, result, req.action);
|
|
12587
|
+
await _postToolResult(ctx, sessionId, id, result, req.action, state.secrets.get(id));
|
|
12147
12588
|
}
|
|
12148
12589
|
for (const req of parseLocalAgentRequests(data)) {
|
|
12149
12590
|
const id = req.toolCallId;
|
|
12150
12591
|
if (state.handled.has(id))
|
|
12151
12592
|
continue;
|
|
12152
12593
|
state.handled.add(id);
|
|
12594
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12153
12595
|
const result = await resolveLocalAgent(sessionId, id, req, state);
|
|
12154
|
-
await _postToolResult(ctx, sessionId, id, result, "local_agent");
|
|
12596
|
+
await _postToolResult(ctx, sessionId, id, result, "local_agent", state.secrets.get(id));
|
|
12155
12597
|
}
|
|
12156
12598
|
}
|
|
12157
12599
|
function parseSlashCommand(input) {
|
|
@@ -12209,7 +12651,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
12209
12651
|
autoYes: opts.localExec.autoYes,
|
|
12210
12652
|
localAgent: opts.localExec.agent,
|
|
12211
12653
|
handled: new Set,
|
|
12212
|
-
journal: new Journal(opts.localExec.cwd)
|
|
12654
|
+
journal: new Journal(opts.localExec.cwd),
|
|
12655
|
+
secrets: new Map,
|
|
12656
|
+
claimed: new Set
|
|
12213
12657
|
} : null;
|
|
12214
12658
|
const reqBody = { content: message };
|
|
12215
12659
|
if (opts.localExec)
|
|
@@ -13121,8 +13565,8 @@ function registerConfigCommand(program2) {
|
|
|
13121
13565
|
}
|
|
13122
13566
|
|
|
13123
13567
|
// src/commands/definitions-apply.ts
|
|
13124
|
-
import
|
|
13125
|
-
import
|
|
13568
|
+
import fs8 from "node:fs";
|
|
13569
|
+
import path7 from "node:path";
|
|
13126
13570
|
init_dist();
|
|
13127
13571
|
init_errors();
|
|
13128
13572
|
var VALID_KINDS = new Set([
|
|
@@ -13164,20 +13608,20 @@ var SERVER_GENERATED_FIELDS = new Set([
|
|
|
13164
13608
|
]);
|
|
13165
13609
|
var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
|
|
13166
13610
|
function loadDefinitions(inputPath) {
|
|
13167
|
-
const resolved =
|
|
13168
|
-
if (!
|
|
13611
|
+
const resolved = path7.resolve(inputPath);
|
|
13612
|
+
if (!fs8.existsSync(resolved)) {
|
|
13169
13613
|
throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
|
|
13170
13614
|
}
|
|
13171
|
-
const stat =
|
|
13615
|
+
const stat = fs8.statSync(resolved);
|
|
13172
13616
|
if (stat.isDirectory()) {
|
|
13173
13617
|
return loadDirectory(resolved);
|
|
13174
13618
|
}
|
|
13175
13619
|
return loadFile(resolved);
|
|
13176
13620
|
}
|
|
13177
13621
|
function loadDirectory(dirPath) {
|
|
13178
|
-
const entries =
|
|
13622
|
+
const entries = fs8.readdirSync(dirPath).sort();
|
|
13179
13623
|
const validExts = new Set([".yaml", ".yml", ".json"]);
|
|
13180
|
-
const files = entries.filter((e) => validExts.has(
|
|
13624
|
+
const files = entries.filter((e) => validExts.has(path7.extname(e).toLowerCase())).map((e) => path7.join(dirPath, e));
|
|
13181
13625
|
if (files.length === 0) {
|
|
13182
13626
|
throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
|
|
13183
13627
|
}
|
|
@@ -13188,8 +13632,8 @@ function loadDirectory(dirPath) {
|
|
|
13188
13632
|
return results;
|
|
13189
13633
|
}
|
|
13190
13634
|
function loadFile(filePath) {
|
|
13191
|
-
const content =
|
|
13192
|
-
const ext =
|
|
13635
|
+
const content = fs8.readFileSync(filePath, "utf-8");
|
|
13636
|
+
const ext = path7.extname(filePath).toLowerCase();
|
|
13193
13637
|
if (ext === ".json") {
|
|
13194
13638
|
const parsed = parseJsonFile(content, filePath);
|
|
13195
13639
|
return [toLocalDefinition(parsed, filePath)];
|
|
@@ -13611,7 +14055,7 @@ Examples:
|
|
|
13611
14055
|
}
|
|
13612
14056
|
|
|
13613
14057
|
// src/commands/definitions-export.ts
|
|
13614
|
-
import
|
|
14058
|
+
import fs9 from "node:fs";
|
|
13615
14059
|
init_errors();
|
|
13616
14060
|
async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
13617
14061
|
const params = {};
|
|
@@ -13627,7 +14071,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
|
13627
14071
|
const text = await res.text();
|
|
13628
14072
|
if (opts.output) {
|
|
13629
14073
|
try {
|
|
13630
|
-
|
|
14074
|
+
fs9.writeFileSync(opts.output, text);
|
|
13631
14075
|
} catch (err) {
|
|
13632
14076
|
throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
|
|
13633
14077
|
}
|
|
@@ -13935,7 +14379,7 @@ function colorStatus(status) {
|
|
|
13935
14379
|
const fn = STATUS_COLORS[status] ?? source_default.white;
|
|
13936
14380
|
return fn(status);
|
|
13937
14381
|
}
|
|
13938
|
-
function
|
|
14382
|
+
function sleep2(ms) {
|
|
13939
14383
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
13940
14384
|
}
|
|
13941
14385
|
async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 300000) {
|
|
@@ -13945,7 +14389,7 @@ async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 30
|
|
|
13945
14389
|
if (TERMINAL_STATUSES.has(status.status)) {
|
|
13946
14390
|
return status;
|
|
13947
14391
|
}
|
|
13948
|
-
await
|
|
14392
|
+
await sleep2(intervalMs);
|
|
13949
14393
|
}
|
|
13950
14394
|
throw new CLIError(`Timed out waiting for execution ${executionId}`, 1 /* Error */);
|
|
13951
14395
|
}
|
|
@@ -14016,7 +14460,7 @@ Detached. Reattach with: martha workflows execution ${executionId} --follow`));
|
|
|
14016
14460
|
}
|
|
14017
14461
|
return;
|
|
14018
14462
|
}
|
|
14019
|
-
await
|
|
14463
|
+
await sleep2(1000);
|
|
14020
14464
|
current = await ctx.api.get(`/api/admin/executions/${encodeURIComponent(executionId)}`);
|
|
14021
14465
|
}
|
|
14022
14466
|
} finally {
|
|
@@ -14347,8 +14791,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
|
|
|
14347
14791
|
} catch {}
|
|
14348
14792
|
}
|
|
14349
14793
|
if (opts.output) {
|
|
14350
|
-
const
|
|
14351
|
-
await
|
|
14794
|
+
const fs10 = await import("node:fs/promises");
|
|
14795
|
+
await fs10.writeFile(opts.output, toWrite, "utf-8");
|
|
14352
14796
|
if (!isJson()) {
|
|
14353
14797
|
console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
|
|
14354
14798
|
}
|
|
@@ -14755,8 +15199,8 @@ Usage:
|
|
|
14755
15199
|
});
|
|
14756
15200
|
parentCmd.command("self-grant <agent>").description("View or configure an agent's self-provisioning policy (request-then-approve). " + "With no flags, prints the current policy.").option("--enable", "Allow the agent to REQUEST capability grants").option("--disable", "Disallow self-provisioning").option("--scope <scope>", "Scope ceiling: none | read_only | read_write").option("--allow <ref...>", "Add allow-list refs (function:NAME or mcp:integration/name)").option("--remove <ref...>", "Remove allow-list refs").option("--collection-root <id...>", "Add collection-subtree roots the agent may self-grant collection-scoped functions into").option("--remove-collection-root <id...>", "Remove collection roots").option("--max-pending <n>", "Max concurrently-pending requests").action(async (agent, opts) => {
|
|
14757
15201
|
const ctx = getCtx();
|
|
14758
|
-
const
|
|
14759
|
-
const current = await ctx.api.get(
|
|
15202
|
+
const path8 = `${API_PATH}/${encodeURIComponent(agent)}`;
|
|
15203
|
+
const current = await ctx.api.get(path8);
|
|
14760
15204
|
const mutating = opts.enable || opts.disable || opts.scope !== undefined || (opts.allow?.length ?? 0) > 0 || (opts.remove?.length ?? 0) > 0 || (opts.collectionRoot?.length ?? 0) > 0 || (opts.removeCollectionRoot?.length ?? 0) > 0 || opts.maxPending !== undefined;
|
|
14761
15205
|
if (!mutating) {
|
|
14762
15206
|
if (isJson()) {
|
|
@@ -14808,7 +15252,7 @@ Usage:
|
|
|
14808
15252
|
roots.delete(c);
|
|
14809
15253
|
body.self_grant_collection_roots = [...roots];
|
|
14810
15254
|
}
|
|
14811
|
-
const updated = await ctx.api.put(
|
|
15255
|
+
const updated = await ctx.api.put(path8, body);
|
|
14812
15256
|
if (isJson()) {
|
|
14813
15257
|
console.log(JSON.stringify(policyView(updated), null, 2));
|
|
14814
15258
|
return;
|
|
@@ -15055,7 +15499,7 @@ var triggersConfig = {
|
|
|
15055
15499
|
};
|
|
15056
15500
|
|
|
15057
15501
|
// src/commands/documents.ts
|
|
15058
|
-
import
|
|
15502
|
+
import fs10 from "node:fs";
|
|
15059
15503
|
init_errors();
|
|
15060
15504
|
var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
|
|
15061
15505
|
var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -15065,7 +15509,7 @@ function nextSpinner() {
|
|
|
15065
15509
|
spinnerIdx++;
|
|
15066
15510
|
return frame;
|
|
15067
15511
|
}
|
|
15068
|
-
function
|
|
15512
|
+
function sleep3(ms) {
|
|
15069
15513
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
15070
15514
|
}
|
|
15071
15515
|
function formatBytes(bytes) {
|
|
@@ -15125,7 +15569,7 @@ async function pollIngestionUntilDone(ctx, documentId, intervalMs = 2000, timeou
|
|
|
15125
15569
|
if (TERMINAL_STATUSES2.has(status.ingestion_status)) {
|
|
15126
15570
|
return status;
|
|
15127
15571
|
}
|
|
15128
|
-
await
|
|
15572
|
+
await sleep3(intervalMs);
|
|
15129
15573
|
}
|
|
15130
15574
|
throw new CLIError(`Timed out waiting for ingestion of document ${documentId}`, 1 /* Error */);
|
|
15131
15575
|
}
|
|
@@ -15200,7 +15644,7 @@ Detached. Check status with: martha documents status ${documentId}`));
|
|
|
15200
15644
|
}
|
|
15201
15645
|
return;
|
|
15202
15646
|
}
|
|
15203
|
-
await
|
|
15647
|
+
await sleep3(1000);
|
|
15204
15648
|
}
|
|
15205
15649
|
} finally {
|
|
15206
15650
|
process.removeListener("SIGINT", sigintHandler);
|
|
@@ -15416,7 +15860,7 @@ ${items.length} collections`));
|
|
|
15416
15860
|
});
|
|
15417
15861
|
cmd.command("upload <collection-id> <file>").description("Upload a document to a collection").option("--wait", "Wait for ingestion to complete").option("--follow", "Follow ingestion progress in real time").action(async (collectionId, filePath, opts) => {
|
|
15418
15862
|
const ctx = getCtx();
|
|
15419
|
-
if (!
|
|
15863
|
+
if (!fs10.existsSync(filePath)) {
|
|
15420
15864
|
throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
|
|
15421
15865
|
}
|
|
15422
15866
|
const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
|
|
@@ -16045,10 +16489,10 @@ function registerApprovalCommands(program2) {
|
|
|
16045
16489
|
}
|
|
16046
16490
|
|
|
16047
16491
|
// src/commands/tasks.ts
|
|
16048
|
-
import { readFileSync } from "node:fs";
|
|
16492
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
16049
16493
|
init_errors();
|
|
16050
16494
|
var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
16051
|
-
var
|
|
16495
|
+
var sleep4 = (ms, signal) => new Promise((resolve2) => {
|
|
16052
16496
|
if (signal?.aborted) {
|
|
16053
16497
|
resolve2();
|
|
16054
16498
|
return;
|
|
@@ -16414,7 +16858,7 @@ ${items.length} task(s) available`));
|
|
|
16414
16858
|
if (reconnects > maxReconnects) {
|
|
16415
16859
|
throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
|
|
16416
16860
|
}
|
|
16417
|
-
await
|
|
16861
|
+
await sleep4(backoffMs, abort.signal);
|
|
16418
16862
|
backoffMs = Math.min(backoffMs * 2, maxBackoff);
|
|
16419
16863
|
continue;
|
|
16420
16864
|
}
|
|
@@ -16478,7 +16922,7 @@ ${items.length} task(s) available`));
|
|
|
16478
16922
|
if (reconnects > maxReconnects) {
|
|
16479
16923
|
throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
|
|
16480
16924
|
}
|
|
16481
|
-
await
|
|
16925
|
+
await sleep4(backoffMs, abort.signal);
|
|
16482
16926
|
backoffMs = Math.min(backoffMs * 2, maxBackoff);
|
|
16483
16927
|
}
|
|
16484
16928
|
} finally {
|
|
@@ -16491,7 +16935,7 @@ ${items.length} task(s) available`));
|
|
|
16491
16935
|
let rawOutcome;
|
|
16492
16936
|
if (opts.outcomeFile) {
|
|
16493
16937
|
try {
|
|
16494
|
-
rawOutcome =
|
|
16938
|
+
rawOutcome = readFileSync2(opts.outcomeFile, "utf-8");
|
|
16495
16939
|
} catch {
|
|
16496
16940
|
throw new CLIError(`--outcome-file could not be read: ${opts.outcomeFile}`, 4 /* Validation */);
|
|
16497
16941
|
}
|
|
@@ -16993,8 +17437,8 @@ ${data.length} spec(s)`));
|
|
|
16993
17437
|
Resources:
|
|
16994
17438
|
`));
|
|
16995
17439
|
for (const r of resources) {
|
|
16996
|
-
const
|
|
16997
|
-
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${
|
|
17440
|
+
const path8 = r.path || `/${r.name}`;
|
|
17441
|
+
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path8}`));
|
|
16998
17442
|
}
|
|
16999
17443
|
}
|
|
17000
17444
|
try {
|
|
@@ -17014,14 +17458,14 @@ Functions:
|
|
|
17014
17458
|
console.log(source_default.dim(`
|
|
17015
17459
|
Proxy: martha integrations proxy ${name} GET /<path>`));
|
|
17016
17460
|
});
|
|
17017
|
-
cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method,
|
|
17461
|
+
cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path8, opts) => {
|
|
17018
17462
|
const ctx = getCtx();
|
|
17019
17463
|
const upperMethod = method.toUpperCase();
|
|
17020
17464
|
const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
17021
17465
|
if (!allowed.has(upperMethod)) {
|
|
17022
17466
|
throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
|
|
17023
17467
|
}
|
|
17024
|
-
const cleanPath =
|
|
17468
|
+
const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
|
|
17025
17469
|
const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
|
|
17026
17470
|
const params = {};
|
|
17027
17471
|
if (opts.query) {
|
|
@@ -17231,8 +17675,8 @@ async function resolveCredentialValue(value) {
|
|
|
17231
17675
|
if (value === "-")
|
|
17232
17676
|
return readStdin();
|
|
17233
17677
|
if (value.startsWith("@")) {
|
|
17234
|
-
const
|
|
17235
|
-
return (await
|
|
17678
|
+
const fs11 = await import("node:fs/promises");
|
|
17679
|
+
return (await fs11.readFile(value.slice(1), "utf-8")).trim();
|
|
17236
17680
|
}
|
|
17237
17681
|
return value;
|
|
17238
17682
|
}
|
|
@@ -17347,8 +17791,8 @@ Notification connections`));
|
|
|
17347
17791
|
if (credentialValue === "-") {
|
|
17348
17792
|
credentialValue = await readStdin2();
|
|
17349
17793
|
} else if (credentialValue?.startsWith("@")) {
|
|
17350
|
-
const
|
|
17351
|
-
credentialValue = await
|
|
17794
|
+
const fs11 = await import("node:fs/promises");
|
|
17795
|
+
credentialValue = await fs11.readFile(credentialValue.slice(1), "utf-8");
|
|
17352
17796
|
}
|
|
17353
17797
|
if (!credentialValue) {
|
|
17354
17798
|
throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
|
|
@@ -17398,11 +17842,11 @@ async function readStdin2() {
|
|
|
17398
17842
|
|
|
17399
17843
|
// src/commands/messaging.ts
|
|
17400
17844
|
init_errors();
|
|
17401
|
-
async function messagingFetch(baseUrl,
|
|
17402
|
-
if (/^(https?:)?\/\//i.test(
|
|
17845
|
+
async function messagingFetch(baseUrl, path8, opts) {
|
|
17846
|
+
if (/^(https?:)?\/\//i.test(path8)) {
|
|
17403
17847
|
throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
|
|
17404
17848
|
}
|
|
17405
|
-
const url = new URL(
|
|
17849
|
+
const url = new URL(path8, baseUrl);
|
|
17406
17850
|
const headers = {
|
|
17407
17851
|
"Content-Type": "application/json"
|
|
17408
17852
|
};
|
|
@@ -18682,7 +19126,7 @@ ${models.length} models`));
|
|
|
18682
19126
|
}
|
|
18683
19127
|
|
|
18684
19128
|
// src/commands/wiki.ts
|
|
18685
|
-
import
|
|
19129
|
+
import fs11 from "node:fs";
|
|
18686
19130
|
init_errors();
|
|
18687
19131
|
function registerWikiCommands(program2) {
|
|
18688
19132
|
const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
|
|
@@ -18729,14 +19173,14 @@ function registerWikiCommands(program2) {
|
|
|
18729
19173
|
console.log(source_default.dim(`
|
|
18730
19174
|
${pages.length} pages`));
|
|
18731
19175
|
});
|
|
18732
|
-
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (
|
|
19176
|
+
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (path8, opts) => {
|
|
18733
19177
|
const ctx = getCtx();
|
|
18734
|
-
const safe =
|
|
19178
|
+
const safe = path8.split("/").map(encodeURIComponent).join("/");
|
|
18735
19179
|
const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
|
|
18736
19180
|
headers: { Accept: "text/markdown,*/*" }
|
|
18737
19181
|
});
|
|
18738
19182
|
if (resp.status === 404) {
|
|
18739
|
-
throw new CLIError(`page not found: ${
|
|
19183
|
+
throw new CLIError(`page not found: ${path8}`, 3 /* NotFound */);
|
|
18740
19184
|
}
|
|
18741
19185
|
if (!resp.ok) {
|
|
18742
19186
|
const detail = await resp.text();
|
|
@@ -18744,16 +19188,16 @@ ${pages.length} pages`));
|
|
|
18744
19188
|
}
|
|
18745
19189
|
const body = await resp.text();
|
|
18746
19190
|
if (opts.out) {
|
|
18747
|
-
|
|
19191
|
+
fs11.writeFileSync(opts.out, body);
|
|
18748
19192
|
if (!isJson()) {
|
|
18749
19193
|
console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
|
|
18750
19194
|
} else {
|
|
18751
|
-
console.log(JSON.stringify({ path:
|
|
19195
|
+
console.log(JSON.stringify({ path: path8, bytes: body.length, file: opts.out }));
|
|
18752
19196
|
}
|
|
18753
19197
|
return;
|
|
18754
19198
|
}
|
|
18755
19199
|
if (isJson()) {
|
|
18756
|
-
console.log(JSON.stringify({ path:
|
|
19200
|
+
console.log(JSON.stringify({ path: path8, body, etag: resp.headers.get("ETag") }));
|
|
18757
19201
|
} else {
|
|
18758
19202
|
process.stdout.write(body);
|
|
18759
19203
|
}
|
|
@@ -18848,10 +19292,10 @@ ${pages.length} pages`));
|
|
|
18848
19292
|
}
|
|
18849
19293
|
if (opts.compilePromptOverrideFile) {
|
|
18850
19294
|
const file = String(opts.compilePromptOverrideFile);
|
|
18851
|
-
if (!
|
|
19295
|
+
if (!fs11.existsSync(file)) {
|
|
18852
19296
|
throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
|
|
18853
19297
|
}
|
|
18854
|
-
const content =
|
|
19298
|
+
const content = fs11.readFileSync(file, "utf8");
|
|
18855
19299
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
18856
19300
|
if (bytes > 16 * 1024) {
|
|
18857
19301
|
throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -18875,7 +19319,7 @@ ${pages.length} pages`));
|
|
|
18875
19319
|
const ctx = getCtx();
|
|
18876
19320
|
const resp = await ctx.api.get("/api/wiki/schema");
|
|
18877
19321
|
if (opts.out) {
|
|
18878
|
-
|
|
19322
|
+
fs11.writeFileSync(opts.out, resp.body);
|
|
18879
19323
|
if (!isJson()) {
|
|
18880
19324
|
console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
|
|
18881
19325
|
} else {
|
|
@@ -18891,10 +19335,10 @@ ${pages.length} pages`));
|
|
|
18891
19335
|
});
|
|
18892
19336
|
schemaCmd.command("set").description("Replace the tenant wiki schema with the contents of <file>").requiredOption("--file <file>", "Path to schema markdown file").action(async (opts) => {
|
|
18893
19337
|
const ctx = getCtx();
|
|
18894
|
-
if (!
|
|
19338
|
+
if (!fs11.existsSync(opts.file)) {
|
|
18895
19339
|
throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
|
|
18896
19340
|
}
|
|
18897
|
-
const body =
|
|
19341
|
+
const body = fs11.readFileSync(opts.file, "utf8");
|
|
18898
19342
|
const bytes = Buffer.byteLength(body, "utf8");
|
|
18899
19343
|
if (bytes > 64 * 1024) {
|
|
18900
19344
|
throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -19420,8 +19864,8 @@ async function resolveCredentialFlag(value) {
|
|
|
19420
19864
|
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
19421
19865
|
}
|
|
19422
19866
|
if (value.startsWith("@")) {
|
|
19423
|
-
const
|
|
19424
|
-
return (await
|
|
19867
|
+
const fs12 = await import("node:fs/promises");
|
|
19868
|
+
return (await fs12.readFile(value.slice(1), "utf-8")).trim();
|
|
19425
19869
|
}
|
|
19426
19870
|
return value;
|
|
19427
19871
|
}
|
|
@@ -20607,19 +21051,19 @@ function registerDoctorCommand(program2) {
|
|
|
20607
21051
|
|
|
20608
21052
|
// src/commands/skill.ts
|
|
20609
21053
|
init_errors();
|
|
20610
|
-
import
|
|
20611
|
-
import
|
|
21054
|
+
import fs12 from "node:fs";
|
|
21055
|
+
import path8 from "node:path";
|
|
20612
21056
|
import { fileURLToPath } from "node:url";
|
|
20613
21057
|
function locateSkill() {
|
|
20614
|
-
const here =
|
|
21058
|
+
const here = path8.dirname(fileURLToPath(import.meta.url));
|
|
20615
21059
|
const candidates = [
|
|
20616
|
-
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
|
|
21060
|
+
path8.join(here, "skills", "martha-cli", "SKILL.md"),
|
|
21061
|
+
path8.join(here, "..", "skills", "martha-cli", "SKILL.md"),
|
|
21062
|
+
path8.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
|
|
21063
|
+
path8.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
|
|
20620
21064
|
];
|
|
20621
21065
|
for (const p of candidates) {
|
|
20622
|
-
if (
|
|
21066
|
+
if (fs12.existsSync(p))
|
|
20623
21067
|
return p;
|
|
20624
21068
|
}
|
|
20625
21069
|
return null;
|
|
@@ -20629,7 +21073,7 @@ async function skillCommand() {
|
|
|
20629
21073
|
if (!skillPath) {
|
|
20630
21074
|
throw new CLIError("SKILL.md not found in the installed package. Reinstall via `npm i -g @aiaiai-pt/martha-cli` or `npx -y @aiaiai-pt/martha-cli@latest skill`.", 1 /* Error */);
|
|
20631
21075
|
}
|
|
20632
|
-
const body =
|
|
21076
|
+
const body = fs12.readFileSync(skillPath, "utf-8");
|
|
20633
21077
|
process.stdout.write(body);
|
|
20634
21078
|
}
|
|
20635
21079
|
function registerSkillCommand(program2) {
|