@wrenautomation/aerie 0.6.1 → 0.7.1
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 +536 -129
- 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 +13 -0
- package/dist/coord/postgres.d.ts +4 -0
- package/dist/coord/postgres.js +57 -6
- 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 +98 -9
- 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/dist/schema.d.ts +17 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/drizzle/0010_coord_notify.sql +29 -0
- package/drizzle/meta/0010_snapshot.json +1781 -0
- package/drizzle/meta/_journal.json +7 -0
- 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
|
}
|
|
@@ -8801,7 +8801,9 @@ var init_schema2 = __esm({
|
|
|
8801
8801
|
/** taken from the queue: where release(queue) puts it back */
|
|
8802
8802
|
queued: boolean4().notNull().default(false),
|
|
8803
8803
|
lane: text2(),
|
|
8804
|
-
pin: text2()
|
|
8804
|
+
pin: text2(),
|
|
8805
|
+
/** the take's own key: a replayed take with it gets this lease back, not the next row */
|
|
8806
|
+
takeKey: text2("take_key")
|
|
8805
8807
|
},
|
|
8806
8808
|
(t) => [primaryKey({ columns: [t.ns, t.taskId, t.holder], name: "pk_coord_lease" })]
|
|
8807
8809
|
);
|
|
@@ -8951,7 +8953,7 @@ function aerieStore(o) {
|
|
|
8951
8953
|
return (r[0]?.v ?? o.prefix ?? "T").toUpperCase();
|
|
8952
8954
|
}
|
|
8953
8955
|
const rules = o.rules ?? WREN_RULES;
|
|
8954
|
-
async function
|
|
8956
|
+
async function write2(by, fn) {
|
|
8955
8957
|
const out = await atomic(db, async (tx) => {
|
|
8956
8958
|
await o.hooks?.onWrite?.(tx, by);
|
|
8957
8959
|
return fn(tx);
|
|
@@ -9111,7 +9113,7 @@ function aerieStore(o) {
|
|
|
9111
9113
|
return rows3.map(viewOf);
|
|
9112
9114
|
}
|
|
9113
9115
|
async function saveView(owner, v) {
|
|
9114
|
-
const id = await
|
|
9116
|
+
const id = await write2(owner, async (tx) => {
|
|
9115
9117
|
await ensureActors(tx, [owner]);
|
|
9116
9118
|
const set3 = {
|
|
9117
9119
|
filter: v.filter,
|
|
@@ -9126,7 +9128,7 @@ function aerieStore(o) {
|
|
|
9126
9128
|
return { id, ...v };
|
|
9127
9129
|
}
|
|
9128
9130
|
async function deleteView(owner, name) {
|
|
9129
|
-
const r = await
|
|
9131
|
+
const r = await write2(
|
|
9130
9132
|
owner,
|
|
9131
9133
|
(tx) => tx.delete(view).where(and(eq(view.owner, owner), eq(view.name, name))).returning({ id: view.id })
|
|
9132
9134
|
);
|
|
@@ -9164,7 +9166,7 @@ function aerieStore(o) {
|
|
|
9164
9166
|
throw new AerieError(`recommendation ${d.recommendation} is not an option`);
|
|
9165
9167
|
const blocks = [];
|
|
9166
9168
|
for (const b of d.blocks ?? []) blocks.push((await must(b, db)).id);
|
|
9167
|
-
const id = await
|
|
9169
|
+
const id = await write2(by, async (tx) => {
|
|
9168
9170
|
const [r] = await tx.insert(decision).values({
|
|
9169
9171
|
question: d.question,
|
|
9170
9172
|
options: d.options,
|
|
@@ -9187,7 +9189,7 @@ function aerieStore(o) {
|
|
|
9187
9189
|
const [d] = await decisions({ ids: [id] });
|
|
9188
9190
|
if (!d) throw new AerieError(`no decision ${id}`, 404);
|
|
9189
9191
|
if (d.answeredAt) throw new AerieError(`decision ${id} is answered (${d.answer})`, 409);
|
|
9190
|
-
await
|
|
9192
|
+
await write2(by, async (tx) => {
|
|
9191
9193
|
await tx.update(decision).set({ answer, answeredBy: by, answeredAt: now(), why: why ?? null }).where(and(eq(decision.id, id), isNull2(decision.answeredAt)));
|
|
9192
9194
|
});
|
|
9193
9195
|
const opt = d.options.find((x) => x.key === answer);
|
|
@@ -9217,7 +9219,7 @@ function aerieStore(o) {
|
|
|
9217
9219
|
recommendation: d.recommendation,
|
|
9218
9220
|
why: p.why
|
|
9219
9221
|
};
|
|
9220
|
-
await
|
|
9222
|
+
await write2(by, async (tx) => {
|
|
9221
9223
|
await tx.update(decision).set({
|
|
9222
9224
|
...p.brief !== void 0 ? { brief: p.brief } : {},
|
|
9223
9225
|
...p.recommendation !== void 0 ? { recommendation: p.recommendation } : {},
|
|
@@ -9248,7 +9250,7 @@ function aerieStore(o) {
|
|
|
9248
9250
|
...drop.flatMap((id) => all.find((x) => x.id === id)?.blocks ?? [])
|
|
9249
9251
|
])
|
|
9250
9252
|
];
|
|
9251
|
-
await
|
|
9253
|
+
await write2(by, async (tx) => {
|
|
9252
9254
|
await tx.update(decision).set({ blocks }).where(eq(decision.id, keep));
|
|
9253
9255
|
await tx.update(decision).set({
|
|
9254
9256
|
answer: "merged",
|
|
@@ -9386,7 +9388,7 @@ function aerieStore(o) {
|
|
|
9386
9388
|
return `${base}${max + 1}`;
|
|
9387
9389
|
}
|
|
9388
9390
|
async function create(input2, by, opts = {}) {
|
|
9389
|
-
const id = await
|
|
9391
|
+
const id = await write2(by, async (tx) => {
|
|
9390
9392
|
const parent = input2.parentId ? await must(input2.parentId, tx) : null;
|
|
9391
9393
|
if (input2.milestone)
|
|
9392
9394
|
input2 = { ...input2, milestone: await milestoneOf(tx, input2.milestone) };
|
|
@@ -9417,7 +9419,7 @@ function aerieStore(o) {
|
|
|
9417
9419
|
return must(id, db);
|
|
9418
9420
|
}
|
|
9419
9421
|
async function edit(id, e, by, opts = {}) {
|
|
9420
|
-
const done = await
|
|
9422
|
+
const done = await write2(by, async (tx) => {
|
|
9421
9423
|
const row = await lockRow(tx, id);
|
|
9422
9424
|
if (opts.expect !== void 0 && opts.expect !== row.version)
|
|
9423
9425
|
throw new AerieError(`stale: ${row.id} is at version ${row.version}`, 409);
|
|
@@ -9435,7 +9437,7 @@ function aerieStore(o) {
|
|
|
9435
9437
|
}
|
|
9436
9438
|
async function importTasks(tasks, by) {
|
|
9437
9439
|
const out = { added: [], changed: [], same: 0 };
|
|
9438
|
-
await
|
|
9440
|
+
await write2(by, async (tx) => {
|
|
9439
9441
|
const have = new Map((await load2(tx)).map((t) => [t.id, t]));
|
|
9440
9442
|
for (const { task: task2, place, expect } of tasks) {
|
|
9441
9443
|
if (expect !== void 0) {
|
|
@@ -9471,7 +9473,7 @@ function aerieStore(o) {
|
|
|
9471
9473
|
})).sort((a, b) => a.path < b.path ? -1 : 1);
|
|
9472
9474
|
}
|
|
9473
9475
|
async function addArea(path2, by, p = {}) {
|
|
9474
|
-
await
|
|
9476
|
+
await write2(by, async (tx) => {
|
|
9475
9477
|
const id = await ensureArea(tx, path2);
|
|
9476
9478
|
const set3 = {
|
|
9477
9479
|
...p.name ? { name: p.name } : {},
|
|
@@ -9488,13 +9490,13 @@ function aerieStore(o) {
|
|
|
9488
9490
|
return all.filter((a) => roots.some((r) => a.path === r || a.path.startsWith(`${r}/`))).map((a) => a.path);
|
|
9489
9491
|
}
|
|
9490
9492
|
async function grant(area2, actor2, role, by) {
|
|
9491
|
-
await
|
|
9493
|
+
await write2(by, async (tx) => {
|
|
9492
9494
|
await ensureActors(tx, [actor2]);
|
|
9493
9495
|
await tx.insert(areaRole).values({ areaId: await areaIdOf(tx, area2), actorId: actor2, role }).onConflictDoNothing();
|
|
9494
9496
|
});
|
|
9495
9497
|
}
|
|
9496
9498
|
async function revoke(area2, actor2, role, by) {
|
|
9497
|
-
await
|
|
9499
|
+
await write2(by, async (tx) => {
|
|
9498
9500
|
const areaId = await areaIdOf(tx, area2);
|
|
9499
9501
|
await tx.delete(areaRole).where(
|
|
9500
9502
|
and(
|
|
@@ -9528,7 +9530,7 @@ function aerieStore(o) {
|
|
|
9528
9530
|
return r[0]?.id ?? null;
|
|
9529
9531
|
}
|
|
9530
9532
|
async function setActor(id, p, by) {
|
|
9531
|
-
await
|
|
9533
|
+
await write2(by, async (tx) => {
|
|
9532
9534
|
await ensureActors(tx, [id]);
|
|
9533
9535
|
const set3 = {
|
|
9534
9536
|
...p.name ? { name: p.name } : {},
|
|
@@ -9547,7 +9549,7 @@ function aerieStore(o) {
|
|
|
9547
9549
|
return r[0]?.v ?? null;
|
|
9548
9550
|
}
|
|
9549
9551
|
async function setSetting(key, value, by, onlyIfUnset = false) {
|
|
9550
|
-
await
|
|
9552
|
+
await write2(by, async (tx) => {
|
|
9551
9553
|
const q = tx.insert(setting).values({ key, value });
|
|
9552
9554
|
await (onlyIfUnset ? q.onConflictDoNothing() : q.onConflictDoUpdate({ target: setting.key, set: { value } }));
|
|
9553
9555
|
});
|
|
@@ -9584,7 +9586,7 @@ function aerieStore(o) {
|
|
|
9584
9586
|
}
|
|
9585
9587
|
async function putMilestones(ms2, by) {
|
|
9586
9588
|
if (!ms2.length) return;
|
|
9587
|
-
await
|
|
9589
|
+
await write2(by, async (tx) => {
|
|
9588
9590
|
for (const m of ms2)
|
|
9589
9591
|
await tx.insert(milestone).values({ id: m.id, title: m.title, description: m.description ?? null }).onConflictDoUpdate({
|
|
9590
9592
|
target: milestone.id,
|
|
@@ -9610,7 +9612,7 @@ function aerieStore(o) {
|
|
|
9610
9612
|
}
|
|
9611
9613
|
async function addRouteRule(r, by) {
|
|
9612
9614
|
if (r.runner === "session" && !r.lane) throw new AerieError("a session rule needs a lane");
|
|
9613
|
-
return
|
|
9615
|
+
return write2(by, async (tx) => {
|
|
9614
9616
|
await ensureActors(tx, [by]);
|
|
9615
9617
|
const [last] = await tx.select({ rank: routeRule.rank }).from(routeRule).orderBy(sql`${routeRule.rank} desc`).limit(1);
|
|
9616
9618
|
const [row] = await tx.insert(routeRule).values({
|
|
@@ -9627,14 +9629,14 @@ function aerieStore(o) {
|
|
|
9627
9629
|
});
|
|
9628
9630
|
}
|
|
9629
9631
|
async function removeRouteRule(id, by) {
|
|
9630
|
-
return
|
|
9632
|
+
return write2(by, async (tx) => {
|
|
9631
9633
|
const r = await tx.delete(routeRule).where(eq(routeRule.id, id)).returning({ id: routeRule.id });
|
|
9632
9634
|
return r.length > 0;
|
|
9633
9635
|
});
|
|
9634
9636
|
}
|
|
9635
9637
|
async function log(taskId, by, kind, body, why) {
|
|
9636
9638
|
const t = await must(taskId, db);
|
|
9637
|
-
await
|
|
9639
|
+
await write2(by, async (tx) => {
|
|
9638
9640
|
await ensureActors(tx, [by]);
|
|
9639
9641
|
await event(tx, t.id, by, kind, body, why);
|
|
9640
9642
|
});
|
|
@@ -10262,6 +10264,7 @@ var init_pglite = __esm({
|
|
|
10262
10264
|
// src/coord/postgres.ts
|
|
10263
10265
|
var postgres_exports = {};
|
|
10264
10266
|
__export(postgres_exports, {
|
|
10267
|
+
COORD_CHANNEL: () => COORD_CHANNEL,
|
|
10265
10268
|
pgCoord: () => pgCoord
|
|
10266
10269
|
});
|
|
10267
10270
|
async function rows2(q, query) {
|
|
@@ -10296,7 +10299,7 @@ function pgCoord(o) {
|
|
|
10296
10299
|
if (!hit[0] || hit[0].status === name) return;
|
|
10297
10300
|
await aerieStore({ db: tx }).edit(id, { status: name }, by);
|
|
10298
10301
|
};
|
|
10299
|
-
const lease = async (tx, id, holder, leaseMs, queued) => {
|
|
10302
|
+
const lease = async (tx, id, holder, leaseMs, queued, key) => {
|
|
10300
10303
|
const t = clock();
|
|
10301
10304
|
const others = await rows2(
|
|
10302
10305
|
tx,
|
|
@@ -10309,9 +10312,10 @@ function pgCoord(o) {
|
|
|
10309
10312
|
);
|
|
10310
10313
|
const gen = num(g?.gen);
|
|
10311
10314
|
await tx.execute(sql`
|
|
10312
|
-
insert into ${L} (ns, task_id, holder, gen, expires, queued, lane, pin)
|
|
10313
|
-
values (${ns}, ${id}, ${holder}, ${gen}, ${plus(t, leaseMs)}, ${!!queued}, ${queued?.lane ?? null}, ${queued?.pin ?? null})
|
|
10315
|
+
insert into ${L} (ns, task_id, holder, gen, expires, queued, lane, pin, take_key)
|
|
10316
|
+
values (${ns}, ${id}, ${holder}, ${gen}, ${plus(t, leaseMs)}, ${!!queued}, ${queued?.lane ?? null}, ${queued?.pin ?? null}, ${key ?? null})
|
|
10314
10317
|
on conflict (ns, task_id, holder) do update set gen = excluded.gen, expires = excluded.expires,
|
|
10318
|
+
take_key = coalesce(excluded.take_key, ${L}.take_key),
|
|
10315
10319
|
queued = excluded.queued or ${L}.queued,
|
|
10316
10320
|
lane = case when excluded.queued then excluded.lane else ${L}.lane end,
|
|
10317
10321
|
pin = case when excluded.queued then excluded.pin else ${L}.pin end`);
|
|
@@ -10363,7 +10367,9 @@ function pgCoord(o) {
|
|
|
10363
10367
|
};
|
|
10364
10368
|
const queueRows = (q) => rows2(
|
|
10365
10369
|
q,
|
|
10366
|
-
sql`select task_id as id, lane, pin, pos from ${Q}
|
|
10370
|
+
sql`select q.task_id as id, q.lane, q.pin, q.pos, k.title from ${Q} q
|
|
10371
|
+
left join ${task} k on k.id = upper(q.task_id)
|
|
10372
|
+
where q.ns = ${ns} order by q.pos, q.task_id`
|
|
10367
10373
|
);
|
|
10368
10374
|
const store = {
|
|
10369
10375
|
name: "aerie",
|
|
@@ -10498,10 +10504,54 @@ function pgCoord(o) {
|
|
|
10498
10504
|
await tx.execute(sql`delete from ${Q} where ns = ${ns} and task_id = any(${textArr(ids)})`);
|
|
10499
10505
|
}),
|
|
10500
10506
|
queueList: async () => (await queueRows(db)).map(
|
|
10501
|
-
(i, pos) => ({
|
|
10507
|
+
(i, pos) => ({
|
|
10508
|
+
id: i.id,
|
|
10509
|
+
lane: i.lane,
|
|
10510
|
+
pin: i.pin,
|
|
10511
|
+
pos,
|
|
10512
|
+
...i.title ? { title: i.title } : {}
|
|
10513
|
+
})
|
|
10502
10514
|
),
|
|
10503
10515
|
queueTake: (x, holder, leaseMs) => atomic(db, async (tx) => {
|
|
10504
10516
|
await lockQueue(tx);
|
|
10517
|
+
if (x.key) {
|
|
10518
|
+
const [had] = await rows2(
|
|
10519
|
+
tx,
|
|
10520
|
+
sql`select task_id as id, gen::float8 as gen from ${L} where ns = ${ns} and holder = ${holder}
|
|
10521
|
+
and take_key = ${x.key} and expires >= ${clock()}`
|
|
10522
|
+
);
|
|
10523
|
+
if (had) return { id: had.id, gen: num(had.gen) };
|
|
10524
|
+
}
|
|
10525
|
+
if (x.pin?.startsWith("run:")) {
|
|
10526
|
+
const t = clock();
|
|
10527
|
+
const lapsed = await rows2(
|
|
10528
|
+
tx,
|
|
10529
|
+
sql`select task_id as id, holder, queued from ${L}
|
|
10530
|
+
where ns = ${ns} and pin = ${x.pin} and expires < ${t} order by task_id`
|
|
10531
|
+
);
|
|
10532
|
+
if (lapsed.length) {
|
|
10533
|
+
await lockTasks(
|
|
10534
|
+
tx,
|
|
10535
|
+
lapsed.map((l) => l.id)
|
|
10536
|
+
);
|
|
10537
|
+
for (const l of lapsed) {
|
|
10538
|
+
const still = await rows2(
|
|
10539
|
+
tx,
|
|
10540
|
+
sql`select 1 from ${L} where ns = ${ns} and task_id = ${l.id} and holder = ${l.holder}
|
|
10541
|
+
and expires < ${t}`
|
|
10542
|
+
);
|
|
10543
|
+
if (still.length) await drop(tx, l.id, [l.holder], { queue: l.queued, by: l.holder });
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10546
|
+
if (x.cap !== void 0) {
|
|
10547
|
+
const [n] = await rows2(
|
|
10548
|
+
tx,
|
|
10549
|
+
sql`select count(*)::int as n from ${L}
|
|
10550
|
+
where ns = ${ns} and pin = ${x.pin} and expires >= ${t}`
|
|
10551
|
+
);
|
|
10552
|
+
if (num(n?.n) >= x.cap) return null;
|
|
10553
|
+
}
|
|
10554
|
+
}
|
|
10505
10555
|
const all = x.lanes === "*";
|
|
10506
10556
|
const lanes = Array.isArray(x.lanes) ? x.lanes : [];
|
|
10507
10557
|
const [item] = await rows2(
|
|
@@ -10514,7 +10564,14 @@ function pgCoord(o) {
|
|
|
10514
10564
|
if (!item) return null;
|
|
10515
10565
|
await lockTasks(tx, [item.id]);
|
|
10516
10566
|
await tx.execute(sql`delete from ${Q} where ns = ${ns} and task_id = ${item.id}`);
|
|
10517
|
-
const r = await lease(
|
|
10567
|
+
const r = await lease(
|
|
10568
|
+
tx,
|
|
10569
|
+
item.id,
|
|
10570
|
+
holder,
|
|
10571
|
+
leaseMs,
|
|
10572
|
+
{ lane: item.lane, pin: item.pin },
|
|
10573
|
+
x.key
|
|
10574
|
+
);
|
|
10518
10575
|
return { id: item.id, gen: r.gen };
|
|
10519
10576
|
}),
|
|
10520
10577
|
fence: async (id, holder, gen) => (await rows2(
|
|
@@ -10526,9 +10583,18 @@ function pgCoord(o) {
|
|
|
10526
10583
|
await o.end?.();
|
|
10527
10584
|
}
|
|
10528
10585
|
};
|
|
10586
|
+
const sub = o.subscribe;
|
|
10587
|
+
if (sub)
|
|
10588
|
+
store.listen = async (onChange) => {
|
|
10589
|
+
const stop = await sub(COORD_CHANNEL, (payload) => {
|
|
10590
|
+
const [what, ...rest] = payload.split(":");
|
|
10591
|
+
if (rest.join(":") === ns && (what === "queue" || what === "lease")) onChange(what);
|
|
10592
|
+
});
|
|
10593
|
+
return { end: stop };
|
|
10594
|
+
};
|
|
10529
10595
|
return store;
|
|
10530
10596
|
}
|
|
10531
|
-
var ms, at2, num, textArr;
|
|
10597
|
+
var COORD_CHANNEL, ms, at2, num, textArr;
|
|
10532
10598
|
var init_postgres = __esm({
|
|
10533
10599
|
"src/coord/postgres.ts"() {
|
|
10534
10600
|
"use strict";
|
|
@@ -10536,6 +10602,7 @@ var init_postgres = __esm({
|
|
|
10536
10602
|
init_db2();
|
|
10537
10603
|
init_schema2();
|
|
10538
10604
|
init_store();
|
|
10605
|
+
COORD_CHANNEL = "aerie_coord";
|
|
10539
10606
|
ms = (v) => sql`(extract(epoch from ${v}) * 1000)::float8`;
|
|
10540
10607
|
at2 = (v) => sql`to_timestamp(${v}::float8 / 1000)`;
|
|
10541
10608
|
num = (v) => Math.round(Number(v));
|
|
@@ -10558,9 +10625,9 @@ __export(setup_exports, {
|
|
|
10558
10625
|
});
|
|
10559
10626
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
10560
10627
|
import { createHash as createHash2, createHmac, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
10561
|
-
import { chmodSync, existsSync as
|
|
10562
|
-
import { homedir as
|
|
10563
|
-
import { dirname as
|
|
10628
|
+
import { chmodSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
10629
|
+
import { homedir as homedir3 } from "node:os";
|
|
10630
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
10564
10631
|
function aerieUrl(adminUrl, role = ROLE) {
|
|
10565
10632
|
const url2 = new URL(adminUrl);
|
|
10566
10633
|
if (!url2.password) throw new Error("admin URL has no password; aerie derives its own from it");
|
|
@@ -10615,10 +10682,10 @@ function adminUrlFromSsm(param, key, region) {
|
|
|
10615
10682
|
throw new Error(`${key} not in ${param} (or its -2, -3 parts)`);
|
|
10616
10683
|
}
|
|
10617
10684
|
function writeSettings(set3, file2 = settingsFile()) {
|
|
10618
|
-
const lines =
|
|
10685
|
+
const lines = existsSync5(file2) ? readFileSync5(file2, "utf8").split("\n").filter(Boolean) : [];
|
|
10619
10686
|
const kept = lines.filter((l) => !Object.keys(set3).some((k) => l.startsWith(`${k}=`)));
|
|
10620
|
-
|
|
10621
|
-
|
|
10687
|
+
mkdirSync3(dirname3(file2), { recursive: true, mode: 448 });
|
|
10688
|
+
writeFileSync3(
|
|
10622
10689
|
file2,
|
|
10623
10690
|
`${[...kept, ...Object.entries(set3).map(([k, v]) => `${k}=${v}`)].join("\n")}
|
|
10624
10691
|
`,
|
|
@@ -10656,14 +10723,14 @@ var init_setup = __esm({
|
|
|
10656
10723
|
ROLE = "aerie";
|
|
10657
10724
|
SCHEMA = "aerie";
|
|
10658
10725
|
ATTRS = "LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS";
|
|
10659
|
-
settingsFile = () =>
|
|
10726
|
+
settingsFile = () => join6(homedir3(), ".config", "aerie", "env");
|
|
10660
10727
|
}
|
|
10661
10728
|
});
|
|
10662
10729
|
|
|
10663
10730
|
// src/bin.ts
|
|
10664
|
-
import { readFileSync as
|
|
10665
|
-
import { homedir as
|
|
10666
|
-
import { join as
|
|
10731
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
10732
|
+
import { homedir as homedir4, userInfo } from "node:os";
|
|
10733
|
+
import { join as join7 } from "node:path";
|
|
10667
10734
|
|
|
10668
10735
|
// node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
|
|
10669
10736
|
var CommanderError = class extends Error {
|
|
@@ -11840,7 +11907,7 @@ var Command = class _Command extends EventEmitter {
|
|
|
11840
11907
|
this._outputConfiguration = {
|
|
11841
11908
|
writeOut: (str2) => process2.stdout.write(str2),
|
|
11842
11909
|
writeErr: (str2) => process2.stderr.write(str2),
|
|
11843
|
-
outputError: (str2,
|
|
11910
|
+
outputError: (str2, write2) => write2(str2),
|
|
11844
11911
|
getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : void 0,
|
|
11845
11912
|
getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : void 0,
|
|
11846
11913
|
getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
|
|
@@ -13825,11 +13892,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
13825
13892
|
hasColors = this._outputConfiguration.getOutHasColors();
|
|
13826
13893
|
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
13827
13894
|
}
|
|
13828
|
-
const
|
|
13895
|
+
const write2 = (str2) => {
|
|
13829
13896
|
if (!hasColors) str2 = this._outputConfiguration.stripColor(str2);
|
|
13830
13897
|
return baseWrite(str2);
|
|
13831
13898
|
};
|
|
13832
|
-
return { error: error62, write, hasColors, helpWidth };
|
|
13899
|
+
return { error: error62, write: write2, hasColors, helpWidth };
|
|
13833
13900
|
}
|
|
13834
13901
|
/**
|
|
13835
13902
|
* Output help information for this command.
|
|
@@ -37652,7 +37719,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37652
37719
|
return h ? h(node2, rewritten) : node2;
|
|
37653
37720
|
};
|
|
37654
37721
|
const cache = /* @__PURE__ */ new Map();
|
|
37655
|
-
function
|
|
37722
|
+
function run2(s) {
|
|
37656
37723
|
const cached2 = cache.get(s);
|
|
37657
37724
|
if (cached2 === RESOLVING) {
|
|
37658
37725
|
return new $ZodLazy({
|
|
@@ -37678,21 +37745,21 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37678
37745
|
let changed = false;
|
|
37679
37746
|
const newShape = {};
|
|
37680
37747
|
for (const k of keys) {
|
|
37681
|
-
const mapped =
|
|
37748
|
+
const mapped = run2(oldShape[k]);
|
|
37682
37749
|
if (mapped !== oldShape[k])
|
|
37683
37750
|
changed = true;
|
|
37684
37751
|
newShape[k] = mapped;
|
|
37685
37752
|
}
|
|
37686
37753
|
let newCatchall = def.catchall;
|
|
37687
37754
|
if (def.catchall) {
|
|
37688
|
-
newCatchall =
|
|
37755
|
+
newCatchall = run2(def.catchall);
|
|
37689
37756
|
if (newCatchall !== def.catchall)
|
|
37690
37757
|
changed = true;
|
|
37691
37758
|
}
|
|
37692
37759
|
return changed ? clone(s, { ...def, shape: newShape, catchall: newCatchall }) : s;
|
|
37693
37760
|
}
|
|
37694
37761
|
case "array": {
|
|
37695
|
-
const mapped =
|
|
37762
|
+
const mapped = run2(def.element);
|
|
37696
37763
|
return mapped === def.element ? s : clone(s, { ...def, element: mapped });
|
|
37697
37764
|
}
|
|
37698
37765
|
case "tuple": {
|
|
@@ -37700,14 +37767,14 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37700
37767
|
let changed = false;
|
|
37701
37768
|
const newItems = [];
|
|
37702
37769
|
for (const item of oldItems) {
|
|
37703
|
-
const mapped =
|
|
37770
|
+
const mapped = run2(item);
|
|
37704
37771
|
if (mapped !== item)
|
|
37705
37772
|
changed = true;
|
|
37706
37773
|
newItems.push(mapped);
|
|
37707
37774
|
}
|
|
37708
37775
|
let newRest = def.rest;
|
|
37709
37776
|
if (def.rest) {
|
|
37710
|
-
newRest =
|
|
37777
|
+
newRest = run2(def.rest);
|
|
37711
37778
|
if (newRest !== def.rest)
|
|
37712
37779
|
changed = true;
|
|
37713
37780
|
}
|
|
@@ -37715,12 +37782,12 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37715
37782
|
}
|
|
37716
37783
|
case "record":
|
|
37717
37784
|
case "map": {
|
|
37718
|
-
const newKey =
|
|
37719
|
-
const newVal =
|
|
37785
|
+
const newKey = run2(def.keyType);
|
|
37786
|
+
const newVal = run2(def.valueType);
|
|
37720
37787
|
return newKey === def.keyType && newVal === def.valueType ? s : clone(s, { ...def, keyType: newKey, valueType: newVal });
|
|
37721
37788
|
}
|
|
37722
37789
|
case "set": {
|
|
37723
|
-
const newVal =
|
|
37790
|
+
const newVal = run2(def.valueType);
|
|
37724
37791
|
return newVal === def.valueType ? s : clone(s, { ...def, valueType: newVal });
|
|
37725
37792
|
}
|
|
37726
37793
|
case "union": {
|
|
@@ -37728,7 +37795,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37728
37795
|
let changed = false;
|
|
37729
37796
|
const newOptions = [];
|
|
37730
37797
|
for (const opt of oldOptions) {
|
|
37731
|
-
const mapped =
|
|
37798
|
+
const mapped = run2(opt);
|
|
37732
37799
|
if (mapped !== opt)
|
|
37733
37800
|
changed = true;
|
|
37734
37801
|
newOptions.push(mapped);
|
|
@@ -37736,8 +37803,8 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37736
37803
|
return changed ? clone(s, { ...def, options: newOptions }) : s;
|
|
37737
37804
|
}
|
|
37738
37805
|
case "intersection": {
|
|
37739
|
-
const newLeft =
|
|
37740
|
-
const newRight =
|
|
37806
|
+
const newLeft = run2(def.left);
|
|
37807
|
+
const newRight = run2(def.right);
|
|
37741
37808
|
return newLeft === def.left && newRight === def.right ? s : clone(s, { ...def, left: newLeft, right: newRight });
|
|
37742
37809
|
}
|
|
37743
37810
|
case "optional":
|
|
@@ -37749,23 +37816,23 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37749
37816
|
case "nonoptional":
|
|
37750
37817
|
case "promise":
|
|
37751
37818
|
case "success": {
|
|
37752
|
-
const newInner =
|
|
37819
|
+
const newInner = run2(def.innerType);
|
|
37753
37820
|
return newInner === def.innerType ? s : clone(s, { ...def, innerType: newInner });
|
|
37754
37821
|
}
|
|
37755
37822
|
case "pipe": {
|
|
37756
|
-
const newIn =
|
|
37757
|
-
const newOut =
|
|
37823
|
+
const newIn = run2(def.in);
|
|
37824
|
+
const newOut = run2(def.out);
|
|
37758
37825
|
return newIn === def.in && newOut === def.out ? s : clone(s, { ...def, in: newIn, out: newOut });
|
|
37759
37826
|
}
|
|
37760
37827
|
case "function": {
|
|
37761
|
-
const newInput =
|
|
37762
|
-
const newOutput =
|
|
37828
|
+
const newInput = run2(def.input);
|
|
37829
|
+
const newOutput = run2(def.output);
|
|
37763
37830
|
return newInput === def.input && newOutput === def.output ? s : clone(s, { ...def, input: newInput, output: newOutput });
|
|
37764
37831
|
}
|
|
37765
37832
|
case "lazy": {
|
|
37766
37833
|
const original = def.getter;
|
|
37767
37834
|
const { _cachedInner, ...rest } = def;
|
|
37768
|
-
return clone(s, { ...rest, getter: () =>
|
|
37835
|
+
return clone(s, { ...rest, getter: () => run2(original()) });
|
|
37769
37836
|
}
|
|
37770
37837
|
// A leaf by choice: `parts` are regex fragments, not data positions.
|
|
37771
37838
|
case "template_literal":
|
|
@@ -37796,7 +37863,7 @@ function visit(schema2, fnOrHandlers) {
|
|
|
37796
37863
|
}
|
|
37797
37864
|
}
|
|
37798
37865
|
}
|
|
37799
|
-
return
|
|
37866
|
+
return run2(schema2);
|
|
37800
37867
|
}
|
|
37801
37868
|
|
|
37802
37869
|
// node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/deep-partial.js
|
|
@@ -37951,12 +38018,17 @@ async function routeOf(a, t, pick2) {
|
|
|
37951
38018
|
};
|
|
37952
38019
|
}
|
|
37953
38020
|
var queueSpot = (r) => r.runner === "session" ? { lane: r.lane } : { lane: r.lane, pin: r.lane };
|
|
37954
|
-
|
|
38021
|
+
var direct = (a, c) => (fn) => fn(a, c);
|
|
38022
|
+
async function route(a, coord, id, by, pick2, atomic2 = direct(a, coord)) {
|
|
37955
38023
|
const t = await a.get(id);
|
|
37956
38024
|
if (!t) throw new AerieError(`no task ${id}`, 404);
|
|
37957
38025
|
const r = await routeOf(a, t, pick2);
|
|
38026
|
+
await atomic2((a2, coord2) => write(a2, coord2, t.id, r, by));
|
|
38027
|
+
return r;
|
|
38028
|
+
}
|
|
38029
|
+
async function write(a, coord, id, r, by) {
|
|
37958
38030
|
await a.log(
|
|
37959
|
-
|
|
38031
|
+
id,
|
|
37960
38032
|
by,
|
|
37961
38033
|
"routed",
|
|
37962
38034
|
{
|
|
@@ -37968,11 +38040,10 @@ async function route(a, coord, id, by, pick2) {
|
|
|
37968
38040
|
},
|
|
37969
38041
|
r.why
|
|
37970
38042
|
);
|
|
37971
|
-
await coord.queueAdd([
|
|
37972
|
-
return r;
|
|
38043
|
+
await coord.queueAdd([id], queueSpot(r));
|
|
37973
38044
|
}
|
|
37974
38045
|
var RUN_FAILED = "Run failed";
|
|
37975
|
-
async function afterDecision(a, coord, d, by, pick2) {
|
|
38046
|
+
async function afterDecision(a, coord, d, by, pick2, atomic2 = direct(a, coord)) {
|
|
37976
38047
|
const out = [];
|
|
37977
38048
|
const worker = d.kind === "run-failed";
|
|
37978
38049
|
for (const id of d.blocks) {
|
|
@@ -37982,16 +38053,18 @@ async function afterDecision(a, coord, d, by, pick2) {
|
|
|
37982
38053
|
await a.edit(t.id, { assignees: ["@william"] }, by, { why: `decision ${d.id}` });
|
|
37983
38054
|
out.push({ id: t.id, lane: null });
|
|
37984
38055
|
} else if (worker && d.answer === "session") {
|
|
37985
|
-
await
|
|
37986
|
-
|
|
37987
|
-
|
|
37988
|
-
|
|
37989
|
-
|
|
37990
|
-
|
|
37991
|
-
|
|
37992
|
-
|
|
38056
|
+
await atomic2(async (a2, coord2) => {
|
|
38057
|
+
await a2.log(
|
|
38058
|
+
t.id,
|
|
38059
|
+
by,
|
|
38060
|
+
"routed",
|
|
38061
|
+
{ runner: "session", model: null, lane: t.area, rule: null },
|
|
38062
|
+
`decision ${d.id}`
|
|
38063
|
+
);
|
|
38064
|
+
await coord2.queueAdd([t.id], { lane: t.area });
|
|
38065
|
+
});
|
|
37993
38066
|
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 });
|
|
38067
|
+
} else out.push({ id: t.id, lane: (await route(a, coord, t.id, by, pick2, atomic2)).lane });
|
|
37995
38068
|
}
|
|
37996
38069
|
return out;
|
|
37997
38070
|
}
|
|
@@ -38097,12 +38170,12 @@ async function hear(file2, o = {}) {
|
|
|
38097
38170
|
const { hearSpeech, speechHere } = await import("@wrenautomation/read/video");
|
|
38098
38171
|
const { mkdtemp: mkdtemp2, rm } = await import("node:fs/promises");
|
|
38099
38172
|
const { tmpdir: tmpdir2 } = await import("node:os");
|
|
38100
|
-
const { join:
|
|
38173
|
+
const { join: join8 } = await import("node:path");
|
|
38101
38174
|
const cohere = (process.env.COHERE_API_KEY ?? "").split(",").filter(Boolean);
|
|
38102
38175
|
const ear = speechHere(cohere);
|
|
38103
38176
|
if (!ear)
|
|
38104
38177
|
throw new AerieError("no ear: install whisper.cpp and its model, or set COHERE_API_KEY");
|
|
38105
|
-
const dir = await mkdtemp2(
|
|
38178
|
+
const dir = await mkdtemp2(join8(tmpdir2(), "aerie-hear-"));
|
|
38106
38179
|
const t0 = performance.now();
|
|
38107
38180
|
try {
|
|
38108
38181
|
const text3 = await hearSpeech(file2, dir, o.ffmpeg ?? "ffmpeg", ear, {
|
|
@@ -38280,7 +38353,7 @@ async function intake(a, input2, by, o = {}) {
|
|
|
38280
38353
|
});
|
|
38281
38354
|
else
|
|
38282
38355
|
try {
|
|
38283
|
-
const r = await route(a, o.coord, id, by, o.pick);
|
|
38356
|
+
const r = await route(a, o.coord, id, by, o.pick, o.atomic);
|
|
38284
38357
|
res.routed.push({ id, runner: r.runner, lane: r.lane });
|
|
38285
38358
|
} catch (e) {
|
|
38286
38359
|
res.routed.push({ id, runner: null, lane: null, error: e.message });
|
|
@@ -38319,12 +38392,12 @@ function plain(r) {
|
|
|
38319
38392
|
`;
|
|
38320
38393
|
}
|
|
38321
38394
|
function registerIntakeCommands(program3, ctx) {
|
|
38322
|
-
const either = (fn) => ctx.withBoard ? ctx.withBoard(
|
|
38395
|
+
const either = (fn) => ctx.withBoard ? ctx.withBoard(fn) : ctx.withStore((a) => fn(a, null));
|
|
38323
38396
|
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
38397
|
"--split <file>",
|
|
38325
38398
|
"your own split as JSON ({tasks: [{title, span, why, area?}]}); - = stdin"
|
|
38326
38399
|
).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) => {
|
|
38400
|
+
(words, o) => either(async (a, c, atomic2) => {
|
|
38328
38401
|
const text3 = o.file ? await readFile(o.file, "utf8") : words.join(" ");
|
|
38329
38402
|
const split = o.split === void 0 ? void 0 : JSON.parse(o.split === "-" ? await stdin() : await readFile(o.split, "utf8"));
|
|
38330
38403
|
const spec = await a.getSetting("router_model");
|
|
@@ -38346,6 +38419,7 @@ function registerIntakeCommands(program3, ctx) {
|
|
|
38346
38419
|
model: await a.getSetting("intake_model") || void 0,
|
|
38347
38420
|
lane: await a.getSetting("intake_lane") || "main",
|
|
38348
38421
|
coord: c ?? void 0,
|
|
38422
|
+
atomic: atomic2,
|
|
38349
38423
|
pick: spec ? modelPicker(spec) : void 0
|
|
38350
38424
|
}
|
|
38351
38425
|
);
|
|
@@ -38407,11 +38481,181 @@ function aerieEnv(env = process.env) {
|
|
|
38407
38481
|
return (k) => env[k] ?? fromFile[k];
|
|
38408
38482
|
}
|
|
38409
38483
|
|
|
38484
|
+
// src/pool.ts
|
|
38485
|
+
import { execFile } from "node:child_process";
|
|
38486
|
+
import { randomUUID } from "node:crypto";
|
|
38487
|
+
import {
|
|
38488
|
+
closeSync,
|
|
38489
|
+
existsSync as existsSync3,
|
|
38490
|
+
mkdirSync,
|
|
38491
|
+
openSync,
|
|
38492
|
+
readdirSync as readdirSync2,
|
|
38493
|
+
readFileSync as readFileSync3,
|
|
38494
|
+
unlinkSync,
|
|
38495
|
+
writeFileSync
|
|
38496
|
+
} from "node:fs";
|
|
38497
|
+
import { homedir as homedir2 } from "node:os";
|
|
38498
|
+
import { dirname, join as join3 } from "node:path";
|
|
38499
|
+
import { promisify } from "node:util";
|
|
38500
|
+
var run = promisify(execFile);
|
|
38501
|
+
var POOL_PREFIX = "aerie-pool-";
|
|
38502
|
+
var DEFAULT_COMMAND = [
|
|
38503
|
+
"claude",
|
|
38504
|
+
"--session-id",
|
|
38505
|
+
"{session}",
|
|
38506
|
+
// unattended: a permission prompt would stall it (the session's own rules still apply)
|
|
38507
|
+
"--permission-mode",
|
|
38508
|
+
"bypassPermissions",
|
|
38509
|
+
"Run `peers serve {lane}`, then wait: tasks on lane {lane} come to you as prompts. Work each one to done."
|
|
38510
|
+
];
|
|
38511
|
+
function parseLanes(v, key = "pool_lanes") {
|
|
38512
|
+
const out = {};
|
|
38513
|
+
for (const part of v.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
38514
|
+
const [lane, n] = part.split("=");
|
|
38515
|
+
const k = Number(n);
|
|
38516
|
+
if (!lane || !Number.isInteger(k) || k < 0) throw new Error(`${key}: "${part}" is not name=N`);
|
|
38517
|
+
out[lane] = k;
|
|
38518
|
+
}
|
|
38519
|
+
return out;
|
|
38520
|
+
}
|
|
38521
|
+
var holds = (holder, session) => holder.endsWith(`-${session.slice(0, 4)}`);
|
|
38522
|
+
async function reconcile(o) {
|
|
38523
|
+
const now = (o.now ?? Date.now)();
|
|
38524
|
+
const out = [];
|
|
38525
|
+
const live = new Set(await o.mux.list());
|
|
38526
|
+
const held = await o.coord.held();
|
|
38527
|
+
const queue = await o.coord.queueList();
|
|
38528
|
+
const leasesOf = (s) => held.flatMap(
|
|
38529
|
+
(h) => h.holders.filter((l) => holds(l.holder, s.session)).map((l) => ({ id: h.id, holder: l.holder }))
|
|
38530
|
+
);
|
|
38531
|
+
const fill = (s, lane) => o.command.map((a) => a.replaceAll("{session}", s).replaceAll("{lane}", lane));
|
|
38532
|
+
const startSlot = async (lane, name, act, why) => {
|
|
38533
|
+
const session = randomUUID();
|
|
38534
|
+
await o.mux.start(name, fill(session, lane), { cwd: o.cwd });
|
|
38535
|
+
o.state.slots.push({ name, lane, session, started: now, busyAt: now });
|
|
38536
|
+
out.push({ act, name, lane, why });
|
|
38537
|
+
};
|
|
38538
|
+
for (const s of [...o.state.slots]) {
|
|
38539
|
+
if (live.has(s.name)) continue;
|
|
38540
|
+
for (const l of leasesOf(s)) await o.coord.release(l.id, l.holder, { queue: true });
|
|
38541
|
+
o.state.slots = o.state.slots.filter((x) => x !== s);
|
|
38542
|
+
if ((o.lanes[s.lane] ?? 0) > 0)
|
|
38543
|
+
await startSlot(s.lane, s.name, "restart", "its tmux session is gone");
|
|
38544
|
+
}
|
|
38545
|
+
for (const s of o.state.slots) {
|
|
38546
|
+
const st = o.sessionState(s.session);
|
|
38547
|
+
if (leasesOf(s).length || st?.status === "busy" || st?.status === "shell") s.busyAt = now;
|
|
38548
|
+
if (st?.waitingFor) out.push({ act: "waits", name: s.name, lane: s.lane, why: st.waitingFor });
|
|
38549
|
+
}
|
|
38550
|
+
for (const [lane, n] of Object.entries(o.lanes)) {
|
|
38551
|
+
const mine = o.state.slots.filter((s) => s.lane === lane);
|
|
38552
|
+
const queued = queue.filter((q) => q.lane === lane && !q.pin).length;
|
|
38553
|
+
const busy = mine.filter((s) => s.busyAt === now).length;
|
|
38554
|
+
const target = Math.min(n, busy + queued);
|
|
38555
|
+
for (let i = 0; mine.length + i < target; i++) {
|
|
38556
|
+
const name = `${POOL_PREFIX}${lane.replace(/[^a-z0-9]+/gi, "-")}-${randomUUID().slice(0, 4)}`;
|
|
38557
|
+
await startSlot(lane, name, "start", `${queued} queued on ${lane}`);
|
|
38558
|
+
}
|
|
38559
|
+
let extra = mine.length - target;
|
|
38560
|
+
for (const s of mine.sort((a, b) => a.busyAt - b.busyAt)) {
|
|
38561
|
+
if (extra <= 0) break;
|
|
38562
|
+
if (s.busyAt === now || now - s.busyAt < o.idleMs) continue;
|
|
38563
|
+
await o.mux.stop(s.name);
|
|
38564
|
+
o.state.slots = o.state.slots.filter((x) => x !== s);
|
|
38565
|
+
out.push({
|
|
38566
|
+
act: "stop",
|
|
38567
|
+
name: s.name,
|
|
38568
|
+
lane,
|
|
38569
|
+
why: `idle ${Math.round((now - s.busyAt) / 6e4)} min, nothing queued`
|
|
38570
|
+
});
|
|
38571
|
+
extra--;
|
|
38572
|
+
}
|
|
38573
|
+
}
|
|
38574
|
+
return out;
|
|
38575
|
+
}
|
|
38576
|
+
function tmuxMux(bin = "tmux") {
|
|
38577
|
+
return {
|
|
38578
|
+
async list() {
|
|
38579
|
+
try {
|
|
38580
|
+
const { stdout } = await run(bin, ["list-sessions", "-F", "#{session_name}"]);
|
|
38581
|
+
return stdout.split("\n").filter((n) => n.startsWith(POOL_PREFIX));
|
|
38582
|
+
} catch (e) {
|
|
38583
|
+
const err = String(e.stderr ?? "");
|
|
38584
|
+
if (/no server running|error connecting to/.test(err)) return [];
|
|
38585
|
+
throw e;
|
|
38586
|
+
}
|
|
38587
|
+
},
|
|
38588
|
+
async start(name, argv, o) {
|
|
38589
|
+
await run(bin, ["new-session", "-d", "-s", name, "-c", o.cwd, "--", ...argv]);
|
|
38590
|
+
},
|
|
38591
|
+
async stop(name) {
|
|
38592
|
+
await run(bin, ["kill-session", "-t", name]).catch(() => {
|
|
38593
|
+
});
|
|
38594
|
+
}
|
|
38595
|
+
};
|
|
38596
|
+
}
|
|
38597
|
+
function claudeSessionState(dir = join3(homedir2(), ".claude", "sessions")) {
|
|
38598
|
+
return (session) => {
|
|
38599
|
+
for (const f of existsSync3(dir) ? readdirSync2(dir) : []) {
|
|
38600
|
+
if (!f.endsWith(".json")) continue;
|
|
38601
|
+
try {
|
|
38602
|
+
const d = JSON.parse(readFileSync3(join3(dir, f), "utf8"));
|
|
38603
|
+
if (d.sessionId === session) return d;
|
|
38604
|
+
} catch {
|
|
38605
|
+
}
|
|
38606
|
+
}
|
|
38607
|
+
return null;
|
|
38608
|
+
};
|
|
38609
|
+
}
|
|
38610
|
+
var poolDir = () => join3(homedir2(), ".local", "state", "aerie");
|
|
38611
|
+
function loadPool(file2 = join3(poolDir(), "pool.json")) {
|
|
38612
|
+
try {
|
|
38613
|
+
return JSON.parse(readFileSync3(file2, "utf8"));
|
|
38614
|
+
} catch {
|
|
38615
|
+
return { slots: [] };
|
|
38616
|
+
}
|
|
38617
|
+
}
|
|
38618
|
+
function savePool(s, file2 = join3(poolDir(), "pool.json")) {
|
|
38619
|
+
mkdirSync(dirname(file2), { recursive: true });
|
|
38620
|
+
writeFileSync(file2, `${JSON.stringify(s, null, 2)}
|
|
38621
|
+
`);
|
|
38622
|
+
}
|
|
38623
|
+
function lockPool(file2 = join3(poolDir(), "pool.lock")) {
|
|
38624
|
+
mkdirSync(dirname(file2), { recursive: true });
|
|
38625
|
+
for (let i = 0; i < 2; i++) {
|
|
38626
|
+
try {
|
|
38627
|
+
const fd = openSync(file2, "wx");
|
|
38628
|
+
writeFileSync(fd, String(process.pid));
|
|
38629
|
+
closeSync(fd);
|
|
38630
|
+
return () => unlinkSync(file2);
|
|
38631
|
+
} catch {
|
|
38632
|
+
const pid = Number(readFileSync3(file2, "utf8"));
|
|
38633
|
+
if (pid && alive(pid)) throw new Error(`aerie pool: already running (pid ${pid})`);
|
|
38634
|
+
unlinkSync(file2);
|
|
38635
|
+
}
|
|
38636
|
+
}
|
|
38637
|
+
throw new Error("aerie pool: cannot take the lock");
|
|
38638
|
+
}
|
|
38639
|
+
function alive(pid) {
|
|
38640
|
+
try {
|
|
38641
|
+
process.kill(pid, 0);
|
|
38642
|
+
return true;
|
|
38643
|
+
} catch {
|
|
38644
|
+
return false;
|
|
38645
|
+
}
|
|
38646
|
+
}
|
|
38647
|
+
|
|
38410
38648
|
// src/run/spawn.ts
|
|
38411
38649
|
init_db2();
|
|
38412
38650
|
import { spawn } from "node:child_process";
|
|
38413
38651
|
import { createInterface } from "node:readline";
|
|
38414
38652
|
var STDERR_KEEP = 64e3;
|
|
38653
|
+
var CLEAN_ENV = ["PATH", "USER", "LANG", "LC_ALL", "TMPDIR", "SHELL", "TERM"];
|
|
38654
|
+
function cleanEnv(from = process.env) {
|
|
38655
|
+
const out = {};
|
|
38656
|
+
for (const k of CLEAN_ENV) if (from[k] !== void 0) out[k] = from[k];
|
|
38657
|
+
return out;
|
|
38658
|
+
}
|
|
38415
38659
|
function runLines(bin, args, o) {
|
|
38416
38660
|
return new Promise((resolve, reject) => {
|
|
38417
38661
|
if (o.signal.aborted) {
|
|
@@ -38420,7 +38664,7 @@ function runLines(bin, args, o) {
|
|
|
38420
38664
|
}
|
|
38421
38665
|
const child = spawn(bin, args, {
|
|
38422
38666
|
cwd: o.cwd,
|
|
38423
|
-
env: { ...process.env, ...o.env },
|
|
38667
|
+
env: o.inherit === false ? { ...cleanEnv(), ...o.env } : { ...process.env, ...o.env },
|
|
38424
38668
|
detached: true,
|
|
38425
38669
|
stdio: ["ignore", "pipe", "pipe"]
|
|
38426
38670
|
});
|
|
@@ -38533,6 +38777,7 @@ function claudeEvent(l) {
|
|
|
38533
38777
|
function claudeRunner(o = {}) {
|
|
38534
38778
|
return {
|
|
38535
38779
|
name: "claude",
|
|
38780
|
+
needs: ["ANTHROPIC_API_KEY"],
|
|
38536
38781
|
async run(t, ctx) {
|
|
38537
38782
|
let last = null;
|
|
38538
38783
|
let session;
|
|
@@ -38583,6 +38828,7 @@ function lastText(v) {
|
|
|
38583
38828
|
function codexRunner(o = {}) {
|
|
38584
38829
|
return {
|
|
38585
38830
|
name: "codex",
|
|
38831
|
+
needs: ["OPENAI_API_KEY", "CODEX_API_KEY"],
|
|
38586
38832
|
async run(t, ctx) {
|
|
38587
38833
|
let summary = null;
|
|
38588
38834
|
const r = await runLines(
|
|
@@ -38611,6 +38857,7 @@ function codexRunner(o = {}) {
|
|
|
38611
38857
|
function grokRunner(o = {}) {
|
|
38612
38858
|
return {
|
|
38613
38859
|
name: "grok",
|
|
38860
|
+
needs: ["XAI_API_KEY"],
|
|
38614
38861
|
async run(t, ctx) {
|
|
38615
38862
|
const out = [];
|
|
38616
38863
|
const r = await runLines(
|
|
@@ -38638,6 +38885,7 @@ var DO_LINE = /^(done|needs-human|failed|planned) via (\S+)(?: ([^:]+))?: (.*)$/
|
|
|
38638
38885
|
function autobrowseRunner(o = {}) {
|
|
38639
38886
|
return {
|
|
38640
38887
|
name: "autobrowse-do",
|
|
38888
|
+
needs: ["AUTOBROWSE_OWNER"],
|
|
38641
38889
|
async run(t, ctx) {
|
|
38642
38890
|
const input2 = t.extra.input ?? {};
|
|
38643
38891
|
const out = [];
|
|
@@ -38691,7 +38939,23 @@ function apiRunner(o) {
|
|
|
38691
38939
|
summary: "no model for the api runner",
|
|
38692
38940
|
question: `No model for ${t.id} on the api runner: set AERIE_API_MODEL, or a route rule with a model?`
|
|
38693
38941
|
};
|
|
38694
|
-
const
|
|
38942
|
+
const tenant = ctx.inherit === false;
|
|
38943
|
+
if (tenant && /^(claude-code|command)(:|$)/.test(spec))
|
|
38944
|
+
return {
|
|
38945
|
+
outcome: "needs-input",
|
|
38946
|
+
summary: `model ${spec} runs on the host's login`,
|
|
38947
|
+
question: `${t.id}: a tenant run cannot use ${spec}. Which model, on the tenant's own key?`
|
|
38948
|
+
};
|
|
38949
|
+
let m;
|
|
38950
|
+
try {
|
|
38951
|
+
m = modelFromSpec(spec, tenant ? ctx.env : { ...process.env, ...o.env, ...ctx.env });
|
|
38952
|
+
} catch (e) {
|
|
38953
|
+
return {
|
|
38954
|
+
outcome: "needs-input",
|
|
38955
|
+
summary: e.message,
|
|
38956
|
+
question: `${t.id}: ${e.message}. Add the key, then answer.`
|
|
38957
|
+
};
|
|
38958
|
+
}
|
|
38695
38959
|
await ctx.emit({ kind: "init", text: m.id, data: { spec, model: m.id } });
|
|
38696
38960
|
try {
|
|
38697
38961
|
const { value, usage } = await withDeadline(
|
|
@@ -38724,7 +38988,7 @@ You have no tools. Answer as JSON: outcome (done, needs-input or failed), summar
|
|
|
38724
38988
|
};
|
|
38725
38989
|
}
|
|
38726
38990
|
function spawnOf(ctx) {
|
|
38727
|
-
return { cwd: ctx.cwd, env: ctx.env, signal: ctx.signal };
|
|
38991
|
+
return { cwd: ctx.cwd, env: ctx.env, inherit: ctx.inherit, signal: ctx.signal };
|
|
38728
38992
|
}
|
|
38729
38993
|
function headlessRunners(get2) {
|
|
38730
38994
|
const opt = (name) => {
|
|
@@ -38751,9 +39015,22 @@ function headlessRunners(get2) {
|
|
|
38751
39015
|
// src/run/worker.ts
|
|
38752
39016
|
import { mkdtemp } from "node:fs/promises";
|
|
38753
39017
|
import { hostname as hostname3, tmpdir } from "node:os";
|
|
38754
|
-
import { join as
|
|
39018
|
+
import { join as join4 } from "node:path";
|
|
38755
39019
|
var MAX_FAILS = 3;
|
|
39020
|
+
async function failsSince(store, id, since) {
|
|
39021
|
+
const [failed, starts, results] = await Promise.all([
|
|
39022
|
+
store.countEvents(id, "run.result", { outcome: "failed" }, since),
|
|
39023
|
+
store.countEvents(id, "run.start", void 0, since),
|
|
39024
|
+
store.countEvents(id, "run.result", void 0, since)
|
|
39025
|
+
]);
|
|
39026
|
+
return { failed, died: starts - results };
|
|
39027
|
+
}
|
|
38756
39028
|
var LEASE_MS = 12e4;
|
|
39029
|
+
function missingCredentials(runner, tenant) {
|
|
39030
|
+
const needs = runner.needs ?? [];
|
|
39031
|
+
if (!needs.length || needs.some((k) => tenant.keys[k])) return [];
|
|
39032
|
+
return needs;
|
|
39033
|
+
}
|
|
38757
39034
|
var holderOf = (r) => `${hostname3()}/aerie-${r}-${process.pid}`;
|
|
38758
39035
|
async function workOnce(o) {
|
|
38759
39036
|
const { store, coord, runner } = o;
|
|
@@ -38762,7 +39039,11 @@ async function workOnce(o) {
|
|
|
38762
39039
|
const leaseMs = o.leaseMs ?? LEASE_MS;
|
|
38763
39040
|
const by = `runner:${runner.name}`;
|
|
38764
39041
|
const lane = runLane(runner.name);
|
|
38765
|
-
const took = await coord.queueTake(
|
|
39042
|
+
const took = await coord.queueTake(
|
|
39043
|
+
{ lanes: [], pin: lane, ...o.cap === void 0 ? {} : { cap: o.cap } },
|
|
39044
|
+
holder,
|
|
39045
|
+
leaseMs
|
|
39046
|
+
);
|
|
38766
39047
|
if (!took) return null;
|
|
38767
39048
|
const { id, gen } = took;
|
|
38768
39049
|
const stop = new AbortController();
|
|
@@ -38790,7 +39071,7 @@ async function workOnce(o) {
|
|
|
38790
39071
|
},
|
|
38791
39072
|
Math.max(1, Math.floor(leaseMs / 3))
|
|
38792
39073
|
);
|
|
38793
|
-
const
|
|
39074
|
+
const write2 = async (kind, body, why) => {
|
|
38794
39075
|
if (await fenced()) await store.log(id, by, kind, body, why);
|
|
38795
39076
|
};
|
|
38796
39077
|
let res;
|
|
@@ -38798,8 +39079,8 @@ async function workOnce(o) {
|
|
|
38798
39079
|
const t = await store.get(id);
|
|
38799
39080
|
const routed = await store.lastEvent(id, "routed");
|
|
38800
39081
|
const model = routed?.body?.model ?? null;
|
|
38801
|
-
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(
|
|
38802
|
-
await
|
|
39082
|
+
const cwd = o.cwdOf ? await o.cwdOf(id) : await mkdtemp(join4(tmpdir(), `aerie-run-${id}-`));
|
|
39083
|
+
await write2(
|
|
38803
39084
|
"run.start",
|
|
38804
39085
|
{ runner: runner.name, model, holder, gen, cwd },
|
|
38805
39086
|
routed?.why ?? void 0
|
|
@@ -38808,16 +39089,31 @@ async function workOnce(o) {
|
|
|
38808
39089
|
const opt = d2.options.find((x) => x.key === d2.answer);
|
|
38809
39090
|
return `${d2.question} \u2192 ${opt ? opt.text : d2.answer} (${d2.answeredBy}${d2.why ? `: ${d2.why}` : ""})`;
|
|
38810
39091
|
});
|
|
39092
|
+
const missing = o.tenant ? missingCredentials(runner, o.tenant) : [];
|
|
39093
|
+
const before = await failsSince(store, id, routed?.seq ?? 0);
|
|
39094
|
+
const died = before.died - 1;
|
|
38811
39095
|
if (!t) res = { outcome: "failed", summary: `task ${id} is gone` };
|
|
39096
|
+
else if (before.failed + died >= MAX_FAILS)
|
|
39097
|
+
res = {
|
|
39098
|
+
outcome: "failed",
|
|
39099
|
+
summary: `the worker died ${died} times on this task, no result written`
|
|
39100
|
+
};
|
|
39101
|
+
else if (missing.length && o.tenant)
|
|
39102
|
+
res = {
|
|
39103
|
+
outcome: "needs-input",
|
|
39104
|
+
summary: `no ${runner.name} credential for tenant ${o.tenant.name}`,
|
|
39105
|
+
question: `Tenant ${o.tenant.name} has no ${missing.join(" or ")} for ${runner.name}. Add it to the tenant's credentials, then answer.`
|
|
39106
|
+
};
|
|
38812
39107
|
else
|
|
38813
39108
|
try {
|
|
38814
39109
|
res = await runner.run(t, {
|
|
38815
39110
|
model,
|
|
38816
39111
|
answers,
|
|
38817
39112
|
cwd,
|
|
38818
|
-
env: { ...o.env, AERIE_TASK: id, AERIE_BY: by },
|
|
39113
|
+
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 },
|
|
39114
|
+
inherit: !o.tenant,
|
|
38819
39115
|
signal: stop.signal,
|
|
38820
|
-
emit: (e) =>
|
|
39116
|
+
emit: (e) => write2(`run.${e.kind}`, { text: e.text, data: e.data })
|
|
38821
39117
|
});
|
|
38822
39118
|
} catch (e) {
|
|
38823
39119
|
res = { outcome: "failed", summary: e.message, data: { error: String(e) } };
|
|
@@ -38827,11 +39123,14 @@ async function workOnce(o) {
|
|
|
38827
39123
|
o.signal?.removeEventListener("abort", onOuter);
|
|
38828
39124
|
}
|
|
38829
39125
|
if (lost || !await fenced()) return { id, outcome: "lost" };
|
|
38830
|
-
if (o.signal?.aborted)
|
|
39126
|
+
if (o.signal?.aborted) {
|
|
39127
|
+
await write2("run.result", { outcome: "stopped", said: res.outcome, summary: "worker stopped" });
|
|
39128
|
+
return { id, outcome: "lost" };
|
|
39129
|
+
}
|
|
38831
39130
|
const open3 = (await store.decisions({ open: true })).find((d2) => d2.blocks.includes(id));
|
|
38832
39131
|
const outcome = open3 ? "needs-input" : res.outcome;
|
|
38833
39132
|
const { summary } = res;
|
|
38834
|
-
await
|
|
39133
|
+
await write2(
|
|
38835
39134
|
"run.result",
|
|
38836
39135
|
{
|
|
38837
39136
|
outcome,
|
|
@@ -38851,8 +39150,8 @@ async function workOnce(o) {
|
|
|
38851
39150
|
return { id, outcome, summary };
|
|
38852
39151
|
}
|
|
38853
39152
|
if (outcome === "failed") {
|
|
38854
|
-
const
|
|
38855
|
-
const fails =
|
|
39153
|
+
const n = await failsSince(store, id, (await store.lastEvent(id, "routed"))?.seq ?? 0);
|
|
39154
|
+
const fails = n.failed + n.died;
|
|
38856
39155
|
await coord.release(id, holder);
|
|
38857
39156
|
if (!coord.ownsStatus) await store.edit(id, { status: "To Do" }, by);
|
|
38858
39157
|
if (fails < MAX_FAILS) {
|
|
@@ -38956,7 +39255,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38956
39255
|
"route a task: the first matching rule, else the router model; then queue it on its lane"
|
|
38957
39256
|
).option("--dry-run", "show the route, write nothing").option("--json", "JSON out").action((id, o) => {
|
|
38958
39257
|
if (!id) return void rt.help();
|
|
38959
|
-
return board(async (a, c) => {
|
|
39258
|
+
return board(async (a, c, atomic2) => {
|
|
38960
39259
|
const pick2 = await pickerOf(a);
|
|
38961
39260
|
if (o.dryRun) {
|
|
38962
39261
|
const t = await a.get(id);
|
|
@@ -38967,7 +39266,7 @@ function registerRunCommands(program3, ctx) {
|
|
|
38967
39266
|
`
|
|
38968
39267
|
);
|
|
38969
39268
|
}
|
|
38970
|
-
const r = await route(a, c, id, ctx.by, pick2);
|
|
39269
|
+
const r = await route(a, c, id, ctx.by, pick2, atomic2);
|
|
38971
39270
|
print2(
|
|
38972
39271
|
o.json ? r : `${id.toUpperCase()} \u2192 ${r.runner}${r.model ? ` (${r.model})` : ""} on ${r.lane}: ${r.why}
|
|
38973
39272
|
`
|
|
@@ -39018,7 +39317,10 @@ function registerRunCommands(program3, ctx) {
|
|
|
39018
39317
|
program3.command("work").requiredOption(
|
|
39019
39318
|
"--runner <name>",
|
|
39020
39319
|
"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(
|
|
39320
|
+
).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(
|
|
39321
|
+
"--cap <n>",
|
|
39322
|
+
"most live runs on this lane, all hosts; default setting run_cap (runner=N,...)"
|
|
39323
|
+
).option("--json", "one JSON line per task").description("take tasks from run:<runner>, run each, write the result").action(
|
|
39022
39324
|
(o) => board(async (a, c) => {
|
|
39023
39325
|
const name = runnerOf(o.runner);
|
|
39024
39326
|
if (ctx.local && name !== "api")
|
|
@@ -39027,6 +39329,12 @@ function registerRunCommands(program3, ctx) {
|
|
|
39027
39329
|
);
|
|
39028
39330
|
const runner = headlessRunners(aerieEnv()).get(name);
|
|
39029
39331
|
if (!runner) throw new Error(`${name} runs in a live session, not a worker`);
|
|
39332
|
+
const caps = parseLanes(await a.getSetting("run_cap") ?? "", "run_cap");
|
|
39333
|
+
const cap2 = o.cap === void 0 ? caps[name] : Number(o.cap);
|
|
39334
|
+
if (cap2 !== void 0 && !(Number.isInteger(cap2) && cap2 > 0))
|
|
39335
|
+
throw new Error(
|
|
39336
|
+
`${o.cap === void 0 ? `run_cap ${name}` : "--cap"}: ${cap2} is not a whole number above 0`
|
|
39337
|
+
);
|
|
39030
39338
|
const stop = new AbortController();
|
|
39031
39339
|
const quit = () => stop.abort();
|
|
39032
39340
|
process.once("SIGINT", quit);
|
|
@@ -39041,20 +39349,105 @@ function registerRunCommands(program3, ctx) {
|
|
|
39041
39349
|
idleMs: Number(o.idle),
|
|
39042
39350
|
once: !!o.once,
|
|
39043
39351
|
signal: stop.signal,
|
|
39352
|
+
...cap2 === void 0 ? {} : { cap: cap2 },
|
|
39044
39353
|
onOutcome: (w) => print2(
|
|
39045
39354
|
o.json ? `${JSON.stringify(w)}
|
|
39046
39355
|
` : `${w.id} ${w.outcome}${"summary" in w ? `: ${w.summary.split("\n")[0]}` : ""}
|
|
39047
39356
|
`
|
|
39048
39357
|
)
|
|
39049
39358
|
});
|
|
39050
|
-
if (!out.length && o.once)
|
|
39359
|
+
if (!out.length && o.once)
|
|
39360
|
+
print2(o.json ? "null\n" : `run:${name}: nothing taken (empty, or at its cap)
|
|
39361
|
+
`);
|
|
39362
|
+
} finally {
|
|
39363
|
+
process.off("SIGINT", quit);
|
|
39364
|
+
process.off("SIGTERM", quit);
|
|
39365
|
+
}
|
|
39366
|
+
})
|
|
39367
|
+
);
|
|
39368
|
+
const pool = program3.command("pool").description("session workers on this host (tmux): N per session lane, setting pool_lanes");
|
|
39369
|
+
const poolOf = async (a) => {
|
|
39370
|
+
const lanes = parseLanes(await a.getSetting("pool_lanes") ?? "");
|
|
39371
|
+
const cmd = await a.getSetting("pool_command");
|
|
39372
|
+
return {
|
|
39373
|
+
lanes,
|
|
39374
|
+
command: cmd ? JSON.parse(cmd) : DEFAULT_COMMAND,
|
|
39375
|
+
idleMs: Number(await a.getSetting("pool_idle_min") || 30) * 6e4
|
|
39376
|
+
};
|
|
39377
|
+
};
|
|
39378
|
+
const pass = async (a, c, mux, o) => {
|
|
39379
|
+
const p = await poolOf(a);
|
|
39380
|
+
const state = loadPool();
|
|
39381
|
+
const acts = await reconcile({
|
|
39382
|
+
mux,
|
|
39383
|
+
coord: c,
|
|
39384
|
+
...p,
|
|
39385
|
+
cwd: o.cwd ?? process.cwd(),
|
|
39386
|
+
state,
|
|
39387
|
+
sessionState: claudeSessionState()
|
|
39388
|
+
});
|
|
39389
|
+
savePool(state);
|
|
39390
|
+
for (const x of acts)
|
|
39391
|
+
print2(o.json ? `${JSON.stringify(x)}
|
|
39392
|
+
` : `${x.act} ${x.name} (${x.lane}): ${x.why}
|
|
39051
39393
|
`);
|
|
39394
|
+
return acts;
|
|
39395
|
+
};
|
|
39396
|
+
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(
|
|
39397
|
+
(o) => board(async (a, c) => {
|
|
39398
|
+
const unlock = lockPool();
|
|
39399
|
+
try {
|
|
39400
|
+
await pass(a, c, tmuxMux(), o);
|
|
39401
|
+
} finally {
|
|
39402
|
+
unlock();
|
|
39403
|
+
}
|
|
39404
|
+
})
|
|
39405
|
+
);
|
|
39406
|
+
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(
|
|
39407
|
+
(o) => board(async (a, c) => {
|
|
39408
|
+
const unlock = lockPool();
|
|
39409
|
+
const stop = new AbortController();
|
|
39410
|
+
const quit = () => stop.abort();
|
|
39411
|
+
process.once("SIGINT", quit);
|
|
39412
|
+
process.once("SIGTERM", quit);
|
|
39413
|
+
try {
|
|
39414
|
+
while (!stop.signal.aborted) {
|
|
39415
|
+
await pass(a, c, tmuxMux(), o).catch((e) => print2(`pool: ${e.message}
|
|
39416
|
+
`));
|
|
39417
|
+
await new Promise((r) => {
|
|
39418
|
+
const t = setTimeout(r, Number(o.every) * 1e3);
|
|
39419
|
+
stop.signal.addEventListener(
|
|
39420
|
+
"abort",
|
|
39421
|
+
() => {
|
|
39422
|
+
clearTimeout(t);
|
|
39423
|
+
r();
|
|
39424
|
+
},
|
|
39425
|
+
{ once: true }
|
|
39426
|
+
);
|
|
39427
|
+
});
|
|
39428
|
+
}
|
|
39052
39429
|
} finally {
|
|
39430
|
+
unlock();
|
|
39053
39431
|
process.off("SIGINT", quit);
|
|
39054
39432
|
process.off("SIGTERM", quit);
|
|
39055
39433
|
}
|
|
39056
39434
|
})
|
|
39057
39435
|
);
|
|
39436
|
+
pool.command("status").option("--json", "JSON out").description("the pool's sessions on this host").action(async (o) => {
|
|
39437
|
+
const s = loadPool();
|
|
39438
|
+
const live = new Set(await tmuxMux().list());
|
|
39439
|
+
const st = claudeSessionState();
|
|
39440
|
+
const rows3 = s.slots.map((x) => ({
|
|
39441
|
+
...x,
|
|
39442
|
+
alive: live.has(x.name),
|
|
39443
|
+
status: st(x.session)?.status ?? null,
|
|
39444
|
+
waitingFor: st(x.session)?.waitingFor ?? null
|
|
39445
|
+
}));
|
|
39446
|
+
print2(
|
|
39447
|
+
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")}
|
|
39448
|
+
` : "No pool sessions.\n"
|
|
39449
|
+
);
|
|
39450
|
+
});
|
|
39058
39451
|
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
39452
|
(words, o) => ctx.withStore(async (a) => {
|
|
39060
39453
|
const rs = await a.searchEvents(words, {
|
|
@@ -39082,14 +39475,14 @@ init_core();
|
|
|
39082
39475
|
import { spawnSync } from "node:child_process";
|
|
39083
39476
|
import { createHash } from "node:crypto";
|
|
39084
39477
|
import {
|
|
39085
|
-
existsSync as
|
|
39086
|
-
mkdirSync,
|
|
39087
|
-
readFileSync as
|
|
39478
|
+
existsSync as existsSync4,
|
|
39479
|
+
mkdirSync as mkdirSync2,
|
|
39480
|
+
readFileSync as readFileSync4,
|
|
39088
39481
|
renameSync,
|
|
39089
|
-
unlinkSync,
|
|
39090
|
-
writeFileSync
|
|
39482
|
+
unlinkSync as unlinkSync2,
|
|
39483
|
+
writeFileSync as writeFileSync2
|
|
39091
39484
|
} from "node:fs";
|
|
39092
|
-
import { basename, dirname, join as
|
|
39485
|
+
import { basename, dirname as dirname2, join as join5 } from "node:path";
|
|
39093
39486
|
init_core();
|
|
39094
39487
|
init_store();
|
|
39095
39488
|
var sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
@@ -39115,8 +39508,8 @@ async function mirror(a, root, o = {}) {
|
|
|
39115
39508
|
for (const t of tasks) {
|
|
39116
39509
|
const was = state.get(t.id);
|
|
39117
39510
|
const found = disk.get(t.id);
|
|
39118
|
-
const cur = was &&
|
|
39119
|
-
const curText = cur &&
|
|
39511
|
+
const cur = was && existsSync4(join5(root, was.path)) ? was.path : found?.path ?? was?.path;
|
|
39512
|
+
const curText = cur && existsSync4(join5(root, cur)) ? readFileSync4(join5(root, cur), "utf8") : void 0;
|
|
39120
39513
|
if (!o.adopt) {
|
|
39121
39514
|
const fileMoved = was !== void 0 && curText !== void 0 && (sha(curText) !== was.hash || cur !== was.path);
|
|
39122
39515
|
if (fileMoved && was && cur && t.version === was.version) {
|
|
@@ -39154,11 +39547,11 @@ async function mirror(a, root, o = {}) {
|
|
|
39154
39547
|
}
|
|
39155
39548
|
const text3 = renderTask(strip(t));
|
|
39156
39549
|
if (!o.dryRun) {
|
|
39157
|
-
|
|
39158
|
-
const tmp =
|
|
39159
|
-
|
|
39160
|
-
renameSync(tmp,
|
|
39161
|
-
if (cur && cur !== target &&
|
|
39550
|
+
mkdirSync2(dirname2(join5(root, target)), { recursive: true });
|
|
39551
|
+
const tmp = join5(root, `${target}.aerie-tmp`);
|
|
39552
|
+
writeFileSync2(tmp, text3);
|
|
39553
|
+
renameSync(tmp, join5(root, target));
|
|
39554
|
+
if (cur && cur !== target && existsSync4(join5(root, cur))) unlinkSync2(join5(root, cur));
|
|
39162
39555
|
await a.setMirrorFile(t.id, target, sha(text3), t.version);
|
|
39163
39556
|
}
|
|
39164
39557
|
if (cur && cur !== target) {
|
|
@@ -39175,7 +39568,7 @@ async function mirror(a, root, o = {}) {
|
|
|
39175
39568
|
return r;
|
|
39176
39569
|
}
|
|
39177
39570
|
function readFile2(path2, text3) {
|
|
39178
|
-
const place =
|
|
39571
|
+
const place = dirname2(path2);
|
|
39179
39572
|
if (!ALL.includes(place)) return null;
|
|
39180
39573
|
try {
|
|
39181
39574
|
return { task: parseTask(text3), place };
|
|
@@ -39616,7 +40009,15 @@ function registerCommands(program3, ctx) {
|
|
|
39616
40009
|
})
|
|
39617
40010
|
);
|
|
39618
40011
|
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 = [
|
|
40012
|
+
const SETTINGS = [
|
|
40013
|
+
"router_model",
|
|
40014
|
+
"intake_model",
|
|
40015
|
+
"intake_lane",
|
|
40016
|
+
"pool_lanes",
|
|
40017
|
+
"pool_command",
|
|
40018
|
+
"pool_idle_min",
|
|
40019
|
+
"run_cap"
|
|
40020
|
+
];
|
|
39620
40021
|
const config2 = program3.command("config").description(`board settings: statuses, task_prefix, ${SETTINGS.join(", ")}`);
|
|
39621
40022
|
config2.command("get").argument("<key>").action(
|
|
39622
40023
|
(key) => key === "statuses" ? print3(`${STATUSES.join(", ")}
|
|
@@ -39739,10 +40140,10 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39739
40140
|
program3.command("decide").argument("<id>", "decision id").argument("<answer>", "an option key, or your own words").description(
|
|
39740
40141
|
"answer a decision; each blocked task goes back to To Do with the answer in its notes"
|
|
39741
40142
|
).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) => {
|
|
40143
|
+
(id, answer, o) => withBoardOrStore(async (a, c, atomic2) => {
|
|
39743
40144
|
const d = await a.decide(Number.parseInt(id, 10), answer, ctx.by, o.why);
|
|
39744
40145
|
const spec = c && await a.getSetting("router_model");
|
|
39745
|
-
const moved = c ? await afterDecision(a, c, d, ctx.by, spec ? modelPicker(spec) : void 0) : [];
|
|
40146
|
+
const moved = c ? await afterDecision(a, c, d, ctx.by, spec ? modelPicker(spec) : void 0, atomic2) : [];
|
|
39746
40147
|
for (const b of d.blocks) await after(a, b);
|
|
39747
40148
|
if (o.json) return void print3(`${JSON.stringify({ ...d, requeued: moved })}
|
|
39748
40149
|
`);
|
|
@@ -39820,6 +40221,9 @@ ${ms2.map((m) => ` ${m.id}: ${m.title} (${m.done}/${m.total} done)`).join("\n")
|
|
|
39820
40221
|
registerIntakeCommands(program3, ctx);
|
|
39821
40222
|
}
|
|
39822
40223
|
|
|
40224
|
+
// src/bin.ts
|
|
40225
|
+
init_db2();
|
|
40226
|
+
|
|
39823
40227
|
// src/migrations.ts
|
|
39824
40228
|
init_drizzle_orm();
|
|
39825
40229
|
import { fileURLToPath } from "node:url";
|
|
@@ -39923,12 +40327,12 @@ async function open2(migrateNow) {
|
|
|
39923
40327
|
}
|
|
39924
40328
|
const { PGlite: PGlite2 } = await import("@electric-sql/pglite");
|
|
39925
40329
|
const { drizzle: drizzle3 } = await Promise.resolve().then(() => (init_pglite(), pglite_exports));
|
|
39926
|
-
const pg = new PGlite2(cfg.dir ??
|
|
40330
|
+
const pg = new PGlite2(cfg.dir ?? join7(homedir4(), ".aerie"));
|
|
39927
40331
|
const db = drizzle3(pg);
|
|
39928
40332
|
await migrateAerie(db);
|
|
39929
40333
|
return { db, close: () => pg.close() };
|
|
39930
40334
|
}
|
|
39931
|
-
var pkg = JSON.parse(
|
|
40335
|
+
var pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
39932
40336
|
var program2 = new Command("aerie").description("One task system for every agent: areas, roles, a board; Backlog.md's flags").showHelpAfterError();
|
|
39933
40337
|
registerCommands(program2, {
|
|
39934
40338
|
by: cfg.by ?? `@${userInfo().username}`,
|
|
@@ -39950,14 +40354,17 @@ registerCommands(program2, {
|
|
|
39950
40354
|
async withBoard(fn) {
|
|
39951
40355
|
const { db, close } = await open2(false);
|
|
39952
40356
|
const { pgCoord: pgCoord2 } = await Promise.resolve().then(() => (init_postgres(), postgres_exports));
|
|
39953
|
-
|
|
39954
|
-
|
|
39955
|
-
|
|
39956
|
-
db,
|
|
40357
|
+
const pair = (q) => [
|
|
40358
|
+
aerieStore({ db: q, ...cfg.prefix ? { prefix: cfg.prefix } : {} }),
|
|
40359
|
+
pgCoord2({
|
|
40360
|
+
db: q,
|
|
39957
40361
|
ns: cfg.coordNs ?? "main",
|
|
39958
40362
|
status: { doing: "In Progress", done: "Done", ready: "To Do", queued: "To Do" }
|
|
39959
|
-
})
|
|
39960
|
-
|
|
40363
|
+
})
|
|
40364
|
+
];
|
|
40365
|
+
try {
|
|
40366
|
+
const [store, coord] = pair(db);
|
|
40367
|
+
return await fn(store, coord, (f) => atomic(db, (tx) => f(...pair(tx))));
|
|
39961
40368
|
} finally {
|
|
39962
40369
|
await close();
|
|
39963
40370
|
}
|