@wrenautomation/aerie 0.6.1 → 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 +491 -121
- package/dist/bin.js.map +4 -4
- package/dist/cli-intake.js +3 -2
- 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 +2 -1
- package/dist/intake/index.js +1 -1
- 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/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
|
}
|
|
@@ -38097,12 +38133,12 @@ async function hear(file2, o = {}) {
|
|
|
38097
38133
|
const { hearSpeech, speechHere } = await import("@wrenautomation/read/video");
|
|
38098
38134
|
const { mkdtemp: mkdtemp2, rm } = await import("node:fs/promises");
|
|
38099
38135
|
const { tmpdir: tmpdir2 } = await import("node:os");
|
|
38100
|
-
const { join:
|
|
38136
|
+
const { join: join8 } = await import("node:path");
|
|
38101
38137
|
const cohere = (process.env.COHERE_API_KEY ?? "").split(",").filter(Boolean);
|
|
38102
38138
|
const ear = speechHere(cohere);
|
|
38103
38139
|
if (!ear)
|
|
38104
38140
|
throw new AerieError("no ear: install whisper.cpp and its model, or set COHERE_API_KEY");
|
|
38105
|
-
const dir = await mkdtemp2(
|
|
38141
|
+
const dir = await mkdtemp2(join8(tmpdir2(), "aerie-hear-"));
|
|
38106
38142
|
const t0 = performance.now();
|
|
38107
38143
|
try {
|
|
38108
38144
|
const text3 = await hearSpeech(file2, dir, o.ffmpeg ?? "ffmpeg", ear, {
|
|
@@ -38280,7 +38316,7 @@ async function intake(a, input2, by, o = {}) {
|
|
|
38280
38316
|
});
|
|
38281
38317
|
else
|
|
38282
38318
|
try {
|
|
38283
|
-
const r = await route(a, o.coord, id, by, o.pick);
|
|
38319
|
+
const r = await route(a, o.coord, id, by, o.pick, o.atomic);
|
|
38284
38320
|
res.routed.push({ id, runner: r.runner, lane: r.lane });
|
|
38285
38321
|
} catch (e) {
|
|
38286
38322
|
res.routed.push({ id, runner: null, lane: null, error: e.message });
|
|
@@ -38319,12 +38355,12 @@ function plain(r) {
|
|
|
38319
38355
|
`;
|
|
38320
38356
|
}
|
|
38321
38357
|
function registerIntakeCommands(program3, ctx) {
|
|
38322
|
-
const either = (fn) => ctx.withBoard ? ctx.withBoard(
|
|
38358
|
+
const either = (fn) => ctx.withBoard ? ctx.withBoard(fn) : ctx.withStore((a) => fn(a, null));
|
|
38323
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(
|
|
38324
38360
|
"--split <file>",
|
|
38325
38361
|
"your own split as JSON ({tasks: [{title, span, why, area?}]}); - = stdin"
|
|
38326
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(
|
|
38327
|
-
(words, o) => either(async (a, c) => {
|
|
38363
|
+
(words, o) => either(async (a, c, atomic2) => {
|
|
38328
38364
|
const text3 = o.file ? await readFile(o.file, "utf8") : words.join(" ");
|
|
38329
38365
|
const split = o.split === void 0 ? void 0 : JSON.parse(o.split === "-" ? await stdin() : await readFile(o.split, "utf8"));
|
|
38330
38366
|
const spec = await a.getSetting("router_model");
|
|
@@ -38346,6 +38382,7 @@ function registerIntakeCommands(program3, ctx) {
|
|
|
38346
38382
|
model: await a.getSetting("intake_model") || void 0,
|
|
38347
38383
|
lane: await a.getSetting("intake_lane") || "main",
|
|
38348
38384
|
coord: c ?? void 0,
|
|
38385
|
+
atomic: atomic2,
|
|
38349
38386
|
pick: spec ? modelPicker(spec) : void 0
|
|
38350
38387
|
}
|
|
38351
38388
|
);
|
|
@@ -38407,11 +38444,181 @@ function aerieEnv(env = process.env) {
|
|
|
38407
38444
|
return (k) => env[k] ?? fromFile[k];
|
|
38408
38445
|
}
|
|
38409
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
|
+
|
|
38410
38611
|
// src/run/spawn.ts
|
|
38411
38612
|
init_db2();
|
|
38412
38613
|
import { spawn } from "node:child_process";
|
|
38413
38614
|
import { createInterface } from "node:readline";
|
|
38414
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
|
+
}
|
|
38415
38622
|
function runLines(bin, args, o) {
|
|
38416
38623
|
return new Promise((resolve, reject) => {
|
|
38417
38624
|
if (o.signal.aborted) {
|
|
@@ -38420,7 +38627,7 @@ function runLines(bin, args, o) {
|
|
|
38420
38627
|
}
|
|
38421
38628
|
const child = spawn(bin, args, {
|
|
38422
38629
|
cwd: o.cwd,
|
|
38423
|
-
env: { ...process.env, ...o.env },
|
|
38630
|
+
env: o.inherit === false ? { ...cleanEnv(), ...o.env } : { ...process.env, ...o.env },
|
|
38424
38631
|
detached: true,
|
|
38425
38632
|
stdio: ["ignore", "pipe", "pipe"]
|
|
38426
38633
|
});
|
|
@@ -38533,6 +38740,7 @@ function claudeEvent(l) {
|
|
|
38533
38740
|
function claudeRunner(o = {}) {
|
|
38534
38741
|
return {
|
|
38535
38742
|
name: "claude",
|
|
38743
|
+
needs: ["ANTHROPIC_API_KEY"],
|
|
38536
38744
|
async run(t, ctx) {
|
|
38537
38745
|
let last = null;
|
|
38538
38746
|
let session;
|
|
@@ -38583,6 +38791,7 @@ function lastText(v) {
|
|
|
38583
38791
|
function codexRunner(o = {}) {
|
|
38584
38792
|
return {
|
|
38585
38793
|
name: "codex",
|
|
38794
|
+
needs: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
|
38586
38795
|
async run(t, ctx) {
|
|
38587
38796
|
let summary = null;
|
|
38588
38797
|
const r = await runLines(
|
|
@@ -38611,6 +38820,7 @@ function codexRunner(o = {}) {
|
|
|
38611
38820
|
function grokRunner(o = {}) {
|
|
38612
38821
|
return {
|
|
38613
38822
|
name: "grok",
|
|
38823
|
+
needs: ["XAI_API_KEY"],
|
|
38614
38824
|
async run(t, ctx) {
|
|
38615
38825
|
const out = [];
|
|
38616
38826
|
const r = await runLines(
|
|
@@ -38638,6 +38848,7 @@ var DO_LINE = /^(done|needs-human|failed|planned) via (\S+)(?: ([^:]+))?: (.*)$/
|
|
|
38638
38848
|
function autobrowseRunner(o = {}) {
|
|
38639
38849
|
return {
|
|
38640
38850
|
name: "autobrowse-do",
|
|
38851
|
+
needs: ["AUTOBROWSE_OWNER"],
|
|
38641
38852
|
async run(t, ctx) {
|
|
38642
38853
|
const input2 = t.extra.input ?? {};
|
|
38643
38854
|
const out = [];
|
|
@@ -38691,7 +38902,23 @@ function apiRunner(o) {
|
|
|
38691
38902
|
summary: "no model for the api runner",
|
|
38692
38903
|
question: `No model for ${t.id} on the api runner: set AERIE_API_MODEL, or a route rule with a model?`
|
|
38693
38904
|
};
|
|
38694
|
-
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
|
+
}
|
|
38695
38922
|
await ctx.emit({ kind: "init", text: m.id, data: { spec, model: m.id } });
|
|
38696
38923
|
try {
|
|
38697
38924
|
const { value, usage } = await withDeadline(
|
|
@@ -38724,7 +38951,7 @@ You have no tools. Answer as JSON: outcome (done, needs-input or failed), summar
|
|
|
38724
38951
|
};
|
|
38725
38952
|
}
|
|
38726
38953
|
function spawnOf(ctx) {
|
|
38727
|
-
return { cwd: ctx.cwd, env: ctx.env, signal: ctx.signal };
|
|
38954
|
+
return { cwd: ctx.cwd, env: ctx.env, inherit: ctx.inherit, signal: ctx.signal };
|
|
38728
38955
|
}
|
|
38729
38956
|
function headlessRunners(get2) {
|
|
38730
38957
|
const opt = (name) => {
|
|
@@ -38751,9 +38978,22 @@ function headlessRunners(get2) {
|
|
|
38751
38978
|
// src/run/worker.ts
|
|
38752
38979
|
import { mkdtemp } from "node:fs/promises";
|
|
38753
38980
|
import { hostname as hostname3, tmpdir } from "node:os";
|
|
38754
|
-
import { join as
|
|
38981
|
+
import { join as join4 } from "node:path";
|
|
38755
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
|
+
}
|
|
38756
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
|
+
}
|
|
38757
38997
|
var holderOf = (r) => `${hostname3()}/aerie-${r}-${process.pid}`;
|
|
38758
38998
|
async function workOnce(o) {
|
|
38759
38999
|
const { store, coord, runner } = o;
|
|
@@ -38762,7 +39002,11 @@ async function workOnce(o) {
|
|
|
38762
39002
|
const leaseMs = o.leaseMs ?? LEASE_MS;
|
|
38763
39003
|
const by = `runner:${runner.name}`;
|
|
38764
39004
|
const lane = runLane(runner.name);
|
|
38765
|
-
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
|
+
);
|
|
38766
39010
|
if (!took) return null;
|
|
38767
39011
|
const { id, gen } = took;
|
|
38768
39012
|
const stop = new AbortController();
|
|
@@ -38790,7 +39034,7 @@ async function workOnce(o) {
|
|
|
38790
39034
|
},
|
|
38791
39035
|
Math.max(1, Math.floor(leaseMs / 3))
|
|
38792
39036
|
);
|
|
38793
|
-
const
|
|
39037
|
+
const write2 = async (kind, body, why) => {
|
|
38794
39038
|
if (await fenced()) await store.log(id, by, kind, body, why);
|
|
38795
39039
|
};
|
|
38796
39040
|
let res;
|
|
@@ -38798,8 +39042,8 @@ async function workOnce(o) {
|
|
|
38798
39042
|
const t = await store.get(id);
|
|
38799
39043
|
const routed = await store.lastEvent(id, "routed");
|
|
38800
39044
|
const model = routed?.body?.model ?? null;
|
|
38801
|
-
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(
|
|
38802
|
-
await
|
|
39045
|
+
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(join4(tmpdir(), `aerie-run-${id}-`));
|
|
39046
|
+
await write2(
|
|
38803
39047
|
"run.start",
|
|
38804
39048
|
{ runner: runner.name, model, holder, gen, cwd },
|
|
38805
39049
|
routed?.why ?? void 0
|
|
@@ -38808,16 +39052,31 @@ async function workOnce(o) {
|
|
|
38808
39052
|
const opt = d2.options.find((x) => x.key === d2.answer);
|
|
38809
39053
|
return `${d2.question} \u2192 ${opt ? opt.text : d2.answer} (${d2.answeredBy}${d2.why ? `: ${d2.why}` : ""})`;
|
|
38810
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;
|
|
38811
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
|
+
};
|
|
38812
39070
|
else
|
|
38813
39071
|
try {
|
|
38814
39072
|
res = await runner.run(t, {
|
|
38815
39073
|
model,
|
|
38816
39074
|
answers,
|
|
38817
39075
|
cwd,
|
|
38818
|
-
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,
|
|
38819
39078
|
signal: stop.signal,
|
|
38820
|
-
emit: (e) =>
|
|
39079
|
+
emit: (e) => write2(`run.${e.kind}`, { text: e.text, data: e.data })
|
|
38821
39080
|
});
|
|
38822
39081
|
} catch (e) {
|
|
38823
39082
|
res = { outcome: "failed", summary: e.message, data: { error: String(e) } };
|
|
@@ -38827,11 +39086,14 @@ async function workOnce(o) {
|
|
|
38827
39086
|
o.signal?.removeEventListener("abort", onOuter);
|
|
38828
39087
|
}
|
|
38829
39088
|
if (lost || !await fenced()) return { id, outcome: "lost" };
|
|
38830
|
-
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
|
+
}
|
|
38831
39093
|
const open3 = (await store.decisions({ open: true })).find((d2) => d2.blocks.includes(id));
|
|
38832
39094
|
const outcome = open3 ? "needs-input" : res.outcome;
|
|
38833
39095
|
const { summary } = res;
|
|
38834
|
-
await
|
|
39096
|
+
await write2(
|
|
38835
39097
|
"run.result",
|
|
38836
39098
|
{
|
|
38837
39099
|
outcome,
|
|
@@ -38851,8 +39113,8 @@ async function workOnce(o) {
|
|
|
38851
39113
|
return { id, outcome, summary };
|
|
38852
39114
|
}
|
|
38853
39115
|
if (outcome === "failed") {
|
|
38854
|
-
const
|
|
38855
|
-
const fails =
|
|
39116
|
+
const n = await failsSince(store, id, (await store.lastEvent(id, "routed"))?.seq ?? 0);
|
|
39117
|
+
const fails = n.failed + n.died;
|
|
38856
39118
|
await coord.release(id, holder);
|
|
38857
39119
|
if (!coord.ownsStatus) await store.edit(id, { status: "To Do" }, by);
|
|
38858
39120
|
if (fails < MAX_FAILS) {
|
|
@@ -38956,7 +39218,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38956
39218
|
"route a task: the first matching rule, else the router model; then queue it on its lane"
|
|
38957
39219
|
).option("--dry-run", "show the route, write nothing").option("--json", "JSON out").action((id, o) => {
|
|
38958
39220
|
if (!id) return void rt.help();
|
|
38959
|
-
return board(async (a, c) => {
|
|
39221
|
+
return board(async (a, c, atomic2) => {
|
|
38960
39222
|
const pick2 = await pickerOf(a);
|
|
38961
39223
|
if (o.dryRun) {
|
|
38962
39224
|
const t = await a.get(id);
|
|
@@ -38967,7 +39229,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38967
39229
|
`
|
|
38968
39230
|
);
|
|
38969
39231
|
}
|
|
38970
|
-
const r = await route(a, c, id, ctx.by, pick2);
|
|
39232
|
+
const r = await route(a, c, id, ctx.by, pick2, atomic2);
|
|
38971
39233
|
print2(
|
|
38972
39234
|
o.json ? r : `${id.toUpperCase()} \u2192 ${r.runner}${r.model ? ` (${r.model})` : ""} on ${r.lane}: ${r.why}
|
|
38973
39235
|
`
|
|
@@ -39018,7 +39280,10 @@ function registerRunCommands(program3, ctx) {
|
|
|
39018
39280
|
program3.command("work").requiredOption(
|
|
39019
39281
|
"--runner <name>",
|
|
39020
39282
|
"a headless runner: claude | codex | grok | api | autobrowse-do"
|
|
39021
|
-
).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(
|
|
39022
39287
|
(o) => board(async (a, c) => {
|
|
39023
39288
|
const name = runnerOf(o.runner);
|
|
39024
39289
|
if (ctx.local && name !== "api")
|
|
@@ -39027,6 +39292,12 @@ function registerRunCommands(program3, ctx) {
|
|
|
39027
39292
|
);
|
|
39028
39293
|
const runner = headlessRunners(aerieEnv()).get(name);
|
|
39029
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
|
+
);
|
|
39030
39301
|
const stop = new AbortController();
|
|
39031
39302
|
const quit = () => stop.abort();
|
|
39032
39303
|
process.once("SIGINT", quit);
|
|
@@ -39041,13 +39312,15 @@ function registerRunCommands(program3, ctx) {
|
|
|
39041
39312
|
idleMs: Number(o.idle),
|
|
39042
39313
|
once: !!o.once,
|
|
39043
39314
|
signal: stop.signal,
|
|
39315
|
+
...cap2 === void 0 ? {} : { cap: cap2 },
|
|
39044
39316
|
onOutcome: (w) => print2(
|
|
39045
39317
|
o.json ? `${JSON.stringify(w)}
|
|
39046
39318
|
` : `${w.id} ${w.outcome}${"summary" in w ? `: ${w.summary.split("\n")[0]}` : ""}
|
|
39047
39319
|
`
|
|
39048
39320
|
)
|
|
39049
39321
|
});
|
|
39050
|
-
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)
|
|
39051
39324
|
`);
|
|
39052
39325
|
} finally {
|
|
39053
39326
|
process.off("SIGINT", quit);
|
|
@@ -39055,6 +39328,89 @@ function registerRunCommands(program3, ctx) {
|
|
|
39055
39328
|
}
|
|
39056
39329
|
})
|
|
39057
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
|
+
});
|
|
39058
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(
|
|
39059
39415
|
(words, o) => ctx.withStore(async (a) => {
|
|
39060
39416
|
const rs = await a.searchEvents(words, {
|
|
@@ -39082,14 +39438,14 @@ init_core();
|
|
|
39082
39438
|
import { spawnSync } from "node:child_process";
|
|
39083
39439
|
import { createHash } from "node:crypto";
|
|
39084
39440
|
import {
|
|
39085
|
-
existsSync as
|
|
39086
|
-
mkdirSync,
|
|
39087
|
-
readFileSync as
|
|
39441
|
+
existsSync as existsSync4,
|
|
39442
|
+
mkdirSync as mkdirSync2,
|
|
39443
|
+
readFileSync as readFileSync4,
|
|
39088
39444
|
renameSync,
|
|
39089
|
-
unlinkSync,
|
|
39090
|
-
writeFileSync
|
|
39445
|
+
unlinkSync as unlinkSync2,
|
|
39446
|
+
writeFileSync as writeFileSync2
|
|
39091
39447
|
} from "node:fs";
|
|
39092
|
-
import { basename, dirname, join as
|
|
39448
|
+
import { basename, dirname as dirname2, join as join5 } from "node:path";
|
|
39093
39449
|
init_core();
|
|
39094
39450
|
init_store();
|
|
39095
39451
|
var sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
@@ -39115,8 +39471,8 @@ async function mirror(a, root, o = {}) {
|
|
|
39115
39471
|
for (const t of tasks) {
|
|
39116
39472
|
const was = state.get(t.id);
|
|
39117
39473
|
const found = disk.get(t.id);
|
|
39118
|
-
const cur = was &&
|
|
39119
|
-
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;
|
|
39120
39476
|
if (!o.adopt) {
|
|
39121
39477
|
const fileMoved = was !== void 0 && curText !== void 0 && (sha(curText) !== was.hash || cur !== was.path);
|
|
39122
39478
|
if (fileMoved && was && cur && t.version === was.version) {
|
|
@@ -39154,11 +39510,11 @@ async function mirror(a, root, o = {}) {
|
|
|
39154
39510
|
}
|
|
39155
39511
|
const text3 = renderTask(strip(t));
|
|
39156
39512
|
if (!o.dryRun) {
|
|
39157
|
-
|
|
39158
|
-
const tmp =
|
|
39159
|
-
|
|
39160
|
-
renameSync(tmp,
|
|
39161
|
-
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));
|
|
39162
39518
|
await a.setMirrorFile(t.id, target, sha(text3), t.version);
|
|
39163
39519
|
}
|
|
39164
39520
|
if (cur && cur !== target) {
|
|
@@ -39175,7 +39531,7 @@ async function mirror(a, root, o = {}) {
|
|
|
39175
39531
|
return r;
|
|
39176
39532
|
}
|
|
39177
39533
|
function readFile2(path2, text3) {
|
|
39178
|
-
const place =
|
|
39534
|
+
const place = dirname2(path2);
|
|
39179
39535
|
if (!ALL.includes(place)) return null;
|
|
39180
39536
|
try {
|
|
39181
39537
|
return { task: parseTask(text3), place };
|
|
@@ -39616,7 +39972,15 @@ function registerCommands(program3, ctx) {
|
|
|
39616
39972
|
})
|
|
39617
39973
|
);
|
|
39618
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))));
|
|
39619
|
-
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
|
+
];
|
|
39620
39984
|
const config2 = program3.command("config").description(`board settings: statuses, task_prefix, ${SETTINGS.join(", ")}`);
|
|
39621
39985
|
config2.command("get").argument("<key>").action(
|
|
39622
39986
|
(key) => key === "statuses" ? print3(`${STATUSES.join(", ")}
|
|
@@ -39739,10 +40103,10 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39739
40103
|
program3.command("decide").argument("<id>", "decision id").argument("<answer>", "an option key, or your own words").description(
|
|
39740
40104
|
"answer a decision; each blocked task goes back to To Do with the answer in its notes"
|
|
39741
40105
|
).option("--why <text>", "why, kept with the answer and in the tasks' notes").option("--json", "JSON out").action(
|
|
39742
|
-
(id, answer, o) => withBoardOrStore(async (a, c) => {
|
|
40106
|
+
(id, answer, o) => withBoardOrStore(async (a, c, atomic2) => {
|
|
39743
40107
|
const d = await a.decide(Number.parseInt(id, 10), answer, ctx.by, o.why);
|
|
39744
40108
|
const spec = c && await a.getSetting("router_model");
|
|
39745
|
-
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) : [];
|
|
39746
40110
|
for (const b of d.blocks) await after(a, b);
|
|
39747
40111
|
if (o.json) return void print3(`${JSON.stringify({ ...d, requeued: moved })}
|
|
39748
40112
|
`);
|
|
@@ -39820,6 +40184,9 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39820
40184
|
registerIntakeCommands(program3, ctx);
|
|
39821
40185
|
}
|
|
39822
40186
|
|
|
40187
|
+
// src/bin.ts
|
|
40188
|
+
init_db2();
|
|
40189
|
+
|
|
39823
40190
|
// src/migrations.ts
|
|
39824
40191
|
init_drizzle_orm();
|
|
39825
40192
|
import { fileURLToPath } from "node:url";
|
|
@@ -39923,12 +40290,12 @@ async function open2(migrateNow) {
|
|
|
39923
40290
|
}
|
|
39924
40291
|
const { PGlite: PGlite2 } = await import("@electric-sql/pglite");
|
|
39925
40292
|
const { drizzle: drizzle3 } = await Promise.resolve().then(() => (init_pglite(), pglite_exports));
|
|
39926
|
-
const pg = new PGlite2(cfg.dir ??
|
|
40293
|
+
const pg = new PGlite2(cfg.dir ?? join7(homedir4(), ".aerie"));
|
|
39927
40294
|
const db = drizzle3(pg);
|
|
39928
40295
|
await migrateAerie(db);
|
|
39929
40296
|
return { db, close: () => pg.close() };
|
|
39930
40297
|
}
|
|
39931
|
-
var pkg = JSON.parse(
|
|
40298
|
+
var pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
39932
40299
|
var program2 = new Command("aerie").description("One task system for every agent: areas, roles, a board; Backlog.md's flags").showHelpAfterError();
|
|
39933
40300
|
registerCommands(program2, {
|
|
39934
40301
|
by: cfg.by ?? `@${userInfo().username}`,
|
|
@@ -39950,14 +40317,17 @@ registerCommands(program2, {
|
|
|
39950
40317
|
async withBoard(fn) {
|
|
39951
40318
|
const { db, close } = await open2(false);
|
|
39952
40319
|
const { pgCoord: pgCoord2 } = await Promise.resolve().then(() => (init_postgres(), postgres_exports));
|
|
39953
|
-
|
|
39954
|
-
|
|
39955
|
-
|
|
39956
|
-
db,
|
|
40320
|
+
const pair = (q) => [
|
|
40321
|
+
aerieStore({ db: q, ...cfg.prefix ? { prefix: cfg.prefix } : {} }),
|
|
40322
|
+
pgCoord2({
|
|
40323
|
+
db: q,
|
|
39957
40324
|
ns: cfg.coordNs ?? "main",
|
|
39958
40325
|
status: { doing: "In Progress", done: "Done", ready: "To Do", queued: "To Do" }
|
|
39959
|
-
})
|
|
39960
|
-
|
|
40326
|
+
})
|
|
40327
|
+
];
|
|
40328
|
+
try {
|
|
40329
|
+
const [store, coord] = pair(db);
|
|
40330
|
+
return await fn(store, coord, (f) => atomic(db, (tx) => f(...pair(tx))));
|
|
39961
40331
|
} finally {
|
|
39962
40332
|
await close();
|
|
39963
40333
|
}
|