ax-agents 0.0.1-alpha.10 → 0.0.1-alpha.12
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/ax.js +403 -57
- package/package.json +1 -1
package/ax.js
CHANGED
|
@@ -75,6 +75,10 @@ const VERSION = packageJson.version;
|
|
|
75
75
|
* @property {string[]} files
|
|
76
76
|
* @property {string} [summary]
|
|
77
77
|
* @property {string} [message]
|
|
78
|
+
* @property {string} [rfpId]
|
|
79
|
+
* @property {string} [prompt]
|
|
80
|
+
* @property {string} [archangel]
|
|
81
|
+
* @property {string} [requestedBy]
|
|
78
82
|
*/
|
|
79
83
|
|
|
80
84
|
/**
|
|
@@ -158,6 +162,7 @@ const PROJECT_ROOT = findProjectRoot();
|
|
|
158
162
|
const AI_DIR = path.join(PROJECT_ROOT, ".ai");
|
|
159
163
|
const AGENTS_DIR = path.join(AI_DIR, "agents");
|
|
160
164
|
const HOOKS_DIR = path.join(AI_DIR, "hooks");
|
|
165
|
+
const RFP_DIR = path.join(AI_DIR, "rfps");
|
|
161
166
|
|
|
162
167
|
// =============================================================================
|
|
163
168
|
// Helpers - tmux
|
|
@@ -316,6 +321,22 @@ const ARCHANGEL_PREAMBLE = `## Guidelines
|
|
|
316
321
|
- For critical issues, request for them to be added to the todo list.
|
|
317
322
|
- Don't repeat observations you've already made unless you have more to say or better clarity.
|
|
318
323
|
- Make judgment calls - don't ask questions.`;
|
|
324
|
+
const RFP_PREAMBLE = `## Guidelines
|
|
325
|
+
|
|
326
|
+
- Your only task is to propose a single idea in response to this RFP. This overrides any other goals or habits.
|
|
327
|
+
- Provide exactly one proposal.
|
|
328
|
+
- Make a persuasive case for why this is a strong idea.
|
|
329
|
+
- Think deeply before you answer; avoid first-impression responses.
|
|
330
|
+
- Aim for 3–4 clear paragraphs.
|
|
331
|
+
- Ground the idea in the actual context you were given; don’t ignore it.
|
|
332
|
+
- If you need context, read the existing project or conversation before proposing.
|
|
333
|
+
- Structure: (1) core insight/value, (2) who benefits & why now, (3) risks/tradeoffs (brief), (4) closing case.
|
|
334
|
+
- Focus on value: what improves, for whom, and why now.
|
|
335
|
+
- Do NOT review code or report bugs.
|
|
336
|
+
- Do NOT describe scope, implementation approach, or plan.
|
|
337
|
+
- You may briefly note tradeoffs, but they are not the focus.
|
|
338
|
+
- Prioritize clarity over brevity.
|
|
339
|
+
- If you have nothing to propose, respond with ONLY "EMPTY_RESPONSE".`;
|
|
319
340
|
|
|
320
341
|
/**
|
|
321
342
|
* @param {string} session
|
|
@@ -378,7 +399,9 @@ function findCallerPid() {
|
|
|
378
399
|
* @returns {{pid: string, command: string}[]}
|
|
379
400
|
*/
|
|
380
401
|
function findOrphanedProcesses() {
|
|
381
|
-
const result = spawnSync("ps", ["-eo", "pid=,ppid=,args="], {
|
|
402
|
+
const result = spawnSync("ps", ["-eo", "pid=,ppid=,args="], {
|
|
403
|
+
encoding: "utf-8",
|
|
404
|
+
});
|
|
382
405
|
|
|
383
406
|
if (result.status !== 0 || !result.stdout.trim()) {
|
|
384
407
|
return [];
|
|
@@ -432,6 +455,17 @@ async function readStdin() {
|
|
|
432
455
|
});
|
|
433
456
|
}
|
|
434
457
|
|
|
458
|
+
/**
|
|
459
|
+
* @param {string | null | undefined} value
|
|
460
|
+
* @returns {Promise<string | undefined>}
|
|
461
|
+
*/
|
|
462
|
+
async function readStdinIfNeeded(value) {
|
|
463
|
+
if (value && value !== "-") return value;
|
|
464
|
+
if (!hasStdinData()) return undefined;
|
|
465
|
+
const stdinText = await readStdin();
|
|
466
|
+
return stdinText || undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
435
469
|
// =============================================================================
|
|
436
470
|
// =============================================================================
|
|
437
471
|
// Helpers - CLI argument parsing
|
|
@@ -460,6 +494,7 @@ async function readStdin() {
|
|
|
460
494
|
* @property {number} [tail]
|
|
461
495
|
* @property {number} [limit]
|
|
462
496
|
* @property {string} [branch]
|
|
497
|
+
* @property {string} [archangels]
|
|
463
498
|
*/
|
|
464
499
|
function parseCliArgs(args) {
|
|
465
500
|
const { values, positionals } = parseArgs({
|
|
@@ -484,6 +519,7 @@ function parseCliArgs(args) {
|
|
|
484
519
|
tail: { type: "string" },
|
|
485
520
|
limit: { type: "string" },
|
|
486
521
|
branch: { type: "string" },
|
|
522
|
+
archangels: { type: "string" },
|
|
487
523
|
},
|
|
488
524
|
allowPositionals: true,
|
|
489
525
|
strict: false, // Don't error on unknown flags
|
|
@@ -508,6 +544,7 @@ function parseCliArgs(args) {
|
|
|
508
544
|
tail: values.tail !== undefined ? Number(values.tail) : undefined,
|
|
509
545
|
limit: values.limit !== undefined ? Number(values.limit) : undefined,
|
|
510
546
|
branch: /** @type {string | undefined} */ (values.branch),
|
|
547
|
+
archangels: /** @type {string | undefined} */ (values.archangels),
|
|
511
548
|
},
|
|
512
549
|
positionals,
|
|
513
550
|
};
|
|
@@ -1150,6 +1187,54 @@ function getArchangelSessionPattern(config) {
|
|
|
1150
1187
|
return `${config.tool}-archangel-${config.name}`;
|
|
1151
1188
|
}
|
|
1152
1189
|
|
|
1190
|
+
/**
|
|
1191
|
+
* @param {string} rfpId
|
|
1192
|
+
* @param {string} prompt
|
|
1193
|
+
*/
|
|
1194
|
+
function writeRfpRecord(rfpId, prompt) {
|
|
1195
|
+
ensureRfpDir();
|
|
1196
|
+
const p = path.join(RFP_DIR, `${rfpId}.md`);
|
|
1197
|
+
const block = [`### ${rfpId}`, "", prompt.trim(), ""].join("\n");
|
|
1198
|
+
writeFileSync(p, block, "utf-8");
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* @param {string} input
|
|
1203
|
+
* @returns {string}
|
|
1204
|
+
*/
|
|
1205
|
+
function resolveRfpId(input) {
|
|
1206
|
+
ensureRfpDir();
|
|
1207
|
+
if (!existsSync(RFP_DIR)) return input;
|
|
1208
|
+
const files = readdirSync(RFP_DIR).filter((f) => f.endsWith(".md"));
|
|
1209
|
+
const ids = files.map((f) => f.replace(/\.md$/, ""));
|
|
1210
|
+
const matches = ids.filter((id) => id.startsWith(input));
|
|
1211
|
+
if (matches.length === 1) return matches[0];
|
|
1212
|
+
if (matches.length > 1) {
|
|
1213
|
+
console.log("ERROR: ambiguous rfp id. Matches:");
|
|
1214
|
+
for (const m of matches) console.log(` ${m}`);
|
|
1215
|
+
process.exit(1);
|
|
1216
|
+
}
|
|
1217
|
+
return input;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* @param {ParentSession | null} parent
|
|
1222
|
+
* @returns {string}
|
|
1223
|
+
*/
|
|
1224
|
+
function generateRfpId(parent) {
|
|
1225
|
+
const now = new Date();
|
|
1226
|
+
const y = now.getFullYear();
|
|
1227
|
+
const mo = String(now.getMonth() + 1).padStart(2, "0");
|
|
1228
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
1229
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
1230
|
+
const mi = String(now.getMinutes()).padStart(2, "0");
|
|
1231
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
1232
|
+
const ts = `${y}-${mo}-${d}-${h}-${mi}-${s}`;
|
|
1233
|
+
const base = parent?.uuid ? parent.uuid.split("-")[0] : randomUUID().split("-")[0];
|
|
1234
|
+
const suffix = randomUUID().split("-")[0].slice(0, 4);
|
|
1235
|
+
return `rfp-${base}-${ts}-${suffix}`.toLowerCase();
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1153
1238
|
// =============================================================================
|
|
1154
1239
|
// Helpers - mailbox
|
|
1155
1240
|
// =============================================================================
|
|
@@ -1166,15 +1251,25 @@ function ensureMailboxDir() {
|
|
|
1166
1251
|
}
|
|
1167
1252
|
}
|
|
1168
1253
|
|
|
1254
|
+
/**
|
|
1255
|
+
* @returns {void}
|
|
1256
|
+
*/
|
|
1257
|
+
function ensureRfpDir() {
|
|
1258
|
+
if (!existsSync(RFP_DIR)) {
|
|
1259
|
+
mkdirSync(RFP_DIR, { recursive: true });
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1169
1263
|
/**
|
|
1170
1264
|
* @param {MailboxPayload} payload
|
|
1265
|
+
* @param {string} [type]
|
|
1171
1266
|
* @returns {void}
|
|
1172
1267
|
*/
|
|
1173
|
-
function writeToMailbox(payload) {
|
|
1268
|
+
function writeToMailbox(payload, type = "observation") {
|
|
1174
1269
|
ensureMailboxDir();
|
|
1175
1270
|
const entry = {
|
|
1176
1271
|
timestamp: new Date().toISOString(),
|
|
1177
|
-
type
|
|
1272
|
+
type,
|
|
1178
1273
|
payload,
|
|
1179
1274
|
};
|
|
1180
1275
|
appendFileSync(MAILBOX_PATH, JSON.stringify(entry) + "\n");
|
|
@@ -2703,11 +2798,29 @@ function startArchangel(config, parentSession = null) {
|
|
|
2703
2798
|
env,
|
|
2704
2799
|
});
|
|
2705
2800
|
child.unref();
|
|
2801
|
+
const watchingLabel = parentSession
|
|
2802
|
+
? parentSession.session || parentSession.uuid?.slice(0, 8)
|
|
2803
|
+
: null;
|
|
2706
2804
|
console.log(
|
|
2707
|
-
`Summoning: ${config.name} (pid ${child.pid})${
|
|
2805
|
+
`Summoning: ${config.name} (pid ${child.pid})${watchingLabel ? ` [watching: ${watchingLabel}]` : ""}`,
|
|
2708
2806
|
);
|
|
2709
2807
|
}
|
|
2710
2808
|
|
|
2809
|
+
/**
|
|
2810
|
+
* @param {string} pattern
|
|
2811
|
+
* @param {number} [timeoutMs]
|
|
2812
|
+
* @returns {Promise<string | undefined>}
|
|
2813
|
+
*/
|
|
2814
|
+
async function waitForArchangelSession(pattern, timeoutMs = ARCHANGEL_STARTUP_TIMEOUT_MS) {
|
|
2815
|
+
const start = Date.now();
|
|
2816
|
+
while (Date.now() - start < timeoutMs) {
|
|
2817
|
+
const session = findArchangelSession(pattern);
|
|
2818
|
+
if (session) return session;
|
|
2819
|
+
await sleep(200);
|
|
2820
|
+
}
|
|
2821
|
+
return undefined;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2711
2824
|
// =============================================================================
|
|
2712
2825
|
// Command: archangel (runs as the archangel process itself)
|
|
2713
2826
|
// =============================================================================
|
|
@@ -3710,7 +3823,13 @@ function cmdMailbox({ limit = 20, branch = null, all = false } = {}) {
|
|
|
3710
3823
|
console.log(`**Branch**: ${p.branch || "?"} @ ${p.commit || "?"}\n`);
|
|
3711
3824
|
}
|
|
3712
3825
|
|
|
3713
|
-
if (p.
|
|
3826
|
+
if (p.rfpId) {
|
|
3827
|
+
console.log(`**RFP**: ${p.rfpId}\n`);
|
|
3828
|
+
}
|
|
3829
|
+
|
|
3830
|
+
if (entry.type === "proposal") {
|
|
3831
|
+
console.log(`**Proposal**: ${p.message || ""}\n`);
|
|
3832
|
+
} else if (p.message) {
|
|
3714
3833
|
console.log(`**Assistant**: ${p.message}\n`);
|
|
3715
3834
|
}
|
|
3716
3835
|
|
|
@@ -3724,13 +3843,229 @@ function cmdMailbox({ limit = 20, branch = null, all = false } = {}) {
|
|
|
3724
3843
|
}
|
|
3725
3844
|
}
|
|
3726
3845
|
|
|
3846
|
+
/**
|
|
3847
|
+
* @param {string} rfpId
|
|
3848
|
+
* @param {string} archangel
|
|
3849
|
+
* @returns {string | null}
|
|
3850
|
+
*/
|
|
3851
|
+
function getProposalFromMailbox(rfpId, archangel) {
|
|
3852
|
+
if (!existsSync(MAILBOX_PATH)) return null;
|
|
3853
|
+
let result = null;
|
|
3854
|
+
try {
|
|
3855
|
+
const lines = readFileSync(MAILBOX_PATH, "utf-8").trim().split("\n").filter(Boolean);
|
|
3856
|
+
for (const line of lines) {
|
|
3857
|
+
try {
|
|
3858
|
+
const entry = JSON.parse(line);
|
|
3859
|
+
if (entry?.type !== "proposal") continue;
|
|
3860
|
+
const p = entry.payload || {};
|
|
3861
|
+
if (p.rfpId === rfpId && p.archangel === archangel) {
|
|
3862
|
+
result = p.message || "";
|
|
3863
|
+
}
|
|
3864
|
+
} catch {
|
|
3865
|
+
// Skip malformed lines
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
} catch (err) {
|
|
3869
|
+
debugError("getProposalFromMailbox", err);
|
|
3870
|
+
}
|
|
3871
|
+
return result;
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
/**
|
|
3875
|
+
* @param {string} prompt
|
|
3876
|
+
* @param {{archangels?: string, fresh?: boolean, noWait?: boolean}} [options]
|
|
3877
|
+
*/
|
|
3878
|
+
async function cmdRfp(prompt, { archangels, fresh = false, noWait = false } = {}) {
|
|
3879
|
+
const configs = loadAgentConfigs();
|
|
3880
|
+
if (configs.length === 0) {
|
|
3881
|
+
console.log(`No archangels found in ${AGENTS_DIR}/`);
|
|
3882
|
+
process.exit(1);
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
const requested = archangels
|
|
3886
|
+
? archangels
|
|
3887
|
+
.split(",")
|
|
3888
|
+
.map((s) => s.trim())
|
|
3889
|
+
.filter(Boolean)
|
|
3890
|
+
: configs.map((c) => c.name);
|
|
3891
|
+
|
|
3892
|
+
if (requested.length === 0) {
|
|
3893
|
+
console.log("ERROR: no archangels specified");
|
|
3894
|
+
process.exit(1);
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
const missing = requested.filter((name) => !configs.some((c) => c.name === name));
|
|
3898
|
+
if (missing.length > 0) {
|
|
3899
|
+
console.log(`ERROR: unknown archangel(s): ${missing.join(", ")}`);
|
|
3900
|
+
process.exit(1);
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
const parent = findParentSession();
|
|
3904
|
+
const rfpId = generateRfpId(parent);
|
|
3905
|
+
|
|
3906
|
+
for (const name of requested) {
|
|
3907
|
+
const config = configs.find((c) => c.name === name);
|
|
3908
|
+
if (!config) continue;
|
|
3909
|
+
|
|
3910
|
+
const pattern = getArchangelSessionPattern(config);
|
|
3911
|
+
let session = findArchangelSession(pattern);
|
|
3912
|
+
if (!session) {
|
|
3913
|
+
startArchangel(config, parent);
|
|
3914
|
+
session = await waitForArchangelSession(pattern);
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
if (!session) {
|
|
3918
|
+
console.log(`ERROR: failed to start archangel '${name}'`);
|
|
3919
|
+
continue;
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
const { agent } = resolveAgent({ sessionName: session });
|
|
3923
|
+
|
|
3924
|
+
if (fresh) {
|
|
3925
|
+
tmuxSendLiteral(session, "/new");
|
|
3926
|
+
await sleep(50);
|
|
3927
|
+
tmuxSend(session, "Enter");
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
const ready = await waitUntilReady(agent, session, ARCHANGEL_STARTUP_TIMEOUT_MS);
|
|
3931
|
+
if (ready.state !== State.READY) {
|
|
3932
|
+
console.log(`[rfp] ${name} not ready (${ready.state}), skipping`);
|
|
3933
|
+
continue;
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
const rfpPrompt = `## RFP ${rfpId}\n\n${RFP_PREAMBLE}\n\n${prompt}\n\nReturn exactly one proposal.`;
|
|
3937
|
+
tmuxSendLiteral(session, rfpPrompt);
|
|
3938
|
+
await sleep(200);
|
|
3939
|
+
tmuxSend(session, "Enter");
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
writeRfpRecord(rfpId, prompt);
|
|
3943
|
+
const archangelList = requested.join(",");
|
|
3944
|
+
console.log(`rfp: ${rfpId} (${archangelList})`);
|
|
3945
|
+
if (noWait) {
|
|
3946
|
+
const cli = path.basename(process.argv[1], ".js");
|
|
3947
|
+
const base = rfpId.split("-")[1];
|
|
3948
|
+
const shortId = `rfp-${base}`;
|
|
3949
|
+
console.log(`e.g.\n ${cli} rfp wait ${shortId} --archangels=${archangelList}`);
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
/**
|
|
3954
|
+
* @param {string} rfpId
|
|
3955
|
+
* @param {{archangels?: string, timeoutMs?: number}} [options]
|
|
3956
|
+
*/
|
|
3957
|
+
async function cmdRfpWait(rfpId, { archangels, timeoutMs = ARCHANGEL_RESPONSE_TIMEOUT_MS } = {}) {
|
|
3958
|
+
const resolvedRfpId = resolveRfpId(rfpId);
|
|
3959
|
+
const configs = loadAgentConfigs();
|
|
3960
|
+
if (configs.length === 0) {
|
|
3961
|
+
console.log(`No archangels found in ${AGENTS_DIR}/`);
|
|
3962
|
+
process.exit(1);
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
const requested = archangels
|
|
3966
|
+
? archangels
|
|
3967
|
+
.split(",")
|
|
3968
|
+
.map((s) => s.trim())
|
|
3969
|
+
.filter(Boolean)
|
|
3970
|
+
: configs.map((c) => c.name);
|
|
3971
|
+
|
|
3972
|
+
if (requested.length === 0) {
|
|
3973
|
+
console.log("ERROR: no archangels specified");
|
|
3974
|
+
process.exit(1);
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
const missing = requested.filter((name) => !configs.some((c) => c.name === name));
|
|
3978
|
+
if (missing.length > 0) {
|
|
3979
|
+
console.log(`ERROR: unknown archangel(s): ${missing.join(", ")}`);
|
|
3980
|
+
process.exit(1);
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
let wroteAny = false;
|
|
3984
|
+
let printedAny = false;
|
|
3985
|
+
|
|
3986
|
+
for (const name of requested) {
|
|
3987
|
+
const config = configs.find((c) => c.name === name);
|
|
3988
|
+
if (!config) continue;
|
|
3989
|
+
|
|
3990
|
+
const pattern = getArchangelSessionPattern(config);
|
|
3991
|
+
const session = findArchangelSession(pattern);
|
|
3992
|
+
if (!session) {
|
|
3993
|
+
console.log(`[rfp] ${name} session not found, skipping`);
|
|
3994
|
+
continue;
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3997
|
+
const existing = getProposalFromMailbox(resolvedRfpId, name);
|
|
3998
|
+
if (existing !== null) {
|
|
3999
|
+
if (printedAny) console.log("");
|
|
4000
|
+
console.log(`[${name}]`);
|
|
4001
|
+
console.log(existing);
|
|
4002
|
+
wroteAny = true;
|
|
4003
|
+
printedAny = true;
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
const { agent } = resolveAgent({ sessionName: session });
|
|
4008
|
+
let result;
|
|
4009
|
+
try {
|
|
4010
|
+
result = await waitUntilReady(agent, session, timeoutMs);
|
|
4011
|
+
} catch (err) {
|
|
4012
|
+
if (err instanceof TimeoutError) {
|
|
4013
|
+
console.log(`[rfp] ${name} timed out`);
|
|
4014
|
+
} else {
|
|
4015
|
+
console.log(`[rfp] ${name} error: ${err instanceof Error ? err.message : err}`);
|
|
4016
|
+
}
|
|
4017
|
+
continue;
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
if (result.state === State.RATE_LIMITED) {
|
|
4021
|
+
console.log(`[rfp] ${name} rate limited`);
|
|
4022
|
+
continue;
|
|
4023
|
+
}
|
|
4024
|
+
if (result.state === State.CONFIRMING) {
|
|
4025
|
+
console.log(`[rfp] ${name} awaiting confirmation`);
|
|
4026
|
+
continue;
|
|
4027
|
+
}
|
|
4028
|
+
|
|
4029
|
+
const response = agent.getResponse(session, result.screen) || "";
|
|
4030
|
+
if (!response || response.trim() === "EMPTY_RESPONSE") {
|
|
4031
|
+
continue;
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
writeToMailbox(
|
|
4035
|
+
{
|
|
4036
|
+
agent: name,
|
|
4037
|
+
session,
|
|
4038
|
+
branch: getCurrentBranch(),
|
|
4039
|
+
commit: getCurrentCommit(),
|
|
4040
|
+
files: [],
|
|
4041
|
+
message: response,
|
|
4042
|
+
rfpId: resolvedRfpId,
|
|
4043
|
+
archangel: name,
|
|
4044
|
+
},
|
|
4045
|
+
"proposal",
|
|
4046
|
+
);
|
|
4047
|
+
if (printedAny) console.log("");
|
|
4048
|
+
console.log(`[${name}]`);
|
|
4049
|
+
console.log(response);
|
|
4050
|
+
wroteAny = true;
|
|
4051
|
+
printedAny = true;
|
|
4052
|
+
}
|
|
4053
|
+
|
|
4054
|
+
if (!wroteAny) process.exit(1);
|
|
4055
|
+
}
|
|
4056
|
+
|
|
3727
4057
|
/**
|
|
3728
4058
|
* @param {Agent} agent
|
|
3729
4059
|
* @param {string | null | undefined} session
|
|
3730
4060
|
* @param {string} message
|
|
3731
4061
|
* @param {{noWait?: boolean, yolo?: boolean, timeoutMs?: number}} [options]
|
|
3732
4062
|
*/
|
|
3733
|
-
async function cmdAsk(
|
|
4063
|
+
async function cmdAsk(
|
|
4064
|
+
agent,
|
|
4065
|
+
session,
|
|
4066
|
+
message,
|
|
4067
|
+
{ noWait = false, yolo = false, timeoutMs = DEFAULT_TIMEOUT_MS } = {},
|
|
4068
|
+
) {
|
|
3734
4069
|
const sessionExists = session != null && tmuxHasSession(session);
|
|
3735
4070
|
const nativeYolo = sessionExists && isYoloSession(/** @type {string} */ (session));
|
|
3736
4071
|
|
|
@@ -4194,72 +4529,62 @@ function resolveAgent({ toolFlag, sessionName } = {}) {
|
|
|
4194
4529
|
function printHelp(agent, cliName) {
|
|
4195
4530
|
const name = cliName;
|
|
4196
4531
|
const backendName = agent.displayName;
|
|
4197
|
-
const hasReview = !!agent.reviewOptions;
|
|
4198
4532
|
|
|
4199
4533
|
console.log(`${name} v${VERSION} - agentic assistant CLI (${backendName})
|
|
4200
4534
|
|
|
4201
4535
|
Usage: ${name} [OPTIONS] <command|message> [ARGS...]
|
|
4202
4536
|
|
|
4203
|
-
|
|
4204
|
-
|
|
4537
|
+
Messaging/State:
|
|
4538
|
+
<message> Send message to ${name}
|
|
4539
|
+
review [TYPE] Review code: pr, uncommitted, commit, custom
|
|
4540
|
+
status Exit code: ready=0 rate_limit=2 confirm=3 thinking=4
|
|
4541
|
+
output [-N] Show response (0=last, -1=prev, -2=older)
|
|
4542
|
+
compact Summarise session to shrink context size
|
|
4543
|
+
reset Start fresh conversation
|
|
4544
|
+
|
|
4545
|
+
Sessions:
|
|
4546
|
+
agents List all running agents
|
|
4205
4547
|
target Show default target session for current tool
|
|
4206
4548
|
attach [SESSION] Attach to agent session interactively
|
|
4207
|
-
|
|
4208
|
-
|
|
4549
|
+
kill Kill sessions (--all, --session=NAME, --orphans [--force])
|
|
4550
|
+
|
|
4551
|
+
Archangels:
|
|
4209
4552
|
summon [name] Summon archangels (all, or by name)
|
|
4210
4553
|
recall [name] Recall archangels (all, or by name)
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
review [TYPE] Review code: pr, uncommitted, commit, custom`
|
|
4218
|
-
: ""
|
|
4219
|
-
}
|
|
4220
|
-
select N Select menu option N
|
|
4554
|
+
mailbox Archangel notes (filters: --branch=git, --all)
|
|
4555
|
+
rfp <prompt> Request proposals (--archangels=a,b)
|
|
4556
|
+
rfp wait <id> Wait for proposals (--archangels=a,b)
|
|
4557
|
+
|
|
4558
|
+
Recovery:
|
|
4559
|
+
debug Show raw screen output and detected state
|
|
4221
4560
|
approve Approve pending action (send 'y')
|
|
4222
4561
|
reject Reject pending action (send 'n')
|
|
4562
|
+
select N Select menu option N
|
|
4223
4563
|
send KEYS Send key sequence (e.g. "1[Enter]", "[Escape]")
|
|
4224
|
-
|
|
4225
|
-
reset Start fresh conversation
|
|
4226
|
-
<message> Send message to ${name}
|
|
4564
|
+
log SESSION View conversation log (--tail=N, --follow, --reasoning)
|
|
4227
4565
|
|
|
4228
4566
|
Flags:
|
|
4229
4567
|
--tool=NAME Use specific agent (codex, claude)
|
|
4230
|
-
--session=
|
|
4568
|
+
--session=ID name | archangel | uuid-prefix | self
|
|
4569
|
+
--fresh Reset conversation before review
|
|
4570
|
+
--yolo Skip all confirmations (dangerous)
|
|
4231
4571
|
--wait Wait for response (default for messages; required for approve/reject)
|
|
4232
4572
|
--no-wait Fire-and-forget: send message, print session ID, exit immediately
|
|
4233
4573
|
--timeout=N Set timeout in seconds (default: ${DEFAULT_TIMEOUT_MS / 1000}, reviews: ${REVIEW_TIMEOUT_MS / 1000})
|
|
4234
|
-
--yolo Skip all confirmations (dangerous)
|
|
4235
|
-
--fresh Reset conversation before review
|
|
4236
|
-
--orphans Kill orphaned claude/codex processes (PPID=1)
|
|
4237
|
-
--force Use SIGKILL instead of SIGTERM (with --orphans)
|
|
4238
|
-
|
|
4239
|
-
Environment:
|
|
4240
|
-
AX_DEFAULT_TOOL Default agent when using 'ax' (claude or codex, default: codex)
|
|
4241
|
-
${agent.envVar} Override default session name
|
|
4242
|
-
AX_CLAUDE_CONFIG_DIR Override Claude config directory (default: ~/.claude)
|
|
4243
|
-
AX_CODEX_CONFIG_DIR Override Codex config directory (default: ~/.codex)
|
|
4244
|
-
AX_REVIEW_MODE=exec Bypass /review, send instructions directly (codex only)
|
|
4245
|
-
AX_DEBUG=1 Enable debug logging
|
|
4246
4574
|
|
|
4247
4575
|
Examples:
|
|
4248
4576
|
${name} "explain this codebase"
|
|
4249
4577
|
${name} "review the error handling" # Auto custom review (${REVIEW_TIMEOUT_MS / 60000}min timeout)
|
|
4250
4578
|
${name} "FYI: auth was refactored" --no-wait # Send context to a working session (no response needed)
|
|
4251
4579
|
${name} review uncommitted --wait
|
|
4252
|
-
${name}
|
|
4253
|
-
${name} kill
|
|
4254
|
-
${name} kill --
|
|
4255
|
-
${name}
|
|
4256
|
-
${name}
|
|
4257
|
-
${name}
|
|
4258
|
-
${name}
|
|
4259
|
-
${name}
|
|
4260
|
-
${name} recall # Recall all archangels
|
|
4261
|
-
${name} recall reviewer # Recall one by name
|
|
4262
|
-
${name} agents # List all agents (shows TYPE=archangel)
|
|
4580
|
+
${name} kill # Kill agents in current project
|
|
4581
|
+
${name} kill --all # Kill all agents across all projects
|
|
4582
|
+
${name} kill --session=NAME # Kill specific session
|
|
4583
|
+
${name} summon # Summon all archangels from .ai/agents/*.md
|
|
4584
|
+
${name} summon reviewer # Summon by name (creates config if new)
|
|
4585
|
+
${name} recall # Recall all archangels
|
|
4586
|
+
${name} recall reviewer # Recall one by name
|
|
4587
|
+
${name} agents # List all agents (shows TYPE=archangel)
|
|
4263
4588
|
|
|
4264
4589
|
Note: Reviews and complex tasks may take several minutes.
|
|
4265
4590
|
Use Bash run_in_background for long operations (not --no-wait).`);
|
|
@@ -4305,7 +4630,10 @@ async function main() {
|
|
|
4305
4630
|
}
|
|
4306
4631
|
|
|
4307
4632
|
// Agent resolution (considers --tool flag, session name, invocation, and env vars)
|
|
4308
|
-
const { agent, error: agentError } = resolveAgent({
|
|
4633
|
+
const { agent, error: agentError } = resolveAgent({
|
|
4634
|
+
toolFlag: flags.tool,
|
|
4635
|
+
sessionName: session,
|
|
4636
|
+
});
|
|
4309
4637
|
if (agentError) {
|
|
4310
4638
|
console.log(`ERROR: ${agentError}`);
|
|
4311
4639
|
process.exit(1);
|
|
@@ -4357,14 +4685,33 @@ async function main() {
|
|
|
4357
4685
|
if (cmd === "attach") return cmdAttach(positionals[1] || session);
|
|
4358
4686
|
if (cmd === "log") return cmdLog(positionals[1] || session, { tail, reasoning, follow });
|
|
4359
4687
|
if (cmd === "mailbox") return cmdMailbox({ limit, branch, all });
|
|
4688
|
+
if (cmd === "rfp") {
|
|
4689
|
+
if (positionals[1] === "wait") {
|
|
4690
|
+
const rfpId = positionals[2];
|
|
4691
|
+
if (!rfpId) {
|
|
4692
|
+
console.log("ERROR: missing rfp id");
|
|
4693
|
+
process.exit(1);
|
|
4694
|
+
}
|
|
4695
|
+
return cmdRfpWait(rfpId, { archangels: flags.archangels, timeoutMs });
|
|
4696
|
+
}
|
|
4697
|
+
const rawPrompt = positionals.slice(1).join(" ");
|
|
4698
|
+
const prompt = await readStdinIfNeeded(rawPrompt);
|
|
4699
|
+
if (!prompt) {
|
|
4700
|
+
console.log("ERROR: missing prompt for rfp");
|
|
4701
|
+
process.exit(1);
|
|
4702
|
+
}
|
|
4703
|
+
return cmdRfp(prompt, { archangels: flags.archangels, fresh, noWait });
|
|
4704
|
+
}
|
|
4360
4705
|
if (cmd === "approve") return cmdApprove(agent, session, { wait, timeoutMs });
|
|
4361
4706
|
if (cmd === "reject") return cmdReject(agent, session, { wait, timeoutMs });
|
|
4362
|
-
if (cmd === "review")
|
|
4363
|
-
|
|
4707
|
+
if (cmd === "review") {
|
|
4708
|
+
const customInstructions = await readStdinIfNeeded(positionals[2]);
|
|
4709
|
+
return cmdReview(agent, session, positionals[1], customInstructions ?? undefined, {
|
|
4364
4710
|
wait,
|
|
4365
4711
|
fresh,
|
|
4366
4712
|
timeoutMs,
|
|
4367
4713
|
});
|
|
4714
|
+
}
|
|
4368
4715
|
if (cmd === "status") return cmdStatus(agent, session);
|
|
4369
4716
|
if (cmd === "debug") return cmdDebug(agent, session);
|
|
4370
4717
|
if (cmd === "output") {
|
|
@@ -4380,18 +4727,17 @@ async function main() {
|
|
|
4380
4727
|
return cmdSelect(agent, session, positionals[1], { wait, timeoutMs });
|
|
4381
4728
|
|
|
4382
4729
|
// Default: send message
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
message = await readStdin();
|
|
4386
|
-
}
|
|
4730
|
+
const rawMessage = positionals.join(" ");
|
|
4731
|
+
let message = await readStdinIfNeeded(rawMessage);
|
|
4387
4732
|
|
|
4388
4733
|
if (!message || flags.help) {
|
|
4389
4734
|
printHelp(agent, cliName);
|
|
4390
4735
|
process.exit(0);
|
|
4391
4736
|
}
|
|
4737
|
+
const messageText = message;
|
|
4392
4738
|
|
|
4393
4739
|
// Detect "review ..." or "please review ..." and route to custom review mode
|
|
4394
|
-
const reviewMatch =
|
|
4740
|
+
const reviewMatch = messageText.match(/^(?:please )?review\s*(.*)/i);
|
|
4395
4741
|
if (reviewMatch && agent.reviewOptions) {
|
|
4396
4742
|
const customInstructions = reviewMatch[1].trim() || null;
|
|
4397
4743
|
return cmdReview(agent, session, "custom", customInstructions, {
|
|
@@ -4401,7 +4747,7 @@ async function main() {
|
|
|
4401
4747
|
});
|
|
4402
4748
|
}
|
|
4403
4749
|
|
|
4404
|
-
return cmdAsk(agent, session,
|
|
4750
|
+
return cmdAsk(agent, session, messageText, { noWait, yolo, timeoutMs });
|
|
4405
4751
|
}
|
|
4406
4752
|
|
|
4407
4753
|
// Run main() only when executed directly (not when imported for testing)
|