@lesscap/baton-cli 0.3.0 → 0.3.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/baton.mjs +200 -68
- package/package.json +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -1015,6 +1015,25 @@ var channelClient = (baseUrl) => {
|
|
|
1015
1015
|
if (!res.ok) throw new Error(`POST channel message \u2192 ${res.status}: ${await res.text()}`);
|
|
1016
1016
|
return await res.json();
|
|
1017
1017
|
},
|
|
1018
|
+
uploadAttachment: async (channelId, token, input) => {
|
|
1019
|
+
const res = await fetch(
|
|
1020
|
+
u(`/channels/${channelId}/attachments?filename=${encodeURIComponent(input.filename)}`),
|
|
1021
|
+
{
|
|
1022
|
+
method: "POST",
|
|
1023
|
+
headers: { "content-type": input.contentType, ...authed(token) },
|
|
1024
|
+
body: input.body
|
|
1025
|
+
}
|
|
1026
|
+
);
|
|
1027
|
+
if (!res.ok) throw new Error(`upload attachment \u2192 ${res.status}: ${await res.text()}`);
|
|
1028
|
+
return await res.json();
|
|
1029
|
+
},
|
|
1030
|
+
downloadAttachment: async (channelId, token, attId) => {
|
|
1031
|
+
const res = await fetch(u(`/channels/${channelId}/attachments/${attId}`), {
|
|
1032
|
+
headers: authed(token)
|
|
1033
|
+
});
|
|
1034
|
+
if (!res.ok) throw new Error(`download attachment \u2192 ${res.status}: ${await res.text()}`);
|
|
1035
|
+
return res;
|
|
1036
|
+
},
|
|
1018
1037
|
read: async (channelId, token, opts) => {
|
|
1019
1038
|
const params = new URLSearchParams({ since: String(opts?.since ?? 0) });
|
|
1020
1039
|
if (opts?.for) params.set("for", opts.for);
|
|
@@ -1221,6 +1240,25 @@ var requirementClient = (baseUrl) => {
|
|
|
1221
1240
|
};
|
|
1222
1241
|
};
|
|
1223
1242
|
|
|
1243
|
+
// src/client/retry.ts
|
|
1244
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1245
|
+
var withRetry = async (fn, opts = {}) => {
|
|
1246
|
+
const tries = Math.max(1, opts.tries ?? 4);
|
|
1247
|
+
const baseMs = opts.baseMs ?? 300;
|
|
1248
|
+
let lastErr;
|
|
1249
|
+
for (let i = 0; i < tries; i++) {
|
|
1250
|
+
try {
|
|
1251
|
+
return await fn();
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
lastErr = err;
|
|
1254
|
+
if (i === tries - 1) break;
|
|
1255
|
+
const backoff = baseMs * 2 ** i;
|
|
1256
|
+
await sleep(backoff + Math.random() * baseMs);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
throw lastErr;
|
|
1260
|
+
};
|
|
1261
|
+
|
|
1224
1262
|
// src/client/sessions.ts
|
|
1225
1263
|
var sessionsClient = (baseUrl) => {
|
|
1226
1264
|
const u = (p) => `${baseUrl}${p}`;
|
|
@@ -1343,15 +1381,24 @@ var clientFromBase = (baseUrl) => ({
|
|
|
1343
1381
|
sessions: sessionsClient(baseUrl),
|
|
1344
1382
|
workers: workersClient(baseUrl)
|
|
1345
1383
|
});
|
|
1384
|
+
var BOUNDARY_EVENTS = /* @__PURE__ */ new Set([
|
|
1385
|
+
"turn_start",
|
|
1386
|
+
"turn_complete",
|
|
1387
|
+
"turn_error"
|
|
1388
|
+
]);
|
|
1389
|
+
var emitRetries = () => Number(process.env.BATON_EMIT_RETRIES) || 4;
|
|
1346
1390
|
var createWorkerClient = (baseUrl, workerToken, sessionId) => {
|
|
1347
1391
|
const u = (p) => `${baseUrl}${p}`;
|
|
1348
1392
|
const auth = { authorization: `Bearer ${workerToken}` };
|
|
1349
1393
|
return {
|
|
1350
|
-
emitEvent: (type, payload) =>
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1394
|
+
emitEvent: (type, payload) => {
|
|
1395
|
+
const post = () => request(u(`/sessions/${sessionId}/events`), {
|
|
1396
|
+
method: "POST",
|
|
1397
|
+
body: { type, payload },
|
|
1398
|
+
headers: auth
|
|
1399
|
+
});
|
|
1400
|
+
return BOUNDARY_EVENTS.has(type) ? withRetry(post, { tries: emitRetries() }) : post();
|
|
1401
|
+
},
|
|
1355
1402
|
setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
|
|
1356
1403
|
method: "POST",
|
|
1357
1404
|
body: { active },
|
|
@@ -1422,16 +1469,23 @@ var readStdin = async () => {
|
|
|
1422
1469
|
return Buffer.concat(chunks).toString("utf8");
|
|
1423
1470
|
};
|
|
1424
1471
|
var renderRoster = (members) => members.length ? members.map((m) => ` ${m.name} (${m.kind})`).join("\n") : " (nobody online)";
|
|
1425
|
-
var
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
"
|
|
1434
|
-
|
|
1472
|
+
var webLink = (url, channelId, token) => {
|
|
1473
|
+
const origin = url.replace(/\/api\/?$/, "");
|
|
1474
|
+
return origin === url ? null : `${origin}/channel/${channelId}#token=${token}`;
|
|
1475
|
+
};
|
|
1476
|
+
var inviteBlock = (url, channelId, token) => {
|
|
1477
|
+
const web = webLink(url, channelId, token);
|
|
1478
|
+
return [
|
|
1479
|
+
"\u2500\u2500 Share this to invite anyone into the room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
|
|
1480
|
+
...web ? [`Humans \u2014 open in a browser: ${web}`, ""] : [],
|
|
1481
|
+
"Agents \u2014 two curl steps:",
|
|
1482
|
+
` 1) see the room: curl -sS -H "authorization: Bearer ${token}" "${url}/channels/${channelId}"`,
|
|
1483
|
+
` 2) read protocol: curl -sS "${url}/channels/help" (then join / listen / send, pick your own NAME)`,
|
|
1484
|
+
"",
|
|
1485
|
+
`connection: url=${url} channel=${channelId} token=${token}`,
|
|
1486
|
+
"\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"
|
|
1487
|
+
].join("\n");
|
|
1488
|
+
};
|
|
1435
1489
|
|
|
1436
1490
|
// src/commands/channel/create.ts
|
|
1437
1491
|
var createCommand = defineCommand({
|
|
@@ -1454,6 +1508,26 @@ var createCommand = defineCommand({
|
|
|
1454
1508
|
}
|
|
1455
1509
|
});
|
|
1456
1510
|
|
|
1511
|
+
// src/commands/channel/download.ts
|
|
1512
|
+
import { writeFileSync as writeFileSync2 } from "node:fs";
|
|
1513
|
+
var downloadCommand = defineCommand({
|
|
1514
|
+
meta: { name: "download", description: "download a channel attachment to a file" },
|
|
1515
|
+
args: {
|
|
1516
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1517
|
+
att: { type: "positional", required: true, description: "attachment id (from a message ref)" },
|
|
1518
|
+
token: { type: "string", required: true, description: "channel token" },
|
|
1519
|
+
out: { type: "string", alias: "o", description: "output file path (default: ./<attId>)" },
|
|
1520
|
+
...common
|
|
1521
|
+
},
|
|
1522
|
+
run: async ({ args }) => {
|
|
1523
|
+
const url = resolveBaseUrl(args.url);
|
|
1524
|
+
const res = await channelClient(url).downloadAttachment(args.channel, args.token, args.att);
|
|
1525
|
+
const out = args.out ?? args.att;
|
|
1526
|
+
writeFileSync2(out, Buffer.from(await res.arrayBuffer()));
|
|
1527
|
+
console.log(`saved \u2192 ${out}`);
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1457
1531
|
// src/commands/channel/info.ts
|
|
1458
1532
|
var aboutCommand = defineCommand({
|
|
1459
1533
|
meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
|
|
@@ -1520,7 +1594,7 @@ var updateCommand = defineCommand({
|
|
|
1520
1594
|
});
|
|
1521
1595
|
|
|
1522
1596
|
// src/commands/channel/listen.ts
|
|
1523
|
-
import { mkdirSync, writeFileSync as
|
|
1597
|
+
import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1524
1598
|
import { tmpdir } from "node:os";
|
|
1525
1599
|
import { join as join2 } from "node:path";
|
|
1526
1600
|
|
|
@@ -1552,6 +1626,12 @@ var unstartedUserMessages = (events) => {
|
|
|
1552
1626
|
const started = startedMessageIds(events);
|
|
1553
1627
|
return events.filter((e) => e.type === "user_message" && !started.has(e.id));
|
|
1554
1628
|
};
|
|
1629
|
+
var opensTurn = (e) => e.type === "user_message" || e.type === "turn_start";
|
|
1630
|
+
var closesTurn = (e) => e.type === "turn_complete" || e.type === "turn_error";
|
|
1631
|
+
var isAgentWorking = (events) => {
|
|
1632
|
+
const last = events.findLast((e) => opensTurn(e) || closesTurn(e));
|
|
1633
|
+
return last ? opensTurn(last) : false;
|
|
1634
|
+
};
|
|
1555
1635
|
|
|
1556
1636
|
// src/commands/channel/listen.ts
|
|
1557
1637
|
var PREVIEW = 280;
|
|
@@ -1569,7 +1649,7 @@ var runListen = async (o) => {
|
|
|
1569
1649
|
const surface = (m) => {
|
|
1570
1650
|
const text = typeof m.text === "string" ? m.text : "";
|
|
1571
1651
|
const file = join2(spillDir, `${m.seq}.txt`);
|
|
1572
|
-
|
|
1652
|
+
writeFileSync3(file, text);
|
|
1573
1653
|
const truncated = text.length > PREVIEW;
|
|
1574
1654
|
emit({
|
|
1575
1655
|
type: "channel.message",
|
|
@@ -1582,6 +1662,13 @@ var runListen = async (o) => {
|
|
|
1582
1662
|
chars: text.length,
|
|
1583
1663
|
truncated,
|
|
1584
1664
|
preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
|
|
1665
|
+
// BASE-prefixed urls so the subscribing agent can fetch directly.
|
|
1666
|
+
attachments: m.attachments?.length ? m.attachments.map((a) => ({
|
|
1667
|
+
filename: a.filename,
|
|
1668
|
+
contentType: a.contentType,
|
|
1669
|
+
size: a.size,
|
|
1670
|
+
url: `${o.url}${a.url}`
|
|
1671
|
+
})) : void 0,
|
|
1585
1672
|
file
|
|
1586
1673
|
});
|
|
1587
1674
|
};
|
|
@@ -1663,6 +1750,12 @@ ${renderRoster(members)}`);
|
|
|
1663
1750
|
});
|
|
1664
1751
|
|
|
1665
1752
|
// src/commands/channel/read.ts
|
|
1753
|
+
var fmtMessage = (m, base) => {
|
|
1754
|
+
const head = `${m.seq} ${m.from} ${m.text}`;
|
|
1755
|
+
const atts = (m.attachments ?? []).map((a) => `
|
|
1756
|
+
\u{1F4CE} ${a.filename} \u2192 ${base}${a.url}`).join("");
|
|
1757
|
+
return head + atts;
|
|
1758
|
+
};
|
|
1666
1759
|
var readCommand = defineCommand({
|
|
1667
1760
|
meta: { name: "read", description: "poll channel messages once (curl-style, since a cursor)" },
|
|
1668
1761
|
args: {
|
|
@@ -1681,12 +1774,51 @@ var readCommand = defineCommand({
|
|
|
1681
1774
|
});
|
|
1682
1775
|
const out = args.from ? msgs.filter((m) => m.from !== args.from) : msgs;
|
|
1683
1776
|
if (args.json) console.log(toJson(out));
|
|
1684
|
-
else console.log(out.length ? out.map((m) =>
|
|
1777
|
+
else console.log(out.length ? out.map((m) => fmtMessage(m, url)).join("\n") : "(none)");
|
|
1685
1778
|
}
|
|
1686
1779
|
});
|
|
1687
1780
|
|
|
1688
1781
|
// src/commands/channel/send.ts
|
|
1689
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1782
|
+
import { openAsBlob as openAsBlob2, readFileSync as readFileSync2 } from "node:fs";
|
|
1783
|
+
import { basename as basename2 } from "node:path";
|
|
1784
|
+
|
|
1785
|
+
// src/mime.ts
|
|
1786
|
+
import { extname } from "node:path";
|
|
1787
|
+
var BY_EXT = {
|
|
1788
|
+
".png": "image/png",
|
|
1789
|
+
".jpg": "image/jpeg",
|
|
1790
|
+
".jpeg": "image/jpeg",
|
|
1791
|
+
".gif": "image/gif",
|
|
1792
|
+
".webp": "image/webp",
|
|
1793
|
+
".svg": "image/svg+xml",
|
|
1794
|
+
".pdf": "application/pdf",
|
|
1795
|
+
".txt": "text/plain",
|
|
1796
|
+
".md": "text/markdown",
|
|
1797
|
+
".json": "application/json",
|
|
1798
|
+
".csv": "text/csv"
|
|
1799
|
+
};
|
|
1800
|
+
var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
1801
|
+
|
|
1802
|
+
// src/commands/attach.ts
|
|
1803
|
+
import { openAsBlob } from "node:fs";
|
|
1804
|
+
import { basename } from "node:path";
|
|
1805
|
+
var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
|
|
1806
|
+
var uploadAttachments = async (client, sessionId, paths) => {
|
|
1807
|
+
const out = [];
|
|
1808
|
+
for (const path of paths) {
|
|
1809
|
+
const body = await openAsBlob(path, { type: contentTypeForPath(path) });
|
|
1810
|
+
out.push(
|
|
1811
|
+
await client.sessions.uploadAttachment(sessionId, {
|
|
1812
|
+
filename: basename(path),
|
|
1813
|
+
contentType: contentTypeForPath(path),
|
|
1814
|
+
body
|
|
1815
|
+
})
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
return out;
|
|
1819
|
+
};
|
|
1820
|
+
|
|
1821
|
+
// src/commands/channel/send.ts
|
|
1690
1822
|
var sendCommand = defineCommand({
|
|
1691
1823
|
meta: { name: "send", description: "post one message to a channel" },
|
|
1692
1824
|
args: {
|
|
@@ -1696,19 +1828,32 @@ var sendCommand = defineCommand({
|
|
|
1696
1828
|
from: { type: "string", description: "your participant name (default: peer)" },
|
|
1697
1829
|
to: { type: "string", description: "direct it at these names (comma-separated); omit = broadcast" },
|
|
1698
1830
|
file: { type: "string", description: "read message body from this file (for large content)" },
|
|
1831
|
+
attach: { type: "string", description: "attach file(s) (comma-separated paths); uploaded + carried on the message" },
|
|
1699
1832
|
...common
|
|
1700
1833
|
},
|
|
1701
1834
|
run: async ({ args }) => {
|
|
1702
1835
|
const url = resolveBaseUrl(args.url);
|
|
1836
|
+
const client = channelClient(url);
|
|
1703
1837
|
let text = args.text;
|
|
1704
1838
|
if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
|
|
1705
1839
|
if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1840
|
+
const attachments = [];
|
|
1841
|
+
for (const path of attachPaths(args.attach)) {
|
|
1842
|
+
attachments.push(
|
|
1843
|
+
await client.uploadAttachment(args.channel, args.token, {
|
|
1844
|
+
filename: basename2(path),
|
|
1845
|
+
contentType: contentTypeForPath(path),
|
|
1846
|
+
body: await openAsBlob2(path, { type: contentTypeForPath(path) })
|
|
1847
|
+
})
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
if (!text && attachments.length === 0)
|
|
1851
|
+
throw new Error("nothing to send: provide text, --file <path>, --attach <file>, or pipe stdin");
|
|
1852
|
+
const msg = await client.send(args.channel, args.token, {
|
|
1709
1853
|
from: args.from ?? "peer",
|
|
1710
|
-
text,
|
|
1711
|
-
to: splitCsv(args.to)
|
|
1854
|
+
text: text ?? "",
|
|
1855
|
+
to: splitCsv(args.to),
|
|
1856
|
+
attachments: attachments.length ? attachments : void 0
|
|
1712
1857
|
});
|
|
1713
1858
|
if (args.json) console.log(toJson(msg));
|
|
1714
1859
|
else console.log(`sent (seq ${msg.seq})`);
|
|
@@ -1719,7 +1864,7 @@ var sendCommand = defineCommand({
|
|
|
1719
1864
|
var channel = defineCommand({
|
|
1720
1865
|
meta: {
|
|
1721
1866
|
name: "channel",
|
|
1722
|
-
description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / close"
|
|
1867
|
+
description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / download / close"
|
|
1723
1868
|
},
|
|
1724
1869
|
subCommands: {
|
|
1725
1870
|
create: createCommand,
|
|
@@ -1731,6 +1876,7 @@ var channel = defineCommand({
|
|
|
1731
1876
|
send: sendCommand,
|
|
1732
1877
|
read: readCommand,
|
|
1733
1878
|
listen: listenCommand,
|
|
1879
|
+
download: downloadCommand,
|
|
1734
1880
|
close: closeCommand
|
|
1735
1881
|
}
|
|
1736
1882
|
});
|
|
@@ -1874,7 +2020,7 @@ var project = defineCommand({
|
|
|
1874
2020
|
});
|
|
1875
2021
|
|
|
1876
2022
|
// src/commands/relay.ts
|
|
1877
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as
|
|
2023
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1878
2024
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1879
2025
|
import { join as join3 } from "node:path";
|
|
1880
2026
|
|
|
@@ -2009,7 +2155,7 @@ var listenCommand2 = defineCommand({
|
|
|
2009
2155
|
const surface = (m) => {
|
|
2010
2156
|
const text = typeof m.text === "string" ? m.text : "";
|
|
2011
2157
|
const file = join3(spillDir, `${m.seq}.txt`);
|
|
2012
|
-
|
|
2158
|
+
writeFileSync4(file, text);
|
|
2013
2159
|
const truncated = text.length > PREVIEW2;
|
|
2014
2160
|
emit({
|
|
2015
2161
|
type: "relay.message",
|
|
@@ -2506,6 +2652,7 @@ var toTurnResult = (message) => {
|
|
|
2506
2652
|
|
|
2507
2653
|
// src/session/runner/turn.ts
|
|
2508
2654
|
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
2655
|
+
var heartbeatMs = () => Number(process.env.BATON_TURN_HEARTBEAT_MS) || 3e4;
|
|
2509
2656
|
var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
|
|
2510
2657
|
var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
|
|
2511
2658
|
var finalizeTurn = async (result, worker2, log) => {
|
|
@@ -2558,6 +2705,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2558
2705
|
const abort = new AbortController();
|
|
2559
2706
|
const ceiling = turnTimeoutMs();
|
|
2560
2707
|
let timer;
|
|
2708
|
+
let heartbeat;
|
|
2561
2709
|
const timeout = new Promise((resolve2) => {
|
|
2562
2710
|
timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
|
|
2563
2711
|
});
|
|
@@ -2566,6 +2714,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2566
2714
|
else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
|
|
2567
2715
|
});
|
|
2568
2716
|
try {
|
|
2717
|
+
heartbeat = setInterval(() => {
|
|
2718
|
+
void worker2.emitEvent("turn_heartbeat", {}).catch((e) => log(`[heartbeat] ${String(e)}`));
|
|
2719
|
+
}, heartbeatMs());
|
|
2569
2720
|
const messages = startQuery(config, text, resuming, queryFn, abort, log, {
|
|
2570
2721
|
envOverlay,
|
|
2571
2722
|
planMode,
|
|
@@ -2600,6 +2751,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2600
2751
|
return -1;
|
|
2601
2752
|
} finally {
|
|
2602
2753
|
clearTimeout(timer);
|
|
2754
|
+
clearInterval(heartbeat);
|
|
2603
2755
|
}
|
|
2604
2756
|
};
|
|
2605
2757
|
|
|
@@ -2680,10 +2832,17 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2680
2832
|
busy = false;
|
|
2681
2833
|
}
|
|
2682
2834
|
};
|
|
2835
|
+
const closeOrphanedTurn = async (reason) => {
|
|
2836
|
+
const events = await deps.worker.listEvents();
|
|
2837
|
+
if (!isAgentWorking(events)) return;
|
|
2838
|
+
await deps.worker.emitEvent("turn_error", { message: reason, synthetic: true });
|
|
2839
|
+
log(`closed orphaned open turn (${reason})`);
|
|
2840
|
+
};
|
|
2683
2841
|
const reconcile = async () => {
|
|
2684
2842
|
reconciling = true;
|
|
2685
2843
|
try {
|
|
2686
|
-
const
|
|
2844
|
+
const events = await deps.worker.listEvents();
|
|
2845
|
+
const pending = unstartedUserMessages(events);
|
|
2687
2846
|
let added = 0;
|
|
2688
2847
|
for (const ev of pending) {
|
|
2689
2848
|
if (state.seen.has(ev.sequence)) continue;
|
|
@@ -2695,6 +2854,12 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2695
2854
|
log(`reconciled ${added} queued message(s) from transcript`);
|
|
2696
2855
|
lastActivity = Date.now();
|
|
2697
2856
|
void drain();
|
|
2857
|
+
} else if (pendingQueue.length === 0 && !busy && !currentTurn && isAgentWorking(events)) {
|
|
2858
|
+
await deps.worker.emitEvent("turn_error", {
|
|
2859
|
+
message: "turn abandoned \u2014 runner restarted",
|
|
2860
|
+
synthetic: true
|
|
2861
|
+
});
|
|
2862
|
+
log("reconcile: closed orphaned open turn from a prior child");
|
|
2698
2863
|
}
|
|
2699
2864
|
} catch (e) {
|
|
2700
2865
|
log(`reconcile failed: ${String(e)}`);
|
|
@@ -2719,6 +2884,11 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2719
2884
|
if (currentTurn) {
|
|
2720
2885
|
log("interrupt \u2014 aborting current turn");
|
|
2721
2886
|
currentTurn.abort();
|
|
2887
|
+
} else {
|
|
2888
|
+
log("interrupt \u2014 no live turn; closing any orphaned open turn");
|
|
2889
|
+
void closeOrphanedTurn("interrupted by user").catch(
|
|
2890
|
+
(e) => log(`orphan close failed: ${String(e)}`)
|
|
2891
|
+
);
|
|
2722
2892
|
}
|
|
2723
2893
|
},
|
|
2724
2894
|
// Report active only now that we're subscribed — before this, a message
|
|
@@ -2811,44 +2981,6 @@ var sessionRunCommand = defineCommand({
|
|
|
2811
2981
|
}
|
|
2812
2982
|
});
|
|
2813
2983
|
|
|
2814
|
-
// src/commands/attach.ts
|
|
2815
|
-
import { openAsBlob } from "node:fs";
|
|
2816
|
-
import { basename } from "node:path";
|
|
2817
|
-
|
|
2818
|
-
// src/mime.ts
|
|
2819
|
-
import { extname } from "node:path";
|
|
2820
|
-
var BY_EXT = {
|
|
2821
|
-
".png": "image/png",
|
|
2822
|
-
".jpg": "image/jpeg",
|
|
2823
|
-
".jpeg": "image/jpeg",
|
|
2824
|
-
".gif": "image/gif",
|
|
2825
|
-
".webp": "image/webp",
|
|
2826
|
-
".svg": "image/svg+xml",
|
|
2827
|
-
".pdf": "application/pdf",
|
|
2828
|
-
".txt": "text/plain",
|
|
2829
|
-
".md": "text/markdown",
|
|
2830
|
-
".json": "application/json",
|
|
2831
|
-
".csv": "text/csv"
|
|
2832
|
-
};
|
|
2833
|
-
var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
2834
|
-
|
|
2835
|
-
// src/commands/attach.ts
|
|
2836
|
-
var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
|
|
2837
|
-
var uploadAttachments = async (client, sessionId, paths) => {
|
|
2838
|
-
const out = [];
|
|
2839
|
-
for (const path of paths) {
|
|
2840
|
-
const body = await openAsBlob(path, { type: contentTypeForPath(path) });
|
|
2841
|
-
out.push(
|
|
2842
|
-
await client.sessions.uploadAttachment(sessionId, {
|
|
2843
|
-
filename: basename(path),
|
|
2844
|
-
contentType: contentTypeForPath(path),
|
|
2845
|
-
body
|
|
2846
|
-
})
|
|
2847
|
-
);
|
|
2848
|
-
}
|
|
2849
|
-
return out;
|
|
2850
|
-
};
|
|
2851
|
-
|
|
2852
2984
|
// src/commands/session/send.ts
|
|
2853
2985
|
var sessionSendCommand = defineCommand({
|
|
2854
2986
|
meta: { name: "send", description: "post a chat message into a session (no streaming reply)" },
|
|
@@ -3421,7 +3553,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
3421
3553
|
|
|
3422
3554
|
// src/worker/machine-id.ts
|
|
3423
3555
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3424
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
3556
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3425
3557
|
import { homedir as homedir3 } from "node:os";
|
|
3426
3558
|
import { dirname as dirname3, join as join8 } from "node:path";
|
|
3427
3559
|
var machineIdPath = (env = process.env) => join8(env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
|
|
@@ -3432,7 +3564,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
|
3432
3564
|
}
|
|
3433
3565
|
const id = randomUUID2();
|
|
3434
3566
|
mkdirSync4(dirname3(path), { recursive: true });
|
|
3435
|
-
|
|
3567
|
+
writeFileSync5(path, `${id}
|
|
3436
3568
|
`, "utf8");
|
|
3437
3569
|
return id;
|
|
3438
3570
|
};
|