@wrenautomation/aerie 0.6.0 → 0.7.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/bin.js +511 -125
- package/dist/bin.js.map +4 -4
- package/dist/cli-intake.js +6 -3
- package/dist/cli-intake.js.map +1 -1
- package/dist/cli-run.d.ts +1 -1
- package/dist/cli-run.js +102 -3
- package/dist/cli-run.js.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +12 -4
- package/dist/cli.js.map +1 -1
- package/dist/coord/port.d.ts +2 -0
- package/dist/coord/postgres.js +23 -0
- package/dist/coord/postgres.js.map +1 -1
- package/dist/intake/index.d.ts +4 -2
- package/dist/intake/index.js +21 -4
- package/dist/intake/index.js.map +1 -1
- package/dist/peers-coord.js +30 -0
- package/dist/peers-coord.js.map +2 -2
- package/dist/pool.d.ts +71 -0
- package/dist/pool.js +205 -0
- package/dist/pool.js.map +1 -0
- package/dist/run/adapters.js +26 -2
- package/dist/run/adapters.js.map +1 -1
- package/dist/run/port.d.ts +5 -1
- package/dist/run/route.d.ts +4 -2
- package/dist/run/route.js +15 -8
- package/dist/run/route.js.map +1 -1
- package/dist/run/spawn.d.ts +4 -0
- package/dist/run/spawn.js +10 -1
- package/dist/run/spawn.js.map +1 -1
- package/dist/run/worker.d.ts +13 -0
- package/dist/run/worker.js +45 -6
- package/dist/run/worker.js.map +1 -1
- package/package.json +1 -1
- package/skills/aerie-orchestrate/SKILL.md +8 -6
- package/skills/aerie-sidecar/SKILL.md +2 -2
package/dist/bin.js
CHANGED
|
@@ -1233,7 +1233,7 @@ var init_sql = __esm({
|
|
|
1233
1233
|
return new SQL([new StringChunk(str2)]);
|
|
1234
1234
|
}
|
|
1235
1235
|
sql2.raw = raw;
|
|
1236
|
-
function
|
|
1236
|
+
function join8(chunks, separator) {
|
|
1237
1237
|
const result = [];
|
|
1238
1238
|
for (const [i, chunk] of chunks.entries()) {
|
|
1239
1239
|
if (i > 0 && separator !== void 0) {
|
|
@@ -1243,7 +1243,7 @@ var init_sql = __esm({
|
|
|
1243
1243
|
}
|
|
1244
1244
|
return new SQL(result);
|
|
1245
1245
|
}
|
|
1246
|
-
sql2.join =
|
|
1246
|
+
sql2.join = join8;
|
|
1247
1247
|
function identifier(value) {
|
|
1248
1248
|
return new Name(value);
|
|
1249
1249
|
}
|
|
@@ -5521,7 +5521,7 @@ var init_select2 = __esm({
|
|
|
5521
5521
|
const baseTableName = this.tableName;
|
|
5522
5522
|
const tableName = getTableLikeName(table);
|
|
5523
5523
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
5524
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
5524
|
+
if (typeof tableName === "string" && this.config.joins?.some((join8) => join8.alias === tableName)) {
|
|
5525
5525
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
5526
5526
|
}
|
|
5527
5527
|
if (!this.isPartialSelect) {
|
|
@@ -7048,7 +7048,7 @@ var init_update = __esm({
|
|
|
7048
7048
|
createJoin(joinType) {
|
|
7049
7049
|
return (table, on) => {
|
|
7050
7050
|
const tableName = getTableLikeName(table);
|
|
7051
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
7051
|
+
if (typeof tableName === "string" && this.config.joins.some((join8) => join8.alias === tableName)) {
|
|
7052
7052
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
7053
7053
|
}
|
|
7054
7054
|
if (typeof on === "function") {
|
|
@@ -7144,10 +7144,10 @@ var init_update = __esm({
|
|
|
7144
7144
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
7145
7145
|
fields[tableName] = fromFields;
|
|
7146
7146
|
}
|
|
7147
|
-
for (const
|
|
7148
|
-
const tableName2 = getTableLikeName(
|
|
7149
|
-
if (typeof tableName2 === "string" && !is(
|
|
7150
|
-
const fromFields = this.getTableLikeFields(
|
|
7147
|
+
for (const join8 of this.config.joins) {
|
|
7148
|
+
const tableName2 = getTableLikeName(join8.table);
|
|
7149
|
+
if (typeof tableName2 === "string" && !is(join8.table, SQL)) {
|
|
7150
|
+
const fromFields = this.getTableLikeFields(join8.table);
|
|
7151
7151
|
fields[tableName2] = fromFields;
|
|
7152
7152
|
}
|
|
7153
7153
|
}
|
|
@@ -8951,7 +8951,7 @@ function aerieStore(o) {
|
|
|
8951
8951
|
return (r[0]?.v ?? o.prefix ?? "T").toUpperCase();
|
|
8952
8952
|
}
|
|
8953
8953
|
const rules = o.rules ?? WREN_RULES;
|
|
8954
|
-
async function
|
|
8954
|
+
async function write2(by, fn) {
|
|
8955
8955
|
const out = await atomic(db, async (tx) => {
|
|
8956
8956
|
await o.hooks?.onWrite?.(tx, by);
|
|
8957
8957
|
return fn(tx);
|
|
@@ -9111,7 +9111,7 @@ function aerieStore(o) {
|
|
|
9111
9111
|
return rows3.map(viewOf);
|
|
9112
9112
|
}
|
|
9113
9113
|
async function saveView(owner, v) {
|
|
9114
|
-
const id = await
|
|
9114
|
+
const id = await write2(owner, async (tx) => {
|
|
9115
9115
|
await ensureActors(tx, [owner]);
|
|
9116
9116
|
const set3 = {
|
|
9117
9117
|
filter: v.filter,
|
|
@@ -9126,7 +9126,7 @@ function aerieStore(o) {
|
|
|
9126
9126
|
return { id, ...v };
|
|
9127
9127
|
}
|
|
9128
9128
|
async function deleteView(owner, name) {
|
|
9129
|
-
const r = await
|
|
9129
|
+
const r = await write2(
|
|
9130
9130
|
owner,
|
|
9131
9131
|
(tx) => tx.delete(view).where(and(eq(view.owner, owner), eq(view.name, name))).returning({ id: view.id })
|
|
9132
9132
|
);
|
|
@@ -9164,7 +9164,7 @@ function aerieStore(o) {
|
|
|
9164
9164
|
throw new AerieError(`recommendation ${d.recommendation} is not an option`);
|
|
9165
9165
|
const blocks = [];
|
|
9166
9166
|
for (const b of d.blocks ?? []) blocks.push((await must(b, db)).id);
|
|
9167
|
-
const id = await
|
|
9167
|
+
const id = await write2(by, async (tx) => {
|
|
9168
9168
|
const [r] = await tx.insert(decision).values({
|
|
9169
9169
|
question: d.question,
|
|
9170
9170
|
options: d.options,
|
|
@@ -9187,7 +9187,7 @@ function aerieStore(o) {
|
|
|
9187
9187
|
const [d] = await decisions({ ids: [id] });
|
|
9188
9188
|
if (!d) throw new AerieError(`no decision ${id}`, 404);
|
|
9189
9189
|
if (d.answeredAt) throw new AerieError(`decision ${id} is answered (${d.answer})`, 409);
|
|
9190
|
-
await
|
|
9190
|
+
await write2(by, async (tx) => {
|
|
9191
9191
|
await tx.update(decision).set({ answer, answeredBy: by, answeredAt: now(), why: why ?? null }).where(and(eq(decision.id, id), isNull2(decision.answeredAt)));
|
|
9192
9192
|
});
|
|
9193
9193
|
const opt = d.options.find((x) => x.key === answer);
|
|
@@ -9217,7 +9217,7 @@ function aerieStore(o) {
|
|
|
9217
9217
|
recommendation: d.recommendation,
|
|
9218
9218
|
why: p.why
|
|
9219
9219
|
};
|
|
9220
|
-
await
|
|
9220
|
+
await write2(by, async (tx) => {
|
|
9221
9221
|
await tx.update(decision).set({
|
|
9222
9222
|
...p.brief !== void 0 ? { brief: p.brief } : {},
|
|
9223
9223
|
...p.recommendation !== void 0 ? { recommendation: p.recommendation } : {},
|
|
@@ -9248,7 +9248,7 @@ function aerieStore(o) {
|
|
|
9248
9248
|
...drop.flatMap((id) => all.find((x) => x.id === id)?.blocks ?? [])
|
|
9249
9249
|
])
|
|
9250
9250
|
];
|
|
9251
|
-
await
|
|
9251
|
+
await write2(by, async (tx) => {
|
|
9252
9252
|
await tx.update(decision).set({ blocks }).where(eq(decision.id, keep));
|
|
9253
9253
|
await tx.update(decision).set({
|
|
9254
9254
|
answer: "merged",
|
|
@@ -9386,7 +9386,7 @@ function aerieStore(o) {
|
|
|
9386
9386
|
return `${base}${max + 1}`;
|
|
9387
9387
|
}
|
|
9388
9388
|
async function create(input2, by, opts = {}) {
|
|
9389
|
-
const id = await
|
|
9389
|
+
const id = await write2(by, async (tx) => {
|
|
9390
9390
|
const parent = input2.parentId ? await must(input2.parentId, tx) : null;
|
|
9391
9391
|
if (input2.milestone)
|
|
9392
9392
|
input2 = { ...input2, milestone: await milestoneOf(tx, input2.milestone) };
|
|
@@ -9417,7 +9417,7 @@ function aerieStore(o) {
|
|
|
9417
9417
|
return must(id, db);
|
|
9418
9418
|
}
|
|
9419
9419
|
async function edit(id, e, by, opts = {}) {
|
|
9420
|
-
const done = await
|
|
9420
|
+
const done = await write2(by, async (tx) => {
|
|
9421
9421
|
const row = await lockRow(tx, id);
|
|
9422
9422
|
if (opts.expect !== void 0 && opts.expect !== row.version)
|
|
9423
9423
|
throw new AerieError(`stale: ${row.id} is at version ${row.version}`, 409);
|
|
@@ -9435,7 +9435,7 @@ function aerieStore(o) {
|
|
|
9435
9435
|
}
|
|
9436
9436
|
async function importTasks(tasks, by) {
|
|
9437
9437
|
const out = { added: [], changed: [], same: 0 };
|
|
9438
|
-
await
|
|
9438
|
+
await write2(by, async (tx) => {
|
|
9439
9439
|
const have = new Map((await load2(tx)).map((t) => [t.id, t]));
|
|
9440
9440
|
for (const { task: task2, place, expect } of tasks) {
|
|
9441
9441
|
if (expect !== void 0) {
|
|
@@ -9471,7 +9471,7 @@ function aerieStore(o) {
|
|
|
9471
9471
|
})).sort((a, b) => a.path < b.path ? -1 : 1);
|
|
9472
9472
|
}
|
|
9473
9473
|
async function addArea(path2, by, p = {}) {
|
|
9474
|
-
await
|
|
9474
|
+
await write2(by, async (tx) => {
|
|
9475
9475
|
const id = await ensureArea(tx, path2);
|
|
9476
9476
|
const set3 = {
|
|
9477
9477
|
...p.name ? { name: p.name } : {},
|
|
@@ -9488,13 +9488,13 @@ function aerieStore(o) {
|
|
|
9488
9488
|
return all.filter((a) => roots.some((r) => a.path === r || a.path.startsWith(`${r}/`))).map((a) => a.path);
|
|
9489
9489
|
}
|
|
9490
9490
|
async function grant(area2, actor2, role, by) {
|
|
9491
|
-
await
|
|
9491
|
+
await write2(by, async (tx) => {
|
|
9492
9492
|
await ensureActors(tx, [actor2]);
|
|
9493
9493
|
await tx.insert(areaRole).values({ areaId: await areaIdOf(tx, area2), actorId: actor2, role }).onConflictDoNothing();
|
|
9494
9494
|
});
|
|
9495
9495
|
}
|
|
9496
9496
|
async function revoke(area2, actor2, role, by) {
|
|
9497
|
-
await
|
|
9497
|
+
await write2(by, async (tx) => {
|
|
9498
9498
|
const areaId = await areaIdOf(tx, area2);
|
|
9499
9499
|
await tx.delete(areaRole).where(
|
|
9500
9500
|
and(
|
|
@@ -9528,7 +9528,7 @@ function aerieStore(o) {
|
|
|
9528
9528
|
return r[0]?.id ?? null;
|
|
9529
9529
|
}
|
|
9530
9530
|
async function setActor(id, p, by) {
|
|
9531
|
-
await
|
|
9531
|
+
await write2(by, async (tx) => {
|
|
9532
9532
|
await ensureActors(tx, [id]);
|
|
9533
9533
|
const set3 = {
|
|
9534
9534
|
...p.name ? { name: p.name } : {},
|
|
@@ -9547,7 +9547,7 @@ function aerieStore(o) {
|
|
|
9547
9547
|
return r[0]?.v ?? null;
|
|
9548
9548
|
}
|
|
9549
9549
|
async function setSetting(key, value, by, onlyIfUnset = false) {
|
|
9550
|
-
await
|
|
9550
|
+
await write2(by, async (tx) => {
|
|
9551
9551
|
const q = tx.insert(setting).values({ key, value });
|
|
9552
9552
|
await (onlyIfUnset ? q.onConflictDoNothing() : q.onConflictDoUpdate({ target: setting.key, set: { value } }));
|
|
9553
9553
|
});
|
|
@@ -9584,7 +9584,7 @@ function aerieStore(o) {
|
|
|
9584
9584
|
}
|
|
9585
9585
|
async function putMilestones(ms2, by) {
|
|
9586
9586
|
if (!ms2.length) return;
|
|
9587
|
-
await
|
|
9587
|
+
await write2(by, async (tx) => {
|
|
9588
9588
|
for (const m of ms2)
|
|
9589
9589
|
await tx.insert(milestone).values({ id: m.id, title: m.title, description: m.description ?? null }).onConflictDoUpdate({
|
|
9590
9590
|
target: milestone.id,
|
|
@@ -9610,7 +9610,7 @@ function aerieStore(o) {
|
|
|
9610
9610
|
}
|
|
9611
9611
|
async function addRouteRule(r, by) {
|
|
9612
9612
|
if (r.runner === "session" && !r.lane) throw new AerieError("a session rule needs a lane");
|
|
9613
|
-
return
|
|
9613
|
+
return write2(by, async (tx) => {
|
|
9614
9614
|
await ensureActors(tx, [by]);
|
|
9615
9615
|
const [last] = await tx.select({ rank: routeRule.rank }).from(routeRule).orderBy(sql`${routeRule.rank} desc`).limit(1);
|
|
9616
9616
|
const [row] = await tx.insert(routeRule).values({
|
|
@@ -9627,14 +9627,14 @@ function aerieStore(o) {
|
|
|
9627
9627
|
});
|
|
9628
9628
|
}
|
|
9629
9629
|
async function removeRouteRule(id, by) {
|
|
9630
|
-
return
|
|
9630
|
+
return write2(by, async (tx) => {
|
|
9631
9631
|
const r = await tx.delete(routeRule).where(eq(routeRule.id, id)).returning({ id: routeRule.id });
|
|
9632
9632
|
return r.length > 0;
|
|
9633
9633
|
});
|
|
9634
9634
|
}
|
|
9635
9635
|
async function log(taskId, by, kind, body, why) {
|
|
9636
9636
|
const t = await must(taskId, db);
|
|
9637
|
-
await
|
|
9637
|
+
await write2(by, async (tx) => {
|
|
9638
9638
|
await ensureActors(tx, [by]);
|
|
9639
9639
|
await event(tx, t.id, by, kind, body, why);
|
|
9640
9640
|
});
|
|
@@ -10502,6 +10502,36 @@ function pgCoord(o) {
|
|
|
10502
10502
|
),
|
|
10503
10503
|
queueTake: (x, holder, leaseMs) => atomic(db, async (tx) => {
|
|
10504
10504
|
await lockQueue(tx);
|
|
10505
|
+
if (x.pin?.startsWith("run:")) {
|
|
10506
|
+
const t = clock();
|
|
10507
|
+
const lapsed = await rows2(
|
|
10508
|
+
tx,
|
|
10509
|
+
sql`select task_id as id, holder, queued from ${L}
|
|
10510
|
+
where ns = ${ns} and pin = ${x.pin} and expires < ${t} order by task_id`
|
|
10511
|
+
);
|
|
10512
|
+
if (lapsed.length) {
|
|
10513
|
+
await lockTasks(
|
|
10514
|
+
tx,
|
|
10515
|
+
lapsed.map((l) => l.id)
|
|
10516
|
+
);
|
|
10517
|
+
for (const l of lapsed) {
|
|
10518
|
+
const still = await rows2(
|
|
10519
|
+
tx,
|
|
10520
|
+
sql`select 1 from ${L} where ns = ${ns} and task_id = ${l.id} and holder = ${l.holder}
|
|
10521
|
+
and expires < ${t}`
|
|
10522
|
+
);
|
|
10523
|
+
if (still.length) await drop(tx, l.id, [l.holder], { queue: l.queued, by: l.holder });
|
|
10524
|
+
}
|
|
10525
|
+
}
|
|
10526
|
+
if (x.cap !== void 0) {
|
|
10527
|
+
const [n] = await rows2(
|
|
10528
|
+
tx,
|
|
10529
|
+
sql`select count(*)::int as n from ${L}
|
|
10530
|
+
where ns = ${ns} and pin = ${x.pin} and expires >= ${t}`
|
|
10531
|
+
);
|
|
10532
|
+
if (num(n?.n) >= x.cap) return null;
|
|
10533
|
+
}
|
|
10534
|
+
}
|
|
10505
10535
|
const all = x.lanes === "*";
|
|
10506
10536
|
const lanes = Array.isArray(x.lanes) ? x.lanes : [];
|
|
10507
10537
|
const [item] = await rows2(
|
|
@@ -10558,9 +10588,9 @@ __export(setup_exports, {
|
|
|
10558
10588
|
});
|
|
10559
10589
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
10560
10590
|
import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
10561
|
-
import { chmodSync, existsSync as
|
|
10562
|
-
import { homedir as
|
|
10563
|
-
import { dirname as
|
|
10591
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
10592
|
+
import { homedir as homedir3 } from "node:os";
|
|
10593
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
10564
10594
|
function aerieUrl(adminUrl, role = ROLE) {
|
|
10565
10595
|
const url2 = new URL(adminUrl);
|
|
10566
10596
|
if (!url2.password) throw new Error("admin URL has no password; aerie derives its own from it");
|
|
@@ -10615,10 +10645,10 @@ function adminUrlFromSsm(param, key, region) {
|
|
|
10615
10645
|
throw new Error(`${key} not in ${param} (or its -2, -3 parts)`);
|
|
10616
10646
|
}
|
|
10617
10647
|
function writeSettings(set3, file2 = settingsFile()) {
|
|
10618
|
-
const lines =
|
|
10648
|
+
const lines = existsSync5(file2) ? readFileSync5(file2, "utf8").split("\n").filter(Boolean) : [];
|
|
10619
10649
|
const kept = lines.filter((l) => !Object.keys(set3).some((k) => l.startsWith(`${k}=`)));
|
|
10620
|
-
|
|
10621
|
-
|
|
10650
|
+
mkdirSync3(dirname3(file2), { recursive: true, mode: 448 });
|
|
10651
|
+
writeFileSync3(
|
|
10622
10652
|
file2,
|
|
10623
10653
|
`${[...kept, ...Object.entries(set3).map(([k, v]) => `${k}=${v}`)].join("\n")}
|
|
10624
10654
|
`,
|
|
@@ -10656,14 +10686,14 @@ var init_setup = __esm({
|
|
|
10656
10686
|
ROLE = "aerie";
|
|
10657
10687
|
SCHEMA = "aerie";
|
|
10658
10688
|
ATTRS = "LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS";
|
|
10659
|
-
settingsFile = () =>
|
|
10689
|
+
settingsFile = () => join6(homedir3(), ".config", "aerie", "env");
|
|
10660
10690
|
}
|
|
10661
10691
|
});
|
|
10662
10692
|
|
|
10663
10693
|
// src/bin.ts
|
|
10664
|
-
import { readFileSync as
|
|
10665
|
-
import { homedir as
|
|
10666
|
-
import { join as
|
|
10694
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
10695
|
+
import { homedir as homedir4, userInfo } from "node:os";
|
|
10696
|
+
import { join as join7 } from "node:path";
|
|
10667
10697
|
|
|
10668
10698
|
// node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
|
|
10669
10699
|
var CommanderError = class extends Error {
|
|
@@ -11840,7 +11870,7 @@ var Command = class _Command extends EventEmitter {
|
|
|
11840
11870
|
this._outputConfiguration = {
|
|
11841
11871
|
writeOut: (str2) => process2.stdout.write(str2),
|
|
11842
11872
|
writeErr: (str2) => process2.stderr.write(str2),
|
|
11843
|
-
outputError: (str2,
|
|
11873
|
+
outputError: (str2, write2) => write2(str2),
|
|
11844
11874
|
getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
|
|
11845
11875
|
getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0,
|
|
11846
11876
|
getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
|
|
@@ -13825,11 +13855,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
13825
13855
|
hasColors = this._outputConfiguration.getOutHasColors();
|
|
13826
13856
|
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
13827
13857
|
}
|
|
13828
|
-
const
|
|
13858
|
+
const write2 = (str2) => {
|
|
13829
13859
|
if (!hasColors) str2 = this._outputConfiguration.stripColor(str2);
|
|
13830
13860
|
return baseWrite(str2);
|
|
13831
13861
|
};
|
|
13832
|
-
return { error: error62, write, hasColors, helpWidth };
|
|
13862
|
+
return { error: error62, write: write2, hasColors, helpWidth };
|
|
13833
13863
|
}
|
|
13834
13864
|
/**
|
|
13835
13865
|
* Output help information for this command.
|
|
@@ -37652,7 +37682,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37652
37682
|
return h ? h(node2, rewritten) : node2;
|
|
37653
37683
|
};
|
|
37654
37684
|
const cache = /* @__PURE__ */ new Map();
|
|
37655
|
-
function
|
|
37685
|
+
function run2(s) {
|
|
37656
37686
|
const cached2 = cache.get(s);
|
|
37657
37687
|
if (cached2 === RESOLVING) {
|
|
37658
37688
|
return new $ZodLazy({
|
|
@@ -37678,21 +37708,21 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37678
37708
|
let changed = false;
|
|
37679
37709
|
const newShape = {};
|
|
37680
37710
|
for (const k of keys) {
|
|
37681
|
-
const mapped =
|
|
37711
|
+
const mapped = run2(oldShape[k]);
|
|
37682
37712
|
if (mapped !== oldShape[k])
|
|
37683
37713
|
changed = true;
|
|
37684
37714
|
newShape[k] = mapped;
|
|
37685
37715
|
}
|
|
37686
37716
|
let newCatchall = def.catchall;
|
|
37687
37717
|
if (def.catchall) {
|
|
37688
|
-
newCatchall =
|
|
37718
|
+
newCatchall = run2(def.catchall);
|
|
37689
37719
|
if (newCatchall !== def.catchall)
|
|
37690
37720
|
changed = true;
|
|
37691
37721
|
}
|
|
37692
37722
|
return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
|
|
37693
37723
|
}
|
|
37694
37724
|
case "array": {
|
|
37695
|
-
const mapped =
|
|
37725
|
+
const mapped = run2(def.element);
|
|
37696
37726
|
return mapped === def.element ? s : clone(s, { ...def, element: mapped });
|
|
37697
37727
|
}
|
|
37698
37728
|
case "tuple": {
|
|
@@ -37700,14 +37730,14 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37700
37730
|
let changed = false;
|
|
37701
37731
|
const newItems = [];
|
|
37702
37732
|
for (const item of oldItems) {
|
|
37703
|
-
const mapped =
|
|
37733
|
+
const mapped = run2(item);
|
|
37704
37734
|
if (mapped !== item)
|
|
37705
37735
|
changed = true;
|
|
37706
37736
|
newItems.push(mapped);
|
|
37707
37737
|
}
|
|
37708
37738
|
let newRest = def.rest;
|
|
37709
37739
|
if (def.rest) {
|
|
37710
|
-
newRest =
|
|
37740
|
+
newRest = run2(def.rest);
|
|
37711
37741
|
if (newRest !== def.rest)
|
|
37712
37742
|
changed = true;
|
|
37713
37743
|
}
|
|
@@ -37715,12 +37745,12 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37715
37745
|
}
|
|
37716
37746
|
case "record":
|
|
37717
37747
|
case "map": {
|
|
37718
|
-
const newKey =
|
|
37719
|
-
const newVal =
|
|
37748
|
+
const newKey = run2(def.keyType);
|
|
37749
|
+
const newVal = run2(def.valueType);
|
|
37720
37750
|
return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
|
|
37721
37751
|
}
|
|
37722
37752
|
case "set": {
|
|
37723
|
-
const newVal =
|
|
37753
|
+
const newVal = run2(def.valueType);
|
|
37724
37754
|
return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
|
|
37725
37755
|
}
|
|
37726
37756
|
case "union": {
|
|
@@ -37728,7 +37758,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37728
37758
|
let changed = false;
|
|
37729
37759
|
const newOptions = [];
|
|
37730
37760
|
for (const opt of oldOptions) {
|
|
37731
|
-
const mapped =
|
|
37761
|
+
const mapped = run2(opt);
|
|
37732
37762
|
if (mapped !== opt)
|
|
37733
37763
|
changed = true;
|
|
37734
37764
|
newOptions.push(mapped);
|
|
@@ -37736,8 +37766,8 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37736
37766
|
return changed ? clone(s, { ...def, options: newOptions }) : s;
|
|
37737
37767
|
}
|
|
37738
37768
|
case "intersection": {
|
|
37739
|
-
const newLeft =
|
|
37740
|
-
const newRight =
|
|
37769
|
+
const newLeft = run2(def.left);
|
|
37770
|
+
const newRight = run2(def.right);
|
|
37741
37771
|
return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
|
|
37742
37772
|
}
|
|
37743
37773
|
case "optional":
|
|
@@ -37749,23 +37779,23 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37749
37779
|
case "nonoptional":
|
|
37750
37780
|
case "promise":
|
|
37751
37781
|
case "success": {
|
|
37752
|
-
const newInner =
|
|
37782
|
+
const newInner = run2(def.innerType);
|
|
37753
37783
|
return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
|
|
37754
37784
|
}
|
|
37755
37785
|
case "pipe": {
|
|
37756
|
-
const newIn =
|
|
37757
|
-
const newOut =
|
|
37786
|
+
const newIn = run2(def.in);
|
|
37787
|
+
const newOut = run2(def.out);
|
|
37758
37788
|
return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
|
|
37759
37789
|
}
|
|
37760
37790
|
case "function": {
|
|
37761
|
-
const newInput =
|
|
37762
|
-
const newOutput =
|
|
37791
|
+
const newInput = run2(def.input);
|
|
37792
|
+
const newOutput = run2(def.output);
|
|
37763
37793
|
return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
|
|
37764
37794
|
}
|
|
37765
37795
|
case "lazy": {
|
|
37766
37796
|
const original = def.getter;
|
|
37767
37797
|
const { _cachedInner, ...rest } = def;
|
|
37768
|
-
return clone(s, { ...rest, getter: () =>
|
|
37798
|
+
return clone(s, { ...rest, getter: () => run2(original()) });
|
|
37769
37799
|
}
|
|
37770
37800
|
// A leaf by choice: `parts` are regex fragments, not data positions.
|
|
37771
37801
|
case "template_literal":
|
|
@@ -37796,7 +37826,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37796
37826
|
}
|
|
37797
37827
|
}
|
|
37798
37828
|
}
|
|
37799
|
-
return
|
|
37829
|
+
return run2(schema2);
|
|
37800
37830
|
}
|
|
37801
37831
|
|
|
37802
37832
|
// node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/deep-partial.js
|
|
@@ -37951,12 +37981,17 @@ async function routeOf(a, t, pick2) {
|
|
|
37951
37981
|
};
|
|
37952
37982
|
}
|
|
37953
37983
|
var queueSpot = (r) => r.runner === "session" ? { lane: r.lane } : { lane: r.lane, pin: r.lane };
|
|
37954
|
-
|
|
37984
|
+
var direct = (a, c) => (fn) => fn(a, c);
|
|
37985
|
+
async function route(a, coord, id, by, pick2, atomic2 = direct(a, coord)) {
|
|
37955
37986
|
const t = await a.get(id);
|
|
37956
37987
|
if (!t) throw new AerieError(`no task ${id}`, 404);
|
|
37957
37988
|
const r = await routeOf(a, t, pick2);
|
|
37989
|
+
await atomic2((a2, coord2) => write(a2, coord2, t.id, r, by));
|
|
37990
|
+
return r;
|
|
37991
|
+
}
|
|
37992
|
+
async function write(a, coord, id, r, by) {
|
|
37958
37993
|
await a.log(
|
|
37959
|
-
|
|
37994
|
+
id,
|
|
37960
37995
|
by,
|
|
37961
37996
|
"routed",
|
|
37962
37997
|
{
|
|
@@ -37968,11 +38003,10 @@ async function route(a, coord, id, by, pick2) {
|
|
|
37968
38003
|
},
|
|
37969
38004
|
r.why
|
|
37970
38005
|
);
|
|
37971
|
-
await coord.queueAdd([
|
|
37972
|
-
return r;
|
|
38006
|
+
await coord.queueAdd([id], queueSpot(r));
|
|
37973
38007
|
}
|
|
37974
38008
|
var RUN_FAILED = "Run failed";
|
|
37975
|
-
async function afterDecision(a, coord, d, by, pick2) {
|
|
38009
|
+
async function afterDecision(a, coord, d, by, pick2, atomic2 = direct(a, coord)) {
|
|
37976
38010
|
const out = [];
|
|
37977
38011
|
const worker = d.kind === "run-failed";
|
|
37978
38012
|
for (const id of d.blocks) {
|
|
@@ -37982,16 +38016,18 @@ async function afterDecision(a, coord, d, by, pick2) {
|
|
|
37982
38016
|
await a.edit(t.id, { assignees: ["@william"] }, by, { why: `decision ${d.id}` });
|
|
37983
38017
|
out.push({ id: t.id, lane: null });
|
|
37984
38018
|
} else if (worker && d.answer === "session") {
|
|
37985
|
-
await
|
|
37986
|
-
|
|
37987
|
-
|
|
37988
|
-
|
|
37989
|
-
|
|
37990
|
-
|
|
37991
|
-
|
|
37992
|
-
|
|
38019
|
+
await atomic2(async (a2, coord2) => {
|
|
38020
|
+
await a2.log(
|
|
38021
|
+
t.id,
|
|
38022
|
+
by,
|
|
38023
|
+
"routed",
|
|
38024
|
+
{ runner: "session", model: null, lane: t.area, rule: null },
|
|
38025
|
+
`decision ${d.id}`
|
|
38026
|
+
);
|
|
38027
|
+
await coord2.queueAdd([t.id], { lane: t.area });
|
|
38028
|
+
});
|
|
37993
38029
|
out.push({ id: t.id, lane: t.area });
|
|
37994
|
-
} else out.push({ id: t.id, lane: (await route(a, coord, t.id, by, pick2)).lane });
|
|
38030
|
+
} else out.push({ id: t.id, lane: (await route(a, coord, t.id, by, pick2, atomic2)).lane });
|
|
37995
38031
|
}
|
|
37996
38032
|
return out;
|
|
37997
38033
|
}
|
|
@@ -38027,6 +38063,7 @@ function normal(w) {
|
|
|
38027
38063
|
return unmark(w).toLowerCase().replace(/[‘’`´]/g, "'").replace(/[“”„]/g, '"').replace(/[–—]/g, "-").replace(/\s+/g, " ").trim().replace(/^[\s"'.,;:!?-]+|[\s"'.,;:!?-]+$/g, "");
|
|
38028
38064
|
}
|
|
38029
38065
|
var sentences = (w) => unmark(w).split(/(?<=[.!?])\s+|\n+/).map((x) => x.trim()).filter(Boolean);
|
|
38066
|
+
var COVERS = 20;
|
|
38030
38067
|
function check3(words, split, o) {
|
|
38031
38068
|
const src = normal(words);
|
|
38032
38069
|
const covered = new Uint8Array(src.length);
|
|
@@ -38059,7 +38096,7 @@ function check3(words, split, o) {
|
|
|
38059
38096
|
from = i + n.length;
|
|
38060
38097
|
let hit = 0;
|
|
38061
38098
|
for (let j = i; j < i + n.length; j++) hit += covered[j] ?? 0;
|
|
38062
|
-
if (hit
|
|
38099
|
+
if (hit < Math.min(n.length / 2, COVERS)) out.missed.push(raw);
|
|
38063
38100
|
}
|
|
38064
38101
|
return out;
|
|
38065
38102
|
}
|
|
@@ -38096,12 +38133,12 @@ async function hear(file2, o = {}) {
|
|
|
38096
38133
|
const { hearSpeech, speechHere } = await import("@wrenautomation/read/video");
|
|
38097
38134
|
const { mkdtemp: mkdtemp2, rm } = await import("node:fs/promises");
|
|
38098
38135
|
const { tmpdir: tmpdir2 } = await import("node:os");
|
|
38099
|
-
const { join:
|
|
38136
|
+
const { join: join8 } = await import("node:path");
|
|
38100
38137
|
const cohere = (process.env.COHERE_API_KEY ?? "").split(",").filter(Boolean);
|
|
38101
38138
|
const ear = speechHere(cohere);
|
|
38102
38139
|
if (!ear)
|
|
38103
38140
|
throw new AerieError("no ear: install whisper.cpp and its model, or set COHERE_API_KEY");
|
|
38104
|
-
const dir = await mkdtemp2(
|
|
38141
|
+
const dir = await mkdtemp2(join8(tmpdir2(), "aerie-hear-"));
|
|
38105
38142
|
const t0 = performance.now();
|
|
38106
38143
|
try {
|
|
38107
38144
|
const text3 = await hearSpeech(file2, dir, o.ffmpeg ?? "ffmpeg", ear, {
|
|
@@ -38268,10 +38305,22 @@ async function intake(a, input2, by, o = {}) {
|
|
|
38268
38305
|
},
|
|
38269
38306
|
taskIds: ids
|
|
38270
38307
|
});
|
|
38271
|
-
if (input2.route
|
|
38308
|
+
if (input2.route)
|
|
38272
38309
|
for (const id of ids) {
|
|
38273
|
-
|
|
38274
|
-
|
|
38310
|
+
if (!o.coord)
|
|
38311
|
+
res.routed.push({
|
|
38312
|
+
id,
|
|
38313
|
+
runner: null,
|
|
38314
|
+
lane: null,
|
|
38315
|
+
error: "no board: route needs Postgres"
|
|
38316
|
+
});
|
|
38317
|
+
else
|
|
38318
|
+
try {
|
|
38319
|
+
const r = await route(a, o.coord, id, by, o.pick, o.atomic);
|
|
38320
|
+
res.routed.push({ id, runner: r.runner, lane: r.lane });
|
|
38321
|
+
} catch (e) {
|
|
38322
|
+
res.routed.push({ id, runner: null, lane: null, error: e.message });
|
|
38323
|
+
}
|
|
38275
38324
|
}
|
|
38276
38325
|
}
|
|
38277
38326
|
return res;
|
|
@@ -38298,17 +38347,20 @@ function plain(r) {
|
|
|
38298
38347
|
for (const s of r.failedSpans)
|
|
38299
38348
|
out.push(` not filed: "${s.title}": span not in the words: "${s.span}"`);
|
|
38300
38349
|
for (const m of r.missed) out.push(` missed: "${m}"`);
|
|
38301
|
-
for (const x of r.routed)
|
|
38350
|
+
for (const x of r.routed)
|
|
38351
|
+
out.push(
|
|
38352
|
+
x.error ? ` ${x.id} not routed: ${x.error}` : ` ${x.id} \u2192 ${x.runner}${x.lane ? ` on ${x.lane}` : ""}`
|
|
38353
|
+
);
|
|
38302
38354
|
return `${out.join("\n")}
|
|
38303
38355
|
`;
|
|
38304
38356
|
}
|
|
38305
38357
|
function registerIntakeCommands(program3, ctx) {
|
|
38306
|
-
const either = (fn) => ctx.withBoard ? ctx.withBoard(
|
|
38358
|
+
const either = (fn) => ctx.withBoard ? ctx.withBoard(fn) : ctx.withStore((a) => fn(a, null));
|
|
38307
38359
|
program3.command("intake").argument("[words...]", "what to file; or --file, --audio, --into").description("words \u2192 tasks: split, checked (spans, missed words, areas, duplicates), filed").option("--file <path>", "the words from a file").option("--audio <path>", "a voice memo: heard by local whisper in 60 s clips").option("--kind <kind>", "text | chat | voice (default: voice with --audio, else text)").option(
|
|
38308
38360
|
"--split <file>",
|
|
38309
38361
|
"your own split as JSON ({tasks: [{title, span, why, area?}]}); - = stdin"
|
|
38310
38362
|
).option("--into <id>", "the intake a 'Split intake' task waits on").option("--area <path>", "area for tasks whose split names none").option("--why <text>", "why this intake").option("--dry-run", "split and check, file nothing").option("--route", "route each filed task").option("--json", "JSON out").action(
|
|
38311
|
-
(words, o) => either(async (a, c) => {
|
|
38363
|
+
(words, o) => either(async (a, c, atomic2) => {
|
|
38312
38364
|
const text3 = o.file ? await readFile(o.file, "utf8") : words.join(" ");
|
|
38313
38365
|
const split = o.split === void 0 ? void 0 : JSON.parse(o.split === "-" ? await stdin() : await readFile(o.split, "utf8"));
|
|
38314
38366
|
const spec = await a.getSetting("router_model");
|
|
@@ -38330,6 +38382,7 @@ function registerIntakeCommands(program3, ctx) {
|
|
|
38330
38382
|
model: await a.getSetting("intake_model") || void 0,
|
|
38331
38383
|
lane: await a.getSetting("intake_lane") || "main",
|
|
38332
38384
|
coord: c ?? void 0,
|
|
38385
|
+
atomic: atomic2,
|
|
38333
38386
|
pick: spec ? modelPicker(spec) : void 0
|
|
38334
38387
|
}
|
|
38335
38388
|
);
|
|
@@ -38391,11 +38444,181 @@ function aerieEnv(env = process.env) {
|
|
|
38391
38444
|
return (k) => env[k] ?? fromFile[k];
|
|
38392
38445
|
}
|
|
38393
38446
|
|
|
38447
|
+
// src/pool.ts
|
|
38448
|
+
import { execFile } from "node:child_process";
|
|
38449
|
+
import { randomUUID } from "node:crypto";
|
|
38450
|
+
import {
|
|
38451
|
+
closeSync,
|
|
38452
|
+
existsSync as existsSync3,
|
|
38453
|
+
mkdirSync,
|
|
38454
|
+
openSync,
|
|
38455
|
+
readdirSync as readdirSync2,
|
|
38456
|
+
readFileSync as readFileSync3,
|
|
38457
|
+
unlinkSync,
|
|
38458
|
+
writeFileSync
|
|
38459
|
+
} from "node:fs";
|
|
38460
|
+
import { homedir as homedir2 } from "node:os";
|
|
38461
|
+
import { dirname, join as join3 } from "node:path";
|
|
38462
|
+
import { promisify } from "node:util";
|
|
38463
|
+
var run = promisify(execFile);
|
|
38464
|
+
var POOL_PREFIX = "aerie-pool-";
|
|
38465
|
+
var DEFAULT_COMMAND = [
|
|
38466
|
+
"claude",
|
|
38467
|
+
"--session-id",
|
|
38468
|
+
"{session}",
|
|
38469
|
+
// unattended: a permission prompt would stall it (the session's own rules still apply)
|
|
38470
|
+
"--permission-mode",
|
|
38471
|
+
"bypassPermissions",
|
|
38472
|
+
"Run `peers serve {lane}`, then wait: tasks on lane {lane} come to you as prompts. Work each one to done."
|
|
38473
|
+
];
|
|
38474
|
+
function parseLanes(v, key = "pool_lanes") {
|
|
38475
|
+
const out = {};
|
|
38476
|
+
for (const part of v.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
38477
|
+
const [lane, n] = part.split("=");
|
|
38478
|
+
const k = Number(n);
|
|
38479
|
+
if (!lane || !Number.isInteger(k) || k < 0) throw new Error(`${key}: "${part}" is not name=N`);
|
|
38480
|
+
out[lane] = k;
|
|
38481
|
+
}
|
|
38482
|
+
return out;
|
|
38483
|
+
}
|
|
38484
|
+
var holds = (holder, session) => holder.endsWith(`-${session.slice(0, 4)}`);
|
|
38485
|
+
async function reconcile(o) {
|
|
38486
|
+
const now = (o.now ?? Date.now)();
|
|
38487
|
+
const out = [];
|
|
38488
|
+
const live = new Set(await o.mux.list());
|
|
38489
|
+
const held = await o.coord.held();
|
|
38490
|
+
const queue = await o.coord.queueList();
|
|
38491
|
+
const leasesOf = (s) => held.flatMap(
|
|
38492
|
+
(h) => h.holders.filter((l) => holds(l.holder, s.session)).map((l) => ({ id: h.id, holder: l.holder }))
|
|
38493
|
+
);
|
|
38494
|
+
const fill = (s, lane) => o.command.map((a) => a.replaceAll("{session}", s).replaceAll("{lane}", lane));
|
|
38495
|
+
const startSlot = async (lane, name, act, why) => {
|
|
38496
|
+
const session = randomUUID();
|
|
38497
|
+
await o.mux.start(name, fill(session, lane), { cwd: o.cwd });
|
|
38498
|
+
o.state.slots.push({ name, lane, session, started: now, busyAt: now });
|
|
38499
|
+
out.push({ act, name, lane, why });
|
|
38500
|
+
};
|
|
38501
|
+
for (const s of [...o.state.slots]) {
|
|
38502
|
+
if (live.has(s.name)) continue;
|
|
38503
|
+
for (const l of leasesOf(s)) await o.coord.release(l.id, l.holder, { queue: true });
|
|
38504
|
+
o.state.slots = o.state.slots.filter((x) => x !== s);
|
|
38505
|
+
if ((o.lanes[s.lane] ?? 0) > 0)
|
|
38506
|
+
await startSlot(s.lane, s.name, "restart", "its tmux session is gone");
|
|
38507
|
+
}
|
|
38508
|
+
for (const s of o.state.slots) {
|
|
38509
|
+
const st = o.sessionState(s.session);
|
|
38510
|
+
if (leasesOf(s).length || st?.status === "busy" || st?.status === "shell") s.busyAt = now;
|
|
38511
|
+
if (st?.waitingFor) out.push({ act: "waits", name: s.name, lane: s.lane, why: st.waitingFor });
|
|
38512
|
+
}
|
|
38513
|
+
for (const [lane, n] of Object.entries(o.lanes)) {
|
|
38514
|
+
const mine = o.state.slots.filter((s) => s.lane === lane);
|
|
38515
|
+
const queued = queue.filter((q) => q.lane === lane && !q.pin).length;
|
|
38516
|
+
const busy = mine.filter((s) => s.busyAt === now).length;
|
|
38517
|
+
const target = Math.min(n, busy + queued);
|
|
38518
|
+
for (let i = 0; mine.length + i < target; i++) {
|
|
38519
|
+
const name = `${POOL_PREFIX}${lane.replace(/[^a-z0-9]+/gi, "-")}-${randomUUID().slice(0, 4)}`;
|
|
38520
|
+
await startSlot(lane, name, "start", `${queued} queued on ${lane}`);
|
|
38521
|
+
}
|
|
38522
|
+
let extra = mine.length - target;
|
|
38523
|
+
for (const s of mine.sort((a, b) => a.busyAt - b.busyAt)) {
|
|
38524
|
+
if (extra <= 0) break;
|
|
38525
|
+
if (s.busyAt === now || now - s.busyAt < o.idleMs) continue;
|
|
38526
|
+
await o.mux.stop(s.name);
|
|
38527
|
+
o.state.slots = o.state.slots.filter((x) => x !== s);
|
|
38528
|
+
out.push({
|
|
38529
|
+
act: "stop",
|
|
38530
|
+
name: s.name,
|
|
38531
|
+
lane,
|
|
38532
|
+
why: `idle ${Math.round((now - s.busyAt) / 6e4)} min, nothing queued`
|
|
38533
|
+
});
|
|
38534
|
+
extra--;
|
|
38535
|
+
}
|
|
38536
|
+
}
|
|
38537
|
+
return out;
|
|
38538
|
+
}
|
|
38539
|
+
function tmuxMux(bin = "tmux") {
|
|
38540
|
+
return {
|
|
38541
|
+
async list() {
|
|
38542
|
+
try {
|
|
38543
|
+
const { stdout } = await run(bin, ["list-sessions", "-F", "#{session_name}"]);
|
|
38544
|
+
return stdout.split("\n").filter((n) => n.startsWith(POOL_PREFIX));
|
|
38545
|
+
} catch (e) {
|
|
38546
|
+
const err = String(e.stderr ?? "");
|
|
38547
|
+
if (/no server running|error connecting to/.test(err)) return [];
|
|
38548
|
+
throw e;
|
|
38549
|
+
}
|
|
38550
|
+
},
|
|
38551
|
+
async start(name, argv, o) {
|
|
38552
|
+
await run(bin, ["new-session", "-d", "-s", name, "-c", o.cwd, "--", ...argv]);
|
|
38553
|
+
},
|
|
38554
|
+
async stop(name) {
|
|
38555
|
+
await run(bin, ["kill-session", "-t", name]).catch(() => {
|
|
38556
|
+
});
|
|
38557
|
+
}
|
|
38558
|
+
};
|
|
38559
|
+
}
|
|
38560
|
+
function claudeSessionState(dir = join3(homedir2(), ".claude", "sessions")) {
|
|
38561
|
+
return (session) => {
|
|
38562
|
+
for (const f of existsSync3(dir) ? readdirSync2(dir) : []) {
|
|
38563
|
+
if (!f.endsWith(".json")) continue;
|
|
38564
|
+
try {
|
|
38565
|
+
const d = JSON.parse(readFileSync3(join3(dir, f), "utf8"));
|
|
38566
|
+
if (d.sessionId === session) return d;
|
|
38567
|
+
} catch {
|
|
38568
|
+
}
|
|
38569
|
+
}
|
|
38570
|
+
return null;
|
|
38571
|
+
};
|
|
38572
|
+
}
|
|
38573
|
+
var poolDir = () => join3(homedir2(), ".local", "state", "aerie");
|
|
38574
|
+
function loadPool(file2 = join3(poolDir(), "pool.json")) {
|
|
38575
|
+
try {
|
|
38576
|
+
return JSON.parse(readFileSync3(file2, "utf8"));
|
|
38577
|
+
} catch {
|
|
38578
|
+
return { slots: [] };
|
|
38579
|
+
}
|
|
38580
|
+
}
|
|
38581
|
+
function savePool(s, file2 = join3(poolDir(), "pool.json")) {
|
|
38582
|
+
mkdirSync(dirname(file2), { recursive: true });
|
|
38583
|
+
writeFileSync(file2, `${JSON.stringify(s, null, 2)}
|
|
38584
|
+
`);
|
|
38585
|
+
}
|
|
38586
|
+
function lockPool(file2 = join3(poolDir(), "pool.lock")) {
|
|
38587
|
+
mkdirSync(dirname(file2), { recursive: true });
|
|
38588
|
+
for (let i = 0; i < 2; i++) {
|
|
38589
|
+
try {
|
|
38590
|
+
const fd = openSync(file2, "wx");
|
|
38591
|
+
writeFileSync(fd, String(process.pid));
|
|
38592
|
+
closeSync(fd);
|
|
38593
|
+
return () => unlinkSync(file2);
|
|
38594
|
+
} catch {
|
|
38595
|
+
const pid = Number(readFileSync3(file2, "utf8"));
|
|
38596
|
+
if (pid && alive(pid)) throw new Error(`aerie pool: already running (pid ${pid})`);
|
|
38597
|
+
unlinkSync(file2);
|
|
38598
|
+
}
|
|
38599
|
+
}
|
|
38600
|
+
throw new Error("aerie pool: cannot take the lock");
|
|
38601
|
+
}
|
|
38602
|
+
function alive(pid) {
|
|
38603
|
+
try {
|
|
38604
|
+
process.kill(pid, 0);
|
|
38605
|
+
return true;
|
|
38606
|
+
} catch {
|
|
38607
|
+
return false;
|
|
38608
|
+
}
|
|
38609
|
+
}
|
|
38610
|
+
|
|
38394
38611
|
// src/run/spawn.ts
|
|
38395
38612
|
init_db2();
|
|
38396
38613
|
import { spawn } from "node:child_process";
|
|
38397
38614
|
import { createInterface } from "node:readline";
|
|
38398
38615
|
var STDERR_KEEP = 64e3;
|
|
38616
|
+
var CLEAN_ENV = ["PATH", "USER", "LANG", "LC_ALL", "TMPDIR", "SHELL", "TERM"];
|
|
38617
|
+
function cleanEnv(from = process.env) {
|
|
38618
|
+
const out = {};
|
|
38619
|
+
for (const k of CLEAN_ENV) if (from[k] !== void 0) out[k] = from[k];
|
|
38620
|
+
return out;
|
|
38621
|
+
}
|
|
38399
38622
|
function runLines(bin, args, o) {
|
|
38400
38623
|
return new Promise((resolve, reject) => {
|
|
38401
38624
|
if (o.signal.aborted) {
|
|
@@ -38404,7 +38627,7 @@ function runLines(bin, args, o) {
|
|
|
38404
38627
|
}
|
|
38405
38628
|
const child = spawn(bin, args, {
|
|
38406
38629
|
cwd: o.cwd,
|
|
38407
|
-
env: { ...process.env, ...o.env },
|
|
38630
|
+
env: o.inherit === false ? { ...cleanEnv(), ...o.env } : { ...process.env, ...o.env },
|
|
38408
38631
|
detached: true,
|
|
38409
38632
|
stdio: ["ignore", "pipe", "pipe"]
|
|
38410
38633
|
});
|
|
@@ -38517,6 +38740,7 @@ function claudeEvent(l) {
|
|
|
38517
38740
|
function claudeRunner(o = {}) {
|
|
38518
38741
|
return {
|
|
38519
38742
|
name: "claude",
|
|
38743
|
+
needs: ["ANTHROPIC_API_KEY"],
|
|
38520
38744
|
async run(t, ctx) {
|
|
38521
38745
|
let last = null;
|
|
38522
38746
|
let session;
|
|
@@ -38567,6 +38791,7 @@ function lastText(v) {
|
|
|
38567
38791
|
function codexRunner(o = {}) {
|
|
38568
38792
|
return {
|
|
38569
38793
|
name: "codex",
|
|
38794
|
+
needs: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
|
38570
38795
|
async run(t, ctx) {
|
|
38571
38796
|
let summary = null;
|
|
38572
38797
|
const r = await runLines(
|
|
@@ -38595,6 +38820,7 @@ function codexRunner(o = {}) {
|
|
|
38595
38820
|
function grokRunner(o = {}) {
|
|
38596
38821
|
return {
|
|
38597
38822
|
name: "grok",
|
|
38823
|
+
needs: ["XAI_API_KEY"],
|
|
38598
38824
|
async run(t, ctx) {
|
|
38599
38825
|
const out = [];
|
|
38600
38826
|
const r = await runLines(
|
|
@@ -38622,6 +38848,7 @@ var DO_LINE = /^(done|needs-human|failed|planned) via (\S+)(?: ([^:]+))?: (.*)$/
|
|
|
38622
38848
|
function autobrowseRunner(o = {}) {
|
|
38623
38849
|
return {
|
|
38624
38850
|
name: "autobrowse-do",
|
|
38851
|
+
needs: ["AUTOBROWSE_OWNER"],
|
|
38625
38852
|
async run(t, ctx) {
|
|
38626
38853
|
const input2 = t.extra.input ?? {};
|
|
38627
38854
|
const out = [];
|
|
@@ -38675,7 +38902,23 @@ function apiRunner(o) {
|
|
|
38675
38902
|
summary: "no model for the api runner",
|
|
38676
38903
|
question: `No model for ${t.id} on the api runner: set AERIE_API_MODEL, or a route rule with a model?`
|
|
38677
38904
|
};
|
|
38678
|
-
const
|
|
38905
|
+
const tenant = ctx.inherit === false;
|
|
38906
|
+
if (tenant && /^(claude-code|command)(:|$)/.test(spec))
|
|
38907
|
+
return {
|
|
38908
|
+
outcome: "needs-input",
|
|
38909
|
+
summary: `model ${spec} runs on the host's login`,
|
|
38910
|
+
question: `${t.id}: a tenant run cannot use ${spec}. Which model, on the tenant's own key?`
|
|
38911
|
+
};
|
|
38912
|
+
let m;
|
|
38913
|
+
try {
|
|
38914
|
+
m = modelFromSpec(spec, tenant ? ctx.env : { ...process.env, ...o.env, ...ctx.env });
|
|
38915
|
+
} catch (e) {
|
|
38916
|
+
return {
|
|
38917
|
+
outcome: "needs-input",
|
|
38918
|
+
summary: e.message,
|
|
38919
|
+
question: `${t.id}: ${e.message}. Add the key, then answer.`
|
|
38920
|
+
};
|
|
38921
|
+
}
|
|
38679
38922
|
await ctx.emit({ kind: "init", text: m.id, data: { spec, model: m.id } });
|
|
38680
38923
|
try {
|
|
38681
38924
|
const { value, usage } = await withDeadline(
|
|
@@ -38708,7 +38951,7 @@ You have no tools. Answer as JSON: outcome (done, needs-input or failed), summar
|
|
|
38708
38951
|
};
|
|
38709
38952
|
}
|
|
38710
38953
|
function spawnOf(ctx) {
|
|
38711
|
-
return { cwd: ctx.cwd, env: ctx.env, signal: ctx.signal };
|
|
38954
|
+
return { cwd: ctx.cwd, env: ctx.env, inherit: ctx.inherit, signal: ctx.signal };
|
|
38712
38955
|
}
|
|
38713
38956
|
function headlessRunners(get2) {
|
|
38714
38957
|
const opt = (name) => {
|
|
@@ -38735,9 +38978,22 @@ function headlessRunners(get2) {
|
|
|
38735
38978
|
// src/run/worker.ts
|
|
38736
38979
|
import { mkdtemp } from "node:fs/promises";
|
|
38737
38980
|
import { hostname as hostname3, tmpdir } from "node:os";
|
|
38738
|
-
import { join as
|
|
38981
|
+
import { join as join4 } from "node:path";
|
|
38739
38982
|
var MAX_FAILS = 3;
|
|
38983
|
+
async function failsSince(store, id, since) {
|
|
38984
|
+
const [failed, starts, results] = await Promise.all([
|
|
38985
|
+
store.countEvents(id, "run.result", { outcome: "failed" }, since),
|
|
38986
|
+
store.countEvents(id, "run.start", void 0, since),
|
|
38987
|
+
store.countEvents(id, "run.result", void 0, since)
|
|
38988
|
+
]);
|
|
38989
|
+
return { failed, died: starts - results };
|
|
38990
|
+
}
|
|
38740
38991
|
var LEASE_MS = 12e4;
|
|
38992
|
+
function missingCredentials(runner, tenant) {
|
|
38993
|
+
const needs = runner.needs ?? [];
|
|
38994
|
+
if (!needs.length || needs.some((k) => tenant.keys[k])) return [];
|
|
38995
|
+
return needs;
|
|
38996
|
+
}
|
|
38741
38997
|
var holderOf = (r) => `${hostname3()}/aerie-${r}-${process.pid}`;
|
|
38742
38998
|
async function workOnce(o) {
|
|
38743
38999
|
const { store, coord, runner } = o;
|
|
@@ -38746,7 +39002,11 @@ async function workOnce(o) {
|
|
|
38746
39002
|
const leaseMs = o.leaseMs ?? LEASE_MS;
|
|
38747
39003
|
const by = `runner:${runner.name}`;
|
|
38748
39004
|
const lane = runLane(runner.name);
|
|
38749
|
-
const took = await coord.queueTake(
|
|
39005
|
+
const took = await coord.queueTake(
|
|
39006
|
+
{ lanes: [], pin: lane, ...o.cap === void 0 ? {} : { cap: o.cap } },
|
|
39007
|
+
holder,
|
|
39008
|
+
leaseMs
|
|
39009
|
+
);
|
|
38750
39010
|
if (!took) return null;
|
|
38751
39011
|
const { id, gen } = took;
|
|
38752
39012
|
const stop = new AbortController();
|
|
@@ -38774,7 +39034,7 @@ async function workOnce(o) {
|
|
|
38774
39034
|
},
|
|
38775
39035
|
Math.max(1, Math.floor(leaseMs / 3))
|
|
38776
39036
|
);
|
|
38777
|
-
const
|
|
39037
|
+
const write2 = async (kind, body, why) => {
|
|
38778
39038
|
if (await fenced()) await store.log(id, by, kind, body, why);
|
|
38779
39039
|
};
|
|
38780
39040
|
let res;
|
|
@@ -38782,8 +39042,8 @@ async function workOnce(o) {
|
|
|
38782
39042
|
const t = await store.get(id);
|
|
38783
39043
|
const routed = await store.lastEvent(id, "routed");
|
|
38784
39044
|
const model = routed?.body?.model ?? null;
|
|
38785
|
-
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(
|
|
38786
|
-
await
|
|
39045
|
+
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(join4(tmpdir(), `aerie-run-${id}-`));
|
|
39046
|
+
await write2(
|
|
38787
39047
|
"run.start",
|
|
38788
39048
|
{ runner: runner.name, model, holder, gen, cwd },
|
|
38789
39049
|
routed?.why ?? void 0
|
|
@@ -38792,16 +39052,31 @@ async function workOnce(o) {
|
|
|
38792
39052
|
const opt = d2.options.find((x) => x.key === d2.answer);
|
|
38793
39053
|
return `${d2.question} \u2192 ${opt ? opt.text : d2.answer} (${d2.answeredBy}${d2.why ? `: ${d2.why}` : ""})`;
|
|
38794
39054
|
});
|
|
39055
|
+
const missing = o.tenant ? missingCredentials(runner, o.tenant) : [];
|
|
39056
|
+
const before = await failsSince(store, id, routed?.seq ?? 0);
|
|
39057
|
+
const died = before.died - 1;
|
|
38795
39058
|
if (!t) res = { outcome: "failed", summary: `task ${id} is gone` };
|
|
39059
|
+
else if (before.failed + died >= MAX_FAILS)
|
|
39060
|
+
res = {
|
|
39061
|
+
outcome: "failed",
|
|
39062
|
+
summary: `the worker died ${died} times on this task, no result written`
|
|
39063
|
+
};
|
|
39064
|
+
else if (missing.length && o.tenant)
|
|
39065
|
+
res = {
|
|
39066
|
+
outcome: "needs-input",
|
|
39067
|
+
summary: `no ${runner.name} credential for tenant ${o.tenant.name}`,
|
|
39068
|
+
question: `Tenant ${o.tenant.name} has no ${missing.join(" or ")} for ${runner.name}. Add it to the tenant's credentials, then answer.`
|
|
39069
|
+
};
|
|
38796
39070
|
else
|
|
38797
39071
|
try {
|
|
38798
39072
|
res = await runner.run(t, {
|
|
38799
39073
|
model,
|
|
38800
39074
|
answers,
|
|
38801
39075
|
cwd,
|
|
38802
|
-
env: { ...o.env, AERIE_TASK: id, AERIE_BY: by },
|
|
39076
|
+
env: o.tenant ? { ...o.env, ...o.tenant.keys, HOME: o.tenant.home, AERIE_TASK: id, AERIE_BY: by } : { ...o.env, AERIE_TASK: id, AERIE_BY: by },
|
|
39077
|
+
inherit: !o.tenant,
|
|
38803
39078
|
signal: stop.signal,
|
|
38804
|
-
emit: (e) =>
|
|
39079
|
+
emit: (e) => write2(`run.${e.kind}`, { text: e.text, data: e.data })
|
|
38805
39080
|
});
|
|
38806
39081
|
} catch (e) {
|
|
38807
39082
|
res = { outcome: "failed", summary: e.message, data: { error: String(e) } };
|
|
@@ -38811,11 +39086,14 @@ async function workOnce(o) {
|
|
|
38811
39086
|
o.signal?.removeEventListener("abort", onOuter);
|
|
38812
39087
|
}
|
|
38813
39088
|
if (lost || !await fenced()) return { id, outcome: "lost" };
|
|
38814
|
-
if (o.signal?.aborted)
|
|
39089
|
+
if (o.signal?.aborted) {
|
|
39090
|
+
await write2("run.result", { outcome: "stopped", said: res.outcome, summary: "worker stopped" });
|
|
39091
|
+
return { id, outcome: "lost" };
|
|
39092
|
+
}
|
|
38815
39093
|
const open3 = (await store.decisions({ open: true })).find((d2) => d2.blocks.includes(id));
|
|
38816
39094
|
const outcome = open3 ? "needs-input" : res.outcome;
|
|
38817
39095
|
const { summary } = res;
|
|
38818
|
-
await
|
|
39096
|
+
await write2(
|
|
38819
39097
|
"run.result",
|
|
38820
39098
|
{
|
|
38821
39099
|
outcome,
|
|
@@ -38835,8 +39113,8 @@ async function workOnce(o) {
|
|
|
38835
39113
|
return { id, outcome, summary };
|
|
38836
39114
|
}
|
|
38837
39115
|
if (outcome === "failed") {
|
|
38838
|
-
const
|
|
38839
|
-
const fails =
|
|
39116
|
+
const n = await failsSince(store, id, (await store.lastEvent(id, "routed"))?.seq ?? 0);
|
|
39117
|
+
const fails = n.failed + n.died;
|
|
38840
39118
|
await coord.release(id, holder);
|
|
38841
39119
|
if (!coord.ownsStatus) await store.edit(id, { status: "To Do" }, by);
|
|
38842
39120
|
if (fails < MAX_FAILS) {
|
|
@@ -38940,7 +39218,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38940
39218
|
"route a task: the first matching rule, else the router model; then queue it on its lane"
|
|
38941
39219
|
).option("--dry-run", "show the route, write nothing").option("--json", "JSON out").action((id, o) => {
|
|
38942
39220
|
if (!id) return void rt.help();
|
|
38943
|
-
return board(async (a, c) => {
|
|
39221
|
+
return board(async (a, c, atomic2) => {
|
|
38944
39222
|
const pick2 = await pickerOf(a);
|
|
38945
39223
|
if (o.dryRun) {
|
|
38946
39224
|
const t = await a.get(id);
|
|
@@ -38951,7 +39229,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38951
39229
|
`
|
|
38952
39230
|
);
|
|
38953
39231
|
}
|
|
38954
|
-
const r = await route(a, c, id, ctx.by, pick2);
|
|
39232
|
+
const r = await route(a, c, id, ctx.by, pick2, atomic2);
|
|
38955
39233
|
print2(
|
|
38956
39234
|
o.json ? r : `${id.toUpperCase()} \u2192 ${r.runner}${r.model ? ` (${r.model})` : ""} on ${r.lane}: ${r.why}
|
|
38957
39235
|
`
|
|
@@ -39002,7 +39280,10 @@ function registerRunCommands(program3, ctx) {
|
|
|
39002
39280
|
program3.command("work").requiredOption(
|
|
39003
39281
|
"--runner <name>",
|
|
39004
39282
|
"a headless runner: claude | codex | grok | api | autobrowse-do"
|
|
39005
|
-
).option("--once", "one task at most, then exit").option("--lease <ms>", "lease length", String(LEASE_MS)).option("--idle <ms>", "wait between empty polls", "15000").option(
|
|
39283
|
+
).option("--once", "one task at most, then exit").option("--lease <ms>", "lease length", String(LEASE_MS)).option("--idle <ms>", "wait between empty polls", "15000").option(
|
|
39284
|
+
"--cap <n>",
|
|
39285
|
+
"most live runs on this lane, all hosts; default setting run_cap (runner=N,...)"
|
|
39286
|
+
).option("--json", "one JSON line per task").description("take tasks from run:<runner>, run each, write the result").action(
|
|
39006
39287
|
(o) => board(async (a, c) => {
|
|
39007
39288
|
const name = runnerOf(o.runner);
|
|
39008
39289
|
if (ctx.local && name !== "api")
|
|
@@ -39011,6 +39292,12 @@ function registerRunCommands(program3, ctx) {
|
|
|
39011
39292
|
);
|
|
39012
39293
|
const runner = headlessRunners(aerieEnv()).get(name);
|
|
39013
39294
|
if (!runner) throw new Error(`${name} runs in a live session, not a worker`);
|
|
39295
|
+
const caps = parseLanes(await a.getSetting("run_cap") ?? "", "run_cap");
|
|
39296
|
+
const cap2 = o.cap === void 0 ? caps[name] : Number(o.cap);
|
|
39297
|
+
if (cap2 !== void 0 && !(Number.isInteger(cap2) && cap2 > 0))
|
|
39298
|
+
throw new Error(
|
|
39299
|
+
`${o.cap === void 0 ? `run_cap ${name}` : "--cap"}: ${cap2} is not a whole number above 0`
|
|
39300
|
+
);
|
|
39014
39301
|
const stop = new AbortController();
|
|
39015
39302
|
const quit = () => stop.abort();
|
|
39016
39303
|
process.once("SIGINT", quit);
|
|
@@ -39025,13 +39312,15 @@ function registerRunCommands(program3, ctx) {
|
|
|
39025
39312
|
idleMs: Number(o.idle),
|
|
39026
39313
|
once: !!o.once,
|
|
39027
39314
|
signal: stop.signal,
|
|
39315
|
+
...cap2 === void 0 ? {} : { cap: cap2 },
|
|
39028
39316
|
onOutcome: (w) => print2(
|
|
39029
39317
|
o.json ? `${JSON.stringify(w)}
|
|
39030
39318
|
` : `${w.id} ${w.outcome}${"summary" in w ? `: ${w.summary.split("\n")[0]}` : ""}
|
|
39031
39319
|
`
|
|
39032
39320
|
)
|
|
39033
39321
|
});
|
|
39034
|
-
if (!out.length && o.once)
|
|
39322
|
+
if (!out.length && o.once)
|
|
39323
|
+
print2(o.json ? "null\n" : `run:${name}: nothing taken (empty, or at its cap)
|
|
39035
39324
|
`);
|
|
39036
39325
|
} finally {
|
|
39037
39326
|
process.off("SIGINT", quit);
|
|
@@ -39039,6 +39328,89 @@ function registerRunCommands(program3, ctx) {
|
|
|
39039
39328
|
}
|
|
39040
39329
|
})
|
|
39041
39330
|
);
|
|
39331
|
+
const pool = program3.command("pool").description("session workers on this host (tmux): N per session lane, setting pool_lanes");
|
|
39332
|
+
const poolOf = async (a) => {
|
|
39333
|
+
const lanes = parseLanes(await a.getSetting("pool_lanes") ?? "");
|
|
39334
|
+
const cmd = await a.getSetting("pool_command");
|
|
39335
|
+
return {
|
|
39336
|
+
lanes,
|
|
39337
|
+
command: cmd ? JSON.parse(cmd) : DEFAULT_COMMAND,
|
|
39338
|
+
idleMs: Number(await a.getSetting("pool_idle_min") || 30) * 6e4
|
|
39339
|
+
};
|
|
39340
|
+
};
|
|
39341
|
+
const pass = async (a, c, mux, o) => {
|
|
39342
|
+
const p = await poolOf(a);
|
|
39343
|
+
const state = loadPool();
|
|
39344
|
+
const acts = await reconcile({
|
|
39345
|
+
mux,
|
|
39346
|
+
coord: c,
|
|
39347
|
+
...p,
|
|
39348
|
+
cwd: o.cwd ?? process.cwd(),
|
|
39349
|
+
state,
|
|
39350
|
+
sessionState: claudeSessionState()
|
|
39351
|
+
});
|
|
39352
|
+
savePool(state);
|
|
39353
|
+
for (const x of acts)
|
|
39354
|
+
print2(o.json ? `${JSON.stringify(x)}
|
|
39355
|
+
` : `${x.act} ${x.name} (${x.lane}): ${x.why}
|
|
39356
|
+
`);
|
|
39357
|
+
return acts;
|
|
39358
|
+
};
|
|
39359
|
+
pool.command("once").option("--cwd <dir>", "where sessions start", process.cwd()).option("--json", "one JSON line per action").description("one reconcile pass: start, restart, stop").action(
|
|
39360
|
+
(o) => board(async (a, c) => {
|
|
39361
|
+
const unlock = lockPool();
|
|
39362
|
+
try {
|
|
39363
|
+
await pass(a, c, tmuxMux(), o);
|
|
39364
|
+
} finally {
|
|
39365
|
+
unlock();
|
|
39366
|
+
}
|
|
39367
|
+
})
|
|
39368
|
+
);
|
|
39369
|
+
pool.command("run").option("--cwd <dir>", "where sessions start", process.cwd()).option("--every <s>", "seconds between passes", "30").option("--json", "one JSON line per action").description("reconcile until stopped (launchd keeps it up); one per host").action(
|
|
39370
|
+
(o) => board(async (a, c) => {
|
|
39371
|
+
const unlock = lockPool();
|
|
39372
|
+
const stop = new AbortController();
|
|
39373
|
+
const quit = () => stop.abort();
|
|
39374
|
+
process.once("SIGINT", quit);
|
|
39375
|
+
process.once("SIGTERM", quit);
|
|
39376
|
+
try {
|
|
39377
|
+
while (!stop.signal.aborted) {
|
|
39378
|
+
await pass(a, c, tmuxMux(), o).catch((e) => print2(`pool: ${e.message}
|
|
39379
|
+
`));
|
|
39380
|
+
await new Promise((r) => {
|
|
39381
|
+
const t = setTimeout(r, Number(o.every) * 1e3);
|
|
39382
|
+
stop.signal.addEventListener(
|
|
39383
|
+
"abort",
|
|
39384
|
+
() => {
|
|
39385
|
+
clearTimeout(t);
|
|
39386
|
+
r();
|
|
39387
|
+
},
|
|
39388
|
+
{ once: true }
|
|
39389
|
+
);
|
|
39390
|
+
});
|
|
39391
|
+
}
|
|
39392
|
+
} finally {
|
|
39393
|
+
unlock();
|
|
39394
|
+
process.off("SIGINT", quit);
|
|
39395
|
+
process.off("SIGTERM", quit);
|
|
39396
|
+
}
|
|
39397
|
+
})
|
|
39398
|
+
);
|
|
39399
|
+
pool.command("status").option("--json", "JSON out").description("the pool's sessions on this host").action(async (o) => {
|
|
39400
|
+
const s = loadPool();
|
|
39401
|
+
const live = new Set(await tmuxMux().list());
|
|
39402
|
+
const st = claudeSessionState();
|
|
39403
|
+
const rows3 = s.slots.map((x) => ({
|
|
39404
|
+
...x,
|
|
39405
|
+
alive: live.has(x.name),
|
|
39406
|
+
status: st(x.session)?.status ?? null,
|
|
39407
|
+
waitingFor: st(x.session)?.waitingFor ?? null
|
|
39408
|
+
}));
|
|
39409
|
+
print2(
|
|
39410
|
+
o.json ? rows3 : rows3.length ? `${rows3.map((r) => `${r.name} ${r.lane} ${r.alive ? r.waitingFor ? `waits: ${r.waitingFor}` : r.status ?? "starting" : "gone"}`).join("\n")}
|
|
39411
|
+
` : "No pool sessions.\n"
|
|
39412
|
+
);
|
|
39413
|
+
});
|
|
39042
39414
|
program3.command("events").description("the event log across tasks").command("search").argument("<words>", 'web-search words: "a phrase", -not, or').option("--task <id>", "one task").option("--kind <kind>", "one kind, e.g. run.tool or routed").option("--limit <n>", "at most n", "50").option("--json", "JSON out").action(
|
|
39043
39415
|
(words, o) => ctx.withStore(async (a) => {
|
|
39044
39416
|
const rs = await a.searchEvents(words, {
|
|
@@ -39066,14 +39438,14 @@ init_core();
|
|
|
39066
39438
|
import { spawnSync } from "node:child_process";
|
|
39067
39439
|
import { createHash } from "node:crypto";
|
|
39068
39440
|
import {
|
|
39069
|
-
existsSync as
|
|
39070
|
-
mkdirSync,
|
|
39071
|
-
readFileSync as
|
|
39441
|
+
existsSync as existsSync4,
|
|
39442
|
+
mkdirSync as mkdirSync2,
|
|
39443
|
+
readFileSync as readFileSync4,
|
|
39072
39444
|
renameSync,
|
|
39073
|
-
unlinkSync,
|
|
39074
|
-
writeFileSync
|
|
39445
|
+
unlinkSync as unlinkSync2,
|
|
39446
|
+
writeFileSync as writeFileSync2
|
|
39075
39447
|
} from "node:fs";
|
|
39076
|
-
import { basename, dirname, join as
|
|
39448
|
+
import { basename, dirname as dirname2, join as join5 } from "node:path";
|
|
39077
39449
|
init_core();
|
|
39078
39450
|
init_store();
|
|
39079
39451
|
var sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
@@ -39099,8 +39471,8 @@ async function mirror(a, root, o = {}) {
|
|
|
39099
39471
|
for (const t of tasks) {
|
|
39100
39472
|
const was = state.get(t.id);
|
|
39101
39473
|
const found = disk.get(t.id);
|
|
39102
|
-
const cur = was &&
|
|
39103
|
-
const curText = cur &&
|
|
39474
|
+
const cur = was && existsSync4(join5(root, was.path)) ? was.path : found?.path ?? was?.path;
|
|
39475
|
+
const curText = cur && existsSync4(join5(root, cur)) ? readFileSync4(join5(root, cur), "utf8") : void 0;
|
|
39104
39476
|
if (!o.adopt) {
|
|
39105
39477
|
const fileMoved = was !== void 0 && curText !== void 0 && (sha(curText) !== was.hash || cur !== was.path);
|
|
39106
39478
|
if (fileMoved && was && cur && t.version === was.version) {
|
|
@@ -39138,11 +39510,11 @@ async function mirror(a, root, o = {}) {
|
|
|
39138
39510
|
}
|
|
39139
39511
|
const text3 = renderTask(strip(t));
|
|
39140
39512
|
if (!o.dryRun) {
|
|
39141
|
-
|
|
39142
|
-
const tmp =
|
|
39143
|
-
|
|
39144
|
-
renameSync(tmp,
|
|
39145
|
-
if (cur && cur !== target &&
|
|
39513
|
+
mkdirSync2(dirname2(join5(root, target)), { recursive: true });
|
|
39514
|
+
const tmp = join5(root, `${target}.aerie-tmp`);
|
|
39515
|
+
writeFileSync2(tmp, text3);
|
|
39516
|
+
renameSync(tmp, join5(root, target));
|
|
39517
|
+
if (cur && cur !== target && existsSync4(join5(root, cur))) unlinkSync2(join5(root, cur));
|
|
39146
39518
|
await a.setMirrorFile(t.id, target, sha(text3), t.version);
|
|
39147
39519
|
}
|
|
39148
39520
|
if (cur && cur !== target) {
|
|
@@ -39159,7 +39531,7 @@ async function mirror(a, root, o = {}) {
|
|
|
39159
39531
|
return r;
|
|
39160
39532
|
}
|
|
39161
39533
|
function readFile2(path2, text3) {
|
|
39162
|
-
const place =
|
|
39534
|
+
const place = dirname2(path2);
|
|
39163
39535
|
if (!ALL.includes(place)) return null;
|
|
39164
39536
|
try {
|
|
39165
39537
|
return { task: parseTask(text3), place };
|
|
@@ -39600,7 +39972,15 @@ function registerCommands(program3, ctx) {
|
|
|
39600
39972
|
})
|
|
39601
39973
|
);
|
|
39602
39974
|
task2.command("log").argument("<id>").description("every write to a task: who, when, why").action((id) => ctx.withStore(async (a) => void print3(await a.events(id))));
|
|
39603
|
-
const SETTINGS = [
|
|
39975
|
+
const SETTINGS = [
|
|
39976
|
+
"router_model",
|
|
39977
|
+
"intake_model",
|
|
39978
|
+
"intake_lane",
|
|
39979
|
+
"pool_lanes",
|
|
39980
|
+
"pool_command",
|
|
39981
|
+
"pool_idle_min",
|
|
39982
|
+
"run_cap"
|
|
39983
|
+
];
|
|
39604
39984
|
const config2 = program3.command("config").description(`board settings: statuses, task_prefix, ${SETTINGS.join(", ")}`);
|
|
39605
39985
|
config2.command("get").argument("<key>").action(
|
|
39606
39986
|
(key) => key === "statuses" ? print3(`${STATUSES.join(", ")}
|
|
@@ -39723,10 +40103,10 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39723
40103
|
program3.command("decide").argument("<id>", "decision id").argument("<answer>", "an option key, or your own words").description(
|
|
39724
40104
|
"answer a decision; each blocked task goes back to To Do with the answer in its notes"
|
|
39725
40105
|
).option("--why <text>", "why, kept with the answer and in the tasks' notes").option("--json", "JSON out").action(
|
|
39726
|
-
(id, answer, o) => withBoardOrStore(async (a, c) => {
|
|
40106
|
+
(id, answer, o) => withBoardOrStore(async (a, c, atomic2) => {
|
|
39727
40107
|
const d = await a.decide(Number.parseInt(id, 10), answer, ctx.by, o.why);
|
|
39728
40108
|
const spec = c && await a.getSetting("router_model");
|
|
39729
|
-
const moved = c ? await afterDecision(a, c, d, ctx.by, spec ? modelPicker(spec) : void 0) : [];
|
|
40109
|
+
const moved = c ? await afterDecision(a, c, d, ctx.by, spec ? modelPicker(spec) : void 0, atomic2) : [];
|
|
39730
40110
|
for (const b of d.blocks) await after(a, b);
|
|
39731
40111
|
if (o.json) return void print3(`${JSON.stringify({ ...d, requeued: moved })}
|
|
39732
40112
|
`);
|
|
@@ -39804,6 +40184,9 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39804
40184
|
registerIntakeCommands(program3, ctx);
|
|
39805
40185
|
}
|
|
39806
40186
|
|
|
40187
|
+
// src/bin.ts
|
|
40188
|
+
init_db2();
|
|
40189
|
+
|
|
39807
40190
|
// src/migrations.ts
|
|
39808
40191
|
init_drizzle_orm();
|
|
39809
40192
|
import { fileURLToPath } from "node:url";
|
|
@@ -39907,12 +40290,12 @@ async function open2(migrateNow) {
|
|
|
39907
40290
|
}
|
|
39908
40291
|
const { PGlite: PGlite2 } = await import("@electric-sql/pglite");
|
|
39909
40292
|
const { drizzle: drizzle3 } = await Promise.resolve().then(() => (init_pglite(), pglite_exports));
|
|
39910
|
-
const pg = new PGlite2(cfg.dir ??
|
|
40293
|
+
const pg = new PGlite2(cfg.dir ?? join7(homedir4(), ".aerie"));
|
|
39911
40294
|
const db = drizzle3(pg);
|
|
39912
40295
|
await migrateAerie(db);
|
|
39913
40296
|
return { db, close: () => pg.close() };
|
|
39914
40297
|
}
|
|
39915
|
-
var pkg = JSON.parse(
|
|
40298
|
+
var pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
39916
40299
|
var program2 = new Command("aerie").description("One task system for every agent: areas, roles, a board; Backlog.md's flags").showHelpAfterError();
|
|
39917
40300
|
registerCommands(program2, {
|
|
39918
40301
|
by: cfg.by ?? `@${userInfo().username}`,
|
|
@@ -39934,14 +40317,17 @@ registerCommands(program2, {
|
|
|
39934
40317
|
async withBoard(fn) {
|
|
39935
40318
|
const { db, close } = await open2(false);
|
|
39936
40319
|
const { pgCoord: pgCoord2 } = await Promise.resolve().then(() => (init_postgres(), postgres_exports));
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
db,
|
|
40320
|
+
const pair = (q) => [
|
|
40321
|
+
aerieStore({ db: q, ...cfg.prefix ? { prefix: cfg.prefix } : {} }),
|
|
40322
|
+
pgCoord2({
|
|
40323
|
+
db: q,
|
|
39941
40324
|
ns: cfg.coordNs ?? "main",
|
|
39942
40325
|
status: { doing: "In Progress", done: "Done", ready: "To Do", queued: "To Do" }
|
|
39943
|
-
})
|
|
39944
|
-
|
|
40326
|
+
})
|
|
40327
|
+
];
|
|
40328
|
+
try {
|
|
40329
|
+
const [store, coord] = pair(db);
|
|
40330
|
+
return await fn(store, coord, (f) => atomic(db, (tx) => f(...pair(tx))));
|
|
39945
40331
|
} finally {
|
|
39946
40332
|
await close();
|
|
39947
40333
|
}
|