@aiaiai-pt/martha-cli 0.24.0 → 0.26.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 +183 -90
- 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,11 +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";
|
|
11080
|
+
init_config();
|
|
11077
11081
|
init_errors();
|
|
11078
11082
|
|
|
11079
11083
|
// src/version.ts
|
|
11080
|
-
var CLI_VERSION = "0.
|
|
11084
|
+
var CLI_VERSION = "0.26.0";
|
|
11081
11085
|
|
|
11082
11086
|
// src/commands/sessions.ts
|
|
11083
11087
|
function relativeTime(iso) {
|
|
@@ -11351,6 +11355,7 @@ function createApprovalTagStripper() {
|
|
|
11351
11355
|
|
|
11352
11356
|
// src/lib/local-exec.ts
|
|
11353
11357
|
import { spawn } from "node:child_process";
|
|
11358
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
11354
11359
|
import { closeSync, openSync, promises as fs5 } from "node:fs";
|
|
11355
11360
|
import * as path4 from "node:path";
|
|
11356
11361
|
var DEFAULT_TIMEOUT_MS = 20000;
|
|
@@ -11373,15 +11378,26 @@ async function runHostCommand(command, toolCallId, opts) {
|
|
|
11373
11378
|
return "";
|
|
11374
11379
|
}
|
|
11375
11380
|
};
|
|
11381
|
+
const stdin = opts.stdinData != null ? "pipe" : "ignore";
|
|
11376
11382
|
return await new Promise((resolve, reject) => {
|
|
11377
11383
|
let settled = false;
|
|
11378
|
-
const child = spawn(
|
|
11384
|
+
const child = opts.argv ? spawn(opts.argv[0], opts.argv.slice(1), {
|
|
11385
|
+
shell: false,
|
|
11386
|
+
cwd: opts.cwd,
|
|
11387
|
+
detached: true,
|
|
11388
|
+
stdio: [stdin, fd, fd]
|
|
11389
|
+
}) : spawn(command, {
|
|
11379
11390
|
shell: true,
|
|
11380
11391
|
cwd: opts.cwd,
|
|
11381
11392
|
detached: true,
|
|
11382
|
-
stdio: [
|
|
11393
|
+
stdio: [stdin, fd, fd]
|
|
11383
11394
|
});
|
|
11384
11395
|
closeSync(fd);
|
|
11396
|
+
if (opts.stdinData != null && child.stdin) {
|
|
11397
|
+
child.stdin.on("error", () => {});
|
|
11398
|
+
child.stdin.write(opts.stdinData);
|
|
11399
|
+
child.stdin.end();
|
|
11400
|
+
}
|
|
11385
11401
|
const timer = setTimeout(async () => {
|
|
11386
11402
|
if (settled)
|
|
11387
11403
|
return;
|
|
@@ -11392,7 +11408,8 @@ async function runHostCommand(command, toolCallId, opts) {
|
|
|
11392
11408
|
stderr: "",
|
|
11393
11409
|
exit_code: 0,
|
|
11394
11410
|
backgrounded: true,
|
|
11395
|
-
pid: child.pid
|
|
11411
|
+
pid: child.pid,
|
|
11412
|
+
log_path: logPath
|
|
11396
11413
|
});
|
|
11397
11414
|
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
11398
11415
|
child.on("error", (e) => {
|
|
@@ -11407,13 +11424,21 @@ async function runHostCommand(command, toolCallId, opts) {
|
|
|
11407
11424
|
return;
|
|
11408
11425
|
settled = true;
|
|
11409
11426
|
clearTimeout(timer);
|
|
11410
|
-
resolve({
|
|
11427
|
+
resolve({
|
|
11428
|
+
stdout: await readLog(),
|
|
11429
|
+
stderr: "",
|
|
11430
|
+
exit_code: code ?? -1,
|
|
11431
|
+
log_path: logPath
|
|
11432
|
+
});
|
|
11411
11433
|
});
|
|
11412
11434
|
});
|
|
11413
11435
|
}
|
|
11414
11436
|
function safeName(s) {
|
|
11415
11437
|
return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
|
|
11416
11438
|
}
|
|
11439
|
+
function mintExecSecret() {
|
|
11440
|
+
return randomBytes2(32).toString("hex");
|
|
11441
|
+
}
|
|
11417
11442
|
|
|
11418
11443
|
class Journal {
|
|
11419
11444
|
cwd;
|
|
@@ -11425,9 +11450,13 @@ class Journal {
|
|
|
11425
11450
|
}
|
|
11426
11451
|
async load() {
|
|
11427
11452
|
try {
|
|
11428
|
-
|
|
11453
|
+
const raw = JSON.parse(await fs5.readFile(this.file(), "utf8"));
|
|
11454
|
+
if (raw["entries"] !== undefined) {
|
|
11455
|
+
return raw;
|
|
11456
|
+
}
|
|
11457
|
+
return { entries: raw, secrets: {} };
|
|
11429
11458
|
} catch {
|
|
11430
|
-
return {};
|
|
11459
|
+
return { entries: {}, secrets: {} };
|
|
11431
11460
|
}
|
|
11432
11461
|
}
|
|
11433
11462
|
async save(data) {
|
|
@@ -11438,11 +11467,11 @@ class Journal {
|
|
|
11438
11467
|
return `${sessionId}:${toolCallId}`;
|
|
11439
11468
|
}
|
|
11440
11469
|
async lookup(sessionId, toolCallId) {
|
|
11441
|
-
return (await this.load())[Journal.key(sessionId, toolCallId)];
|
|
11470
|
+
return (await this.load()).entries[Journal.key(sessionId, toolCallId)];
|
|
11442
11471
|
}
|
|
11443
11472
|
async recordRunning(sessionId, toolCallId, command, now) {
|
|
11444
11473
|
const data = await this.load();
|
|
11445
|
-
data[Journal.key(sessionId, toolCallId)] = {
|
|
11474
|
+
data.entries[Journal.key(sessionId, toolCallId)] = {
|
|
11446
11475
|
state: "running",
|
|
11447
11476
|
command,
|
|
11448
11477
|
started_at: now
|
|
@@ -11452,8 +11481,8 @@ class Journal {
|
|
|
11452
11481
|
async recordDone(sessionId, toolCallId, result) {
|
|
11453
11482
|
const data = await this.load();
|
|
11454
11483
|
const k = Journal.key(sessionId, toolCallId);
|
|
11455
|
-
const prev = data[k];
|
|
11456
|
-
data[k] = {
|
|
11484
|
+
const prev = data.entries[k];
|
|
11485
|
+
data.entries[k] = {
|
|
11457
11486
|
state: "done",
|
|
11458
11487
|
command: prev?.command ?? "",
|
|
11459
11488
|
started_at: prev?.started_at ?? "",
|
|
@@ -11461,6 +11490,14 @@ class Journal {
|
|
|
11461
11490
|
};
|
|
11462
11491
|
await this.save(data);
|
|
11463
11492
|
}
|
|
11493
|
+
async recordSecret(sessionId, toolCallId, secret) {
|
|
11494
|
+
const data = await this.load();
|
|
11495
|
+
data.secrets[Journal.key(sessionId, toolCallId)] = secret;
|
|
11496
|
+
await this.save(data);
|
|
11497
|
+
}
|
|
11498
|
+
async lookupSecret(sessionId, toolCallId) {
|
|
11499
|
+
return (await this.load()).secrets[Journal.key(sessionId, toolCallId)];
|
|
11500
|
+
}
|
|
11464
11501
|
}
|
|
11465
11502
|
function decideFromJournal(entry) {
|
|
11466
11503
|
if (!entry)
|
|
@@ -11816,66 +11853,51 @@ function parseHostFileRequests(data) {
|
|
|
11816
11853
|
}
|
|
11817
11854
|
|
|
11818
11855
|
// src/lib/local-agent.ts
|
|
11819
|
-
|
|
11820
|
-
|
|
11856
|
+
var AGENT_PRESETS = {
|
|
11857
|
+
claude: {
|
|
11858
|
+
name: "claude",
|
|
11859
|
+
command: ["claude", "-p"],
|
|
11860
|
+
promptVia: "argv",
|
|
11861
|
+
description: "Claude Code headless (prompt passed as an argument)."
|
|
11862
|
+
},
|
|
11863
|
+
codex: {
|
|
11864
|
+
name: "codex",
|
|
11865
|
+
command: ["codex", "exec", "--skip-git-repo-check"],
|
|
11866
|
+
promptVia: "stdin",
|
|
11867
|
+
description: "OpenAI Codex CLI (prompt on stdin; runs outside a git repo)."
|
|
11868
|
+
},
|
|
11869
|
+
"cursor-agent": {
|
|
11870
|
+
name: "cursor-agent",
|
|
11871
|
+
command: ["cursor-agent", "--print", "--trust"],
|
|
11872
|
+
promptVia: "argv",
|
|
11873
|
+
description: "Cursor Agent headless (prompt passed as an argument)."
|
|
11874
|
+
}
|
|
11875
|
+
};
|
|
11876
|
+
var DEFAULT_LOCAL_AGENT = "codex";
|
|
11821
11877
|
var DEFAULT_LOCAL_AGENT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_LOCAL_AGENT_TIMEOUT_MS;
|
|
11833
|
-
return await new Promise((resolve2, reject) => {
|
|
11834
|
-
let out = "";
|
|
11835
|
-
let settled = false;
|
|
11836
|
-
let child;
|
|
11837
|
-
try {
|
|
11838
|
-
child = spawn2(argv[0], argv.slice(1), {
|
|
11839
|
-
cwd: opts.cwd,
|
|
11840
|
-
shell: false,
|
|
11841
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
11842
|
-
});
|
|
11843
|
-
} catch (e) {
|
|
11844
|
-
reject(e);
|
|
11845
|
-
return;
|
|
11846
|
-
}
|
|
11847
|
-
const append = (chunk) => {
|
|
11848
|
-
if (out.length < cap)
|
|
11849
|
-
out += chunk.toString("utf8");
|
|
11878
|
+
function resolveAgentInvocation(value) {
|
|
11879
|
+
const trimmed = (value || "").trim();
|
|
11880
|
+
if (!trimmed)
|
|
11881
|
+
throw new Error("local agent command is empty");
|
|
11882
|
+
const preset = AGENT_PRESETS[trimmed];
|
|
11883
|
+
if (preset) {
|
|
11884
|
+
return {
|
|
11885
|
+
command: preset.command,
|
|
11886
|
+
promptVia: preset.promptVia,
|
|
11887
|
+
source: preset.name
|
|
11850
11888
|
};
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
}, timeoutMs);
|
|
11864
|
-
child.on("error", (e) => {
|
|
11865
|
-
if (settled)
|
|
11866
|
-
return;
|
|
11867
|
-
settled = true;
|
|
11868
|
-
clearTimeout(timer);
|
|
11869
|
-
reject(e);
|
|
11870
|
-
});
|
|
11871
|
-
child.on("close", (code) => {
|
|
11872
|
-
if (settled)
|
|
11873
|
-
return;
|
|
11874
|
-
settled = true;
|
|
11875
|
-
clearTimeout(timer);
|
|
11876
|
-
resolve2({ text: out.slice(0, cap), exit_code: code ?? -1 });
|
|
11877
|
-
});
|
|
11878
|
-
});
|
|
11889
|
+
}
|
|
11890
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
11891
|
+
return { command: parts, promptVia: "argv", source: trimmed };
|
|
11892
|
+
}
|
|
11893
|
+
function buildSpawn(invocation, task) {
|
|
11894
|
+
if (invocation.command.length === 0)
|
|
11895
|
+
throw new Error("local agent command is empty");
|
|
11896
|
+
const [bin, ...rest] = invocation.command;
|
|
11897
|
+
if (invocation.promptVia === "stdin") {
|
|
11898
|
+
return { bin, args: rest, stdin: task };
|
|
11899
|
+
}
|
|
11900
|
+
return { bin, args: [...rest, task], stdin: null };
|
|
11879
11901
|
}
|
|
11880
11902
|
function parseLocalAgentRequests(data) {
|
|
11881
11903
|
let tools;
|
|
@@ -12032,7 +12054,7 @@ async function resolveHostFile(req, state) {
|
|
|
12032
12054
|
return { error: true, message: result.message };
|
|
12033
12055
|
return result;
|
|
12034
12056
|
}
|
|
12035
|
-
async function resolveLocalAgent(req, state) {
|
|
12057
|
+
async function resolveLocalAgent(sessionId, toolCallId, req, state) {
|
|
12036
12058
|
if (!state.autoYes) {
|
|
12037
12059
|
process.stderr.write(source_default.yellow(`
|
|
12038
12060
|
[local_agent] refused (re-run with --yes to allow): ${req.task.slice(0, 80)}
|
|
@@ -12042,28 +12064,87 @@ async function resolveLocalAgent(req, state) {
|
|
|
12042
12064
|
message: "Local agent delegation declined; re-run with --yes to allow."
|
|
12043
12065
|
};
|
|
12044
12066
|
}
|
|
12045
|
-
|
|
12067
|
+
let invocation;
|
|
12068
|
+
try {
|
|
12069
|
+
invocation = resolveAgentInvocation(state.localAgent);
|
|
12070
|
+
} catch (e) {
|
|
12071
|
+
return { error: true, message: `invalid local agent config: ${String(e)}` };
|
|
12072
|
+
}
|
|
12073
|
+
const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
|
|
12074
|
+
if (decision.kind === "replay")
|
|
12075
|
+
return decision.result;
|
|
12076
|
+
if (decision.kind === "crashed") {
|
|
12077
|
+
return {
|
|
12078
|
+
error: true,
|
|
12079
|
+
exit_code: -1,
|
|
12080
|
+
message: `A previous local_agent run (${decision.command}) did not complete; ` + `not re-running. Its log survives under .martha-runner/ — read it with host_read.`
|
|
12081
|
+
};
|
|
12082
|
+
}
|
|
12046
12083
|
const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
|
|
12084
|
+
const { bin, args, stdin } = buildSpawn(invocation, req.task);
|
|
12047
12085
|
process.stderr.write(source_default.dim(`
|
|
12048
|
-
[local_agent] (${
|
|
12086
|
+
[local_agent] (${invocation.source}) ${req.task.slice(0, 100)}
|
|
12049
12087
|
`));
|
|
12088
|
+
await state.journal.recordRunning(sessionId, toolCallId, `local_agent:${invocation.source}`, new Date().toISOString());
|
|
12089
|
+
let result;
|
|
12050
12090
|
try {
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
};
|
|
12091
|
+
result = await runHostCommand(`local_agent:${invocation.source}`, toolCallId, {
|
|
12092
|
+
cwd,
|
|
12093
|
+
argv: [bin, ...args],
|
|
12094
|
+
stdinData: stdin ?? undefined,
|
|
12095
|
+
timeoutMs: DEFAULT_LOCAL_AGENT_TIMEOUT_MS
|
|
12096
|
+
});
|
|
12057
12097
|
} catch (e) {
|
|
12098
|
+
const failed = {
|
|
12099
|
+
stdout: "",
|
|
12100
|
+
stderr: String(e),
|
|
12101
|
+
exit_code: -1
|
|
12102
|
+
};
|
|
12103
|
+
await state.journal.recordDone(sessionId, toolCallId, failed);
|
|
12058
12104
|
return {
|
|
12059
12105
|
error: true,
|
|
12060
|
-
message: `local agent failed to start (${
|
|
12106
|
+
message: `local agent failed to start (${invocation.source}): ${String(e)}. ` + `Pick an installed agent with --local-agent <claude|codex|cursor-agent> ` + `or set one via 'martha config set local_agent <name>'.`
|
|
12107
|
+
};
|
|
12108
|
+
}
|
|
12109
|
+
await state.journal.recordDone(sessionId, toolCallId, result);
|
|
12110
|
+
if (result.backgrounded) {
|
|
12111
|
+
const mins = Math.round(DEFAULT_LOCAL_AGENT_TIMEOUT_MS / 60000);
|
|
12112
|
+
return {
|
|
12113
|
+
text: result.stdout || "(no output captured yet)",
|
|
12114
|
+
backgrounded: true,
|
|
12115
|
+
pid: result.pid,
|
|
12116
|
+
log_path: result.log_path,
|
|
12117
|
+
message: `The local agent is still running after ${mins} min; it was left running ` + `in the background (it survives this session). Read its live log and final ` + `result with host_read on ${result.log_path}.`
|
|
12061
12118
|
};
|
|
12062
12119
|
}
|
|
12120
|
+
return {
|
|
12121
|
+
text: result.stdout || "(local agent produced no output)",
|
|
12122
|
+
exit_code: result.exit_code,
|
|
12123
|
+
log_path: result.log_path
|
|
12124
|
+
};
|
|
12063
12125
|
}
|
|
12064
|
-
async function
|
|
12126
|
+
async function claimGate(ctx, sessionId, toolCallId, state) {
|
|
12127
|
+
if (state.claimed.has(toolCallId))
|
|
12128
|
+
return;
|
|
12129
|
+
const secret = state.secrets.get(toolCallId) ?? mintExecSecret();
|
|
12130
|
+
const secretHash = createHash2("sha256").update(secret).digest("hex");
|
|
12131
|
+
state.secrets.set(toolCallId, secret);
|
|
12132
|
+
await state.journal.recordSecret(sessionId, toolCallId, secret);
|
|
12065
12133
|
try {
|
|
12066
|
-
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-
|
|
12134
|
+
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-claim`, { tool_call_id: toolCallId, secret_hash: secretHash });
|
|
12135
|
+
state.claimed.add(toolCallId);
|
|
12136
|
+
} catch (e) {
|
|
12137
|
+
process.stderr.write(source_default.yellow(`[local-exec] failed to claim gate for ${toolCallId}: ${String(e)}
|
|
12138
|
+
`));
|
|
12139
|
+
}
|
|
12140
|
+
}
|
|
12141
|
+
async function _postToolResult(ctx, sessionId, toolCallId, result, label, execSecret) {
|
|
12142
|
+
try {
|
|
12143
|
+
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, {
|
|
12144
|
+
tool_call_id: toolCallId,
|
|
12145
|
+
result,
|
|
12146
|
+
...execSecret !== undefined ? { exec_secret: execSecret } : {}
|
|
12147
|
+
});
|
|
12067
12148
|
} catch (e) {
|
|
12068
12149
|
process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
|
|
12069
12150
|
`));
|
|
@@ -12075,40 +12156,45 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
|
|
|
12075
12156
|
if (state.handled.has(id))
|
|
12076
12157
|
continue;
|
|
12077
12158
|
state.handled.add(id);
|
|
12159
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12078
12160
|
const result = await resolveHostExec(sessionId, id, req.command, state);
|
|
12079
|
-
await _postToolResult(ctx, sessionId, id, result, "host_exec");
|
|
12161
|
+
await _postToolResult(ctx, sessionId, id, result, "host_exec", state.secrets.get(id));
|
|
12080
12162
|
}
|
|
12081
12163
|
for (const req of parseHostScreenshotRequests(data)) {
|
|
12082
12164
|
const id = req.toolCallId;
|
|
12083
12165
|
if (state.handled.has(id))
|
|
12084
12166
|
continue;
|
|
12085
12167
|
state.handled.add(id);
|
|
12168
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12086
12169
|
const result = await resolveHostScreenshot(ctx, sessionId, req, state);
|
|
12087
|
-
await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
|
|
12170
|
+
await _postToolResult(ctx, sessionId, id, result, "host_screenshot", state.secrets.get(id));
|
|
12088
12171
|
}
|
|
12089
12172
|
for (const req of parseHostBrowserRequests(data)) {
|
|
12090
12173
|
const id = req.toolCallId;
|
|
12091
12174
|
if (state.handled.has(id))
|
|
12092
12175
|
continue;
|
|
12093
12176
|
state.handled.add(id);
|
|
12177
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12094
12178
|
const result = await resolveHostBrowser(ctx, sessionId, req, state);
|
|
12095
|
-
await _postToolResult(ctx, sessionId, id, result, "host_browser");
|
|
12179
|
+
await _postToolResult(ctx, sessionId, id, result, "host_browser", state.secrets.get(id));
|
|
12096
12180
|
}
|
|
12097
12181
|
for (const req of parseHostFileRequests(data)) {
|
|
12098
12182
|
const id = req.toolCallId;
|
|
12099
12183
|
if (state.handled.has(id))
|
|
12100
12184
|
continue;
|
|
12101
12185
|
state.handled.add(id);
|
|
12186
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12102
12187
|
const result = await resolveHostFile(req, state);
|
|
12103
|
-
await _postToolResult(ctx, sessionId, id, result, req.action);
|
|
12188
|
+
await _postToolResult(ctx, sessionId, id, result, req.action, state.secrets.get(id));
|
|
12104
12189
|
}
|
|
12105
12190
|
for (const req of parseLocalAgentRequests(data)) {
|
|
12106
12191
|
const id = req.toolCallId;
|
|
12107
12192
|
if (state.handled.has(id))
|
|
12108
12193
|
continue;
|
|
12109
12194
|
state.handled.add(id);
|
|
12110
|
-
|
|
12111
|
-
await
|
|
12195
|
+
await claimGate(ctx, sessionId, id, state);
|
|
12196
|
+
const result = await resolveLocalAgent(sessionId, id, req, state);
|
|
12197
|
+
await _postToolResult(ctx, sessionId, id, result, "local_agent", state.secrets.get(id));
|
|
12112
12198
|
}
|
|
12113
12199
|
}
|
|
12114
12200
|
function parseSlashCommand(input) {
|
|
@@ -12164,8 +12250,11 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
12164
12250
|
const localExecState = opts.localExec ? {
|
|
12165
12251
|
cwd: opts.localExec.cwd,
|
|
12166
12252
|
autoYes: opts.localExec.autoYes,
|
|
12253
|
+
localAgent: opts.localExec.agent,
|
|
12167
12254
|
handled: new Set,
|
|
12168
|
-
journal: new Journal(opts.localExec.cwd)
|
|
12255
|
+
journal: new Journal(opts.localExec.cwd),
|
|
12256
|
+
secrets: new Map,
|
|
12257
|
+
claimed: new Set
|
|
12169
12258
|
} : null;
|
|
12170
12259
|
const reqBody = { content: message };
|
|
12171
12260
|
if (opts.localExec)
|
|
@@ -12685,7 +12774,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
|
|
|
12685
12774
|
}
|
|
12686
12775
|
}
|
|
12687
12776
|
function registerChatCommand(program2) {
|
|
12688
|
-
program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--local-exec", "Allow the agent to run host tools on THIS machine (#568). Requires the server to have LOCAL_EXEC_ENABLED=1.").option("--yes", "Auto-approve host tool execution (required with --local-exec for non-interactive runs).").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
|
|
12777
|
+
program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--local-exec", "Allow the agent to run host tools on THIS machine (#568). Requires the server to have LOCAL_EXEC_ENABLED=1.").option("--yes", "Auto-approve host tool execution (required with --local-exec for non-interactive runs).").option("--local-agent <name>", "Which local coding agent `local_agent` delegates to: a preset (claude | codex | cursor-agent) or a raw command. Overrides MARTHA_LOCAL_AGENT_CMD and `config local_agent` for this run.").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
|
|
12689
12778
|
const ctx = createContext({
|
|
12690
12779
|
profileOverride: program2.opts().profile,
|
|
12691
12780
|
verbose: program2.opts().verbose
|
|
@@ -12720,7 +12809,8 @@ function registerChatCommand(program2) {
|
|
|
12720
12809
|
}
|
|
12721
12810
|
const showTools = !!opts.showTools;
|
|
12722
12811
|
const timeoutMs = opts.timeout ? parseTimeoutSeconds(opts.timeout) * 1000 : undefined;
|
|
12723
|
-
const
|
|
12812
|
+
const localAgentChoice = opts.localAgent || process.env.MARTHA_LOCAL_AGENT_CMD || loadConfig().settings.local_agent || DEFAULT_LOCAL_AGENT;
|
|
12813
|
+
const localExec = opts.localExec ? { cwd: process.cwd(), autoYes: !!opts.yes, agent: localAgentChoice } : undefined;
|
|
12724
12814
|
if (opts.message) {
|
|
12725
12815
|
await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs, localExec);
|
|
12726
12816
|
} else {
|
|
@@ -12841,6 +12931,8 @@ Global Settings
|
|
|
12841
12931
|
console.log(` Editor: ${settings.editor}`);
|
|
12842
12932
|
if (settings.pager)
|
|
12843
12933
|
console.log(` Pager: ${settings.pager}`);
|
|
12934
|
+
if (settings.local_agent)
|
|
12935
|
+
console.log(` Local agent: ${settings.local_agent}`);
|
|
12844
12936
|
if (settings.chat) {
|
|
12845
12937
|
if (settings.chat.show_tool_calls)
|
|
12846
12938
|
console.log(` Tool calls: ${settings.chat.show_tool_calls}`);
|
|
@@ -12879,6 +12971,7 @@ var SETTINGS_KEYS = {
|
|
|
12879
12971
|
color: (s) => s.color,
|
|
12880
12972
|
editor: (s) => s.editor,
|
|
12881
12973
|
pager: (s) => s.pager,
|
|
12974
|
+
local_agent: (s) => s.local_agent,
|
|
12882
12975
|
"chat.show_tool_calls": (s) => s.chat?.show_tool_calls,
|
|
12883
12976
|
"chat.markdown": (s) => s.chat?.markdown,
|
|
12884
12977
|
"chat.history_file": (s) => s.chat?.history_file
|