@lesscap/baton-cli 0.3.0 → 0.3.2
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 +301 -135
- package/package.json +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -949,28 +949,33 @@ function getBaseURL() {
|
|
|
949
949
|
// src/client/channel.ts
|
|
950
950
|
var channelClient = (baseUrl) => {
|
|
951
951
|
const u = (p) => `${baseUrl}${p}`;
|
|
952
|
-
const
|
|
953
|
-
const sendJson = (token) => ({ "content-type": "application/json", ...authed(token) });
|
|
952
|
+
const JSON_CT = { "content-type": "application/json" };
|
|
954
953
|
const member = (channelId, name) => u(`/channels/${channelId}/members/${encodeURIComponent(name)}`);
|
|
955
954
|
return {
|
|
956
|
-
create: async (title, description) => {
|
|
957
|
-
const res = await fetch(u(
|
|
955
|
+
create: async (workspaceId, title, description, token) => {
|
|
956
|
+
const res = await fetch(u(`/workspaces/${workspaceId}/channels`), {
|
|
958
957
|
method: "POST",
|
|
959
|
-
headers: {
|
|
960
|
-
body: JSON.stringify({
|
|
958
|
+
headers: { ...JSON_CT, ...token ? { authorization: `Bearer ${token}` } : {} },
|
|
959
|
+
body: JSON.stringify({
|
|
960
|
+
...title ? { title } : {},
|
|
961
|
+
...description ? { description } : {}
|
|
962
|
+
})
|
|
961
963
|
});
|
|
962
|
-
if (!res.ok)
|
|
964
|
+
if (!res.ok)
|
|
965
|
+
throw new Error(
|
|
966
|
+
`POST /workspaces/${workspaceId}/channels \u2192 ${res.status}: ${await res.text()}`
|
|
967
|
+
);
|
|
963
968
|
return await res.json();
|
|
964
969
|
},
|
|
965
|
-
manifest: async (channelId
|
|
966
|
-
const res = await fetch(u(`/channels/${channelId}`)
|
|
970
|
+
manifest: async (channelId) => {
|
|
971
|
+
const res = await fetch(u(`/channels/${channelId}`));
|
|
967
972
|
if (!res.ok) throw new Error(`GET channel \u2192 ${res.status}: ${await res.text()}`);
|
|
968
973
|
return await res.json();
|
|
969
974
|
},
|
|
970
|
-
update: async (channelId,
|
|
975
|
+
update: async (channelId, patch2) => {
|
|
971
976
|
const res = await fetch(u(`/channels/${channelId}`), {
|
|
972
977
|
method: "PATCH",
|
|
973
|
-
headers:
|
|
978
|
+
headers: JSON_CT,
|
|
974
979
|
body: JSON.stringify(patch2)
|
|
975
980
|
});
|
|
976
981
|
if (!res.ok) throw new Error(`PATCH channel \u2192 ${res.status}: ${await res.text()}`);
|
|
@@ -981,60 +986,63 @@ var channelClient = (baseUrl) => {
|
|
|
981
986
|
if (!res.ok) throw new Error(`GET /channels/help \u2192 ${res.status}: ${await res.text()}`);
|
|
982
987
|
return await res.text();
|
|
983
988
|
},
|
|
984
|
-
destroy: async (channelId
|
|
985
|
-
const res = await fetch(u(`/channels/${channelId}`), {
|
|
986
|
-
method: "DELETE",
|
|
987
|
-
headers: authed(token)
|
|
988
|
-
});
|
|
989
|
+
destroy: async (channelId) => {
|
|
990
|
+
const res = await fetch(u(`/channels/${channelId}`), { method: "DELETE" });
|
|
989
991
|
if (!res.ok) throw new Error(`DELETE channel \u2192 ${res.status}: ${await res.text()}`);
|
|
990
992
|
},
|
|
991
|
-
join: async (channelId,
|
|
993
|
+
join: async (channelId, name, kind) => {
|
|
992
994
|
const res = await fetch(member(channelId, name), {
|
|
993
995
|
method: "PUT",
|
|
994
|
-
headers:
|
|
996
|
+
headers: JSON_CT,
|
|
995
997
|
body: JSON.stringify({ kind: kind ?? "agent" })
|
|
996
998
|
});
|
|
997
999
|
if (!res.ok) throw new Error(`join channel \u2192 ${res.status}: ${await res.text()}`);
|
|
998
1000
|
return (await res.json()).members;
|
|
999
1001
|
},
|
|
1000
|
-
leave: async (channelId,
|
|
1001
|
-
const res = await fetch(member(channelId, name), { method: "DELETE"
|
|
1002
|
+
leave: async (channelId, name) => {
|
|
1003
|
+
const res = await fetch(member(channelId, name), { method: "DELETE" });
|
|
1002
1004
|
if (!res.ok) throw new Error(`leave channel \u2192 ${res.status}: ${await res.text()}`);
|
|
1003
1005
|
},
|
|
1004
|
-
members: async (channelId
|
|
1005
|
-
const res = await fetch(u(`/channels/${channelId}/members`)
|
|
1006
|
+
members: async (channelId) => {
|
|
1007
|
+
const res = await fetch(u(`/channels/${channelId}/members`));
|
|
1006
1008
|
if (!res.ok) throw new Error(`GET members \u2192 ${res.status}: ${await res.text()}`);
|
|
1007
1009
|
return (await res.json()).members;
|
|
1008
1010
|
},
|
|
1009
|
-
send: async (channelId,
|
|
1011
|
+
send: async (channelId, msg) => {
|
|
1010
1012
|
const res = await fetch(u(`/channels/${channelId}/messages`), {
|
|
1011
1013
|
method: "POST",
|
|
1012
|
-
headers:
|
|
1014
|
+
headers: JSON_CT,
|
|
1013
1015
|
body: JSON.stringify(msg)
|
|
1014
1016
|
});
|
|
1015
1017
|
if (!res.ok) throw new Error(`POST channel message \u2192 ${res.status}: ${await res.text()}`);
|
|
1016
1018
|
return await res.json();
|
|
1017
1019
|
},
|
|
1018
|
-
|
|
1020
|
+
uploadAttachment: async (channelId, input) => {
|
|
1021
|
+
const res = await fetch(
|
|
1022
|
+
u(`/channels/${channelId}/attachments?filename=${encodeURIComponent(input.filename)}`),
|
|
1023
|
+
{ method: "POST", headers: { "content-type": input.contentType }, body: input.body }
|
|
1024
|
+
);
|
|
1025
|
+
if (!res.ok) throw new Error(`upload attachment \u2192 ${res.status}: ${await res.text()}`);
|
|
1026
|
+
return await res.json();
|
|
1027
|
+
},
|
|
1028
|
+
downloadAttachment: async (channelId, attId) => {
|
|
1029
|
+
const res = await fetch(u(`/channels/${channelId}/attachments/${attId}`));
|
|
1030
|
+
if (!res.ok) throw new Error(`download attachment \u2192 ${res.status}: ${await res.text()}`);
|
|
1031
|
+
return res;
|
|
1032
|
+
},
|
|
1033
|
+
read: async (channelId, opts) => {
|
|
1019
1034
|
const params = new URLSearchParams({ since: String(opts?.since ?? 0) });
|
|
1020
1035
|
if (opts?.for) params.set("for", opts.for);
|
|
1021
|
-
const res = await fetch(u(`/channels/${channelId}/messages?${params}`)
|
|
1022
|
-
headers: authed(token)
|
|
1023
|
-
});
|
|
1036
|
+
const res = await fetch(u(`/channels/${channelId}/messages?${params}`));
|
|
1024
1037
|
if (!res.ok) throw new Error(`GET messages \u2192 ${res.status}: ${await res.text()}`);
|
|
1025
1038
|
return (await res.json()).messages;
|
|
1026
1039
|
},
|
|
1027
|
-
listen: (channelId,
|
|
1040
|
+
listen: (channelId, opts) => {
|
|
1028
1041
|
let cursor = opts.since ?? 0;
|
|
1029
1042
|
const params = new URLSearchParams({ since: String(cursor) });
|
|
1030
1043
|
if (opts.for) params.set("for", opts.for);
|
|
1031
1044
|
if (opts.as) params.set("as", opts.as);
|
|
1032
|
-
const es = new EventSource(u(`/channels/${channelId}/stream?${params}`)
|
|
1033
|
-
fetch: (url, init2) => fetch(url, {
|
|
1034
|
-
...init2,
|
|
1035
|
-
headers: { ...init2?.headers, ...authed(token) }
|
|
1036
|
-
})
|
|
1037
|
-
});
|
|
1045
|
+
const es = new EventSource(u(`/channels/${channelId}/stream?${params}`));
|
|
1038
1046
|
es.onmessage = (e) => {
|
|
1039
1047
|
try {
|
|
1040
1048
|
const m = JSON.parse(e.data);
|
|
@@ -1126,7 +1134,7 @@ var fmtTask = (t) => `${t.code} [${t.status}] ${t.title}`;
|
|
|
1126
1134
|
var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you"} ${c.body}`;
|
|
1127
1135
|
var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.worktreePath ?? "(pending)"}`;
|
|
1128
1136
|
var fmtWorker = (w) => {
|
|
1129
|
-
const offline = w.
|
|
1137
|
+
const offline = w.connected === false ? " [offline]" : "";
|
|
1130
1138
|
return `W-${w.id} ${w.name} ${w.hostname}${offline}`;
|
|
1131
1139
|
};
|
|
1132
1140
|
var renderOne = (item, fmt, json) => json ? toJson(item) : fmt(item);
|
|
@@ -1221,6 +1229,25 @@ var requirementClient = (baseUrl) => {
|
|
|
1221
1229
|
};
|
|
1222
1230
|
};
|
|
1223
1231
|
|
|
1232
|
+
// src/client/retry.ts
|
|
1233
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1234
|
+
var withRetry = async (fn, opts = {}) => {
|
|
1235
|
+
const tries = Math.max(1, opts.tries ?? 4);
|
|
1236
|
+
const baseMs = opts.baseMs ?? 300;
|
|
1237
|
+
let lastErr;
|
|
1238
|
+
for (let i = 0; i < tries; i++) {
|
|
1239
|
+
try {
|
|
1240
|
+
return await fn();
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
lastErr = err;
|
|
1243
|
+
if (i === tries - 1) break;
|
|
1244
|
+
const backoff = baseMs * 2 ** i;
|
|
1245
|
+
await sleep(backoff + Math.random() * baseMs);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
throw lastErr;
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1224
1251
|
// src/client/sessions.ts
|
|
1225
1252
|
var sessionsClient = (baseUrl) => {
|
|
1226
1253
|
const u = (p) => `${baseUrl}${p}`;
|
|
@@ -1343,15 +1370,24 @@ var clientFromBase = (baseUrl) => ({
|
|
|
1343
1370
|
sessions: sessionsClient(baseUrl),
|
|
1344
1371
|
workers: workersClient(baseUrl)
|
|
1345
1372
|
});
|
|
1373
|
+
var BOUNDARY_EVENTS = /* @__PURE__ */ new Set([
|
|
1374
|
+
"turn_start",
|
|
1375
|
+
"turn_complete",
|
|
1376
|
+
"turn_error"
|
|
1377
|
+
]);
|
|
1378
|
+
var emitRetries = () => Number(process.env.BATON_EMIT_RETRIES) || 4;
|
|
1346
1379
|
var createWorkerClient = (baseUrl, workerToken, sessionId) => {
|
|
1347
1380
|
const u = (p) => `${baseUrl}${p}`;
|
|
1348
1381
|
const auth = { authorization: `Bearer ${workerToken}` };
|
|
1349
1382
|
return {
|
|
1350
|
-
emitEvent: (type, payload) =>
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1383
|
+
emitEvent: (type, payload) => {
|
|
1384
|
+
const post = () => request(u(`/sessions/${sessionId}/events`), {
|
|
1385
|
+
method: "POST",
|
|
1386
|
+
body: { type, payload },
|
|
1387
|
+
headers: auth
|
|
1388
|
+
});
|
|
1389
|
+
return BOUNDARY_EVENTS.has(type) ? withRetry(post, { tries: emitRetries() }) : post();
|
|
1390
|
+
},
|
|
1355
1391
|
setActive: (active) => request(u(`/sessions/${sessionId}/status`), {
|
|
1356
1392
|
method: "POST",
|
|
1357
1393
|
body: { active },
|
|
@@ -1381,6 +1417,11 @@ var clientFor = (args) => {
|
|
|
1381
1417
|
const auth = resolveAuth(process.env, fileToken);
|
|
1382
1418
|
return createClient(baseUrl, auth && auth !== "cookie" ? auth : void 0);
|
|
1383
1419
|
};
|
|
1420
|
+
var resolveBearer = () => {
|
|
1421
|
+
const fileToken = loadProjectConfigOrNull(projectConfigPath())?.worker?.apiToken;
|
|
1422
|
+
const auth = resolveAuth(process.env, fileToken);
|
|
1423
|
+
return auth && auth !== "cookie" ? auth.bearer : void 0;
|
|
1424
|
+
};
|
|
1384
1425
|
var resolveProjectId = (args) => {
|
|
1385
1426
|
if (args.project !== void 0 && args.project !== "") return Number(args.project);
|
|
1386
1427
|
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
@@ -1406,11 +1447,10 @@ var closeCommand = defineCommand({
|
|
|
1406
1447
|
meta: { name: "close", description: "delete a channel (removes the room and all its messages)" },
|
|
1407
1448
|
args: {
|
|
1408
1449
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1409
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1410
1450
|
...common
|
|
1411
1451
|
},
|
|
1412
1452
|
run: async ({ args }) => {
|
|
1413
|
-
await channelClient(resolveBaseUrl(args.url)).destroy(args.channel
|
|
1453
|
+
await channelClient(resolveBaseUrl(args.url)).destroy(args.channel);
|
|
1414
1454
|
console.log(args.json ? toJson({ ok: true, deleted: args.channel }) : `closed ${args.channel}`);
|
|
1415
1455
|
}
|
|
1416
1456
|
});
|
|
@@ -1422,35 +1462,77 @@ var readStdin = async () => {
|
|
|
1422
1462
|
return Buffer.concat(chunks).toString("utf8");
|
|
1423
1463
|
};
|
|
1424
1464
|
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
|
-
|
|
1465
|
+
var webLink = (url, channelId) => {
|
|
1466
|
+
const origin = url.replace(/\/api\/?$/, "");
|
|
1467
|
+
return origin === url ? null : `${origin}/channel/${channelId}`;
|
|
1468
|
+
};
|
|
1469
|
+
var inviteBlock = (url, channelId) => {
|
|
1470
|
+
const web = webLink(url, channelId);
|
|
1471
|
+
return [
|
|
1472
|
+
"\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",
|
|
1473
|
+
...web ? [`Humans \u2014 open in a browser: ${web}`, ""] : [],
|
|
1474
|
+
"Agents \u2014 two curl steps:",
|
|
1475
|
+
` 1) see the room: curl -sS "${url}/channels/${channelId}"`,
|
|
1476
|
+
` 2) read protocol: curl -sS "${url}/channels/help" (then join / listen / send, pick your own NAME)`,
|
|
1477
|
+
"",
|
|
1478
|
+
`connection: url=${url} channel=${channelId}`,
|
|
1479
|
+
"\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"
|
|
1480
|
+
].join("\n");
|
|
1481
|
+
};
|
|
1435
1482
|
|
|
1436
1483
|
// src/commands/channel/create.ts
|
|
1437
1484
|
var createCommand = defineCommand({
|
|
1438
|
-
meta: {
|
|
1485
|
+
meta: {
|
|
1486
|
+
name: "create",
|
|
1487
|
+
description: "open a channel (chat room) in a workspace and print an invite"
|
|
1488
|
+
},
|
|
1439
1489
|
args: {
|
|
1490
|
+
workspace: {
|
|
1491
|
+
type: "string",
|
|
1492
|
+
description: "owning workspace id (or the cwd .baton.json workspace)"
|
|
1493
|
+
},
|
|
1440
1494
|
name: { type: "string", description: "room title (optional)" },
|
|
1441
|
-
desc: {
|
|
1495
|
+
desc: {
|
|
1496
|
+
type: "string",
|
|
1497
|
+
description: "room self-description: purpose / topic / how to participate"
|
|
1498
|
+
},
|
|
1442
1499
|
...common
|
|
1443
1500
|
},
|
|
1444
1501
|
run: async ({ args }) => {
|
|
1445
1502
|
const url = resolveBaseUrl(args.url);
|
|
1446
|
-
const
|
|
1503
|
+
const workspaceId = resolveWorkspaceId(args);
|
|
1504
|
+
const { channelId } = await channelClient(url).create(
|
|
1505
|
+
workspaceId,
|
|
1506
|
+
args.name,
|
|
1507
|
+
args.desc,
|
|
1508
|
+
resolveBearer()
|
|
1509
|
+
);
|
|
1447
1510
|
if (args.json) {
|
|
1448
|
-
console.log(toJson({ channelId,
|
|
1511
|
+
console.log(toJson({ channelId, url, workspaceId, name: args.name, description: args.desc }));
|
|
1449
1512
|
return;
|
|
1450
1513
|
}
|
|
1451
|
-
console.log(`channel ${channelId} opened
|
|
1514
|
+
console.log(`channel ${channelId} opened in workspace ${workspaceId}
|
|
1452
1515
|
`);
|
|
1453
|
-
console.log(inviteBlock(url, channelId
|
|
1516
|
+
console.log(inviteBlock(url, channelId));
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
// src/commands/channel/download.ts
|
|
1521
|
+
import { writeFileSync as writeFileSync2 } from "node:fs";
|
|
1522
|
+
var downloadCommand = defineCommand({
|
|
1523
|
+
meta: { name: "download", description: "download a channel attachment to a file" },
|
|
1524
|
+
args: {
|
|
1525
|
+
channel: { type: "positional", required: true, description: "channel id" },
|
|
1526
|
+
att: { type: "positional", required: true, description: "attachment id (from a message ref)" },
|
|
1527
|
+
out: { type: "string", alias: "o", description: "output file path (default: ./<attId>)" },
|
|
1528
|
+
...common
|
|
1529
|
+
},
|
|
1530
|
+
run: async ({ args }) => {
|
|
1531
|
+
const url = resolveBaseUrl(args.url);
|
|
1532
|
+
const res = await channelClient(url).downloadAttachment(args.channel, args.att);
|
|
1533
|
+
const out = args.out ?? args.att;
|
|
1534
|
+
writeFileSync2(out, Buffer.from(await res.arrayBuffer()));
|
|
1535
|
+
console.log(`saved \u2192 ${out}`);
|
|
1454
1536
|
}
|
|
1455
1537
|
});
|
|
1456
1538
|
|
|
@@ -1459,12 +1541,11 @@ var aboutCommand = defineCommand({
|
|
|
1459
1541
|
meta: { name: "about", description: "show a channel's manifest (description + online roster)" },
|
|
1460
1542
|
args: {
|
|
1461
1543
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1462
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1463
1544
|
...common
|
|
1464
1545
|
},
|
|
1465
1546
|
run: async ({ args }) => {
|
|
1466
1547
|
const url = resolveBaseUrl(args.url);
|
|
1467
|
-
const m = await channelClient(url).manifest(args.channel
|
|
1548
|
+
const m = await channelClient(url).manifest(args.channel);
|
|
1468
1549
|
if (args.json) {
|
|
1469
1550
|
console.log(toJson(m));
|
|
1470
1551
|
return;
|
|
@@ -1479,7 +1560,7 @@ protocol: curl -sS "${url}${m.help}"`);
|
|
|
1479
1560
|
}
|
|
1480
1561
|
});
|
|
1481
1562
|
var helpCommand = defineCommand({
|
|
1482
|
-
meta: { name: "help", description: "print the channel protocol doc
|
|
1563
|
+
meta: { name: "help", description: "print the channel protocol doc" },
|
|
1483
1564
|
args: { ...common },
|
|
1484
1565
|
run: async ({ args }) => {
|
|
1485
1566
|
console.log(await channelClient(resolveBaseUrl(args.url)).help());
|
|
@@ -1489,12 +1570,11 @@ var membersCommand = defineCommand({
|
|
|
1489
1570
|
meta: { name: "members", description: "list the channel's online roster" },
|
|
1490
1571
|
args: {
|
|
1491
1572
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1492
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1493
1573
|
...common
|
|
1494
1574
|
},
|
|
1495
1575
|
run: async ({ args }) => {
|
|
1496
1576
|
const url = resolveBaseUrl(args.url);
|
|
1497
|
-
const members = await channelClient(url).members(args.channel
|
|
1577
|
+
const members = await channelClient(url).members(args.channel);
|
|
1498
1578
|
console.log(args.json ? toJson(members) : renderRoster(members));
|
|
1499
1579
|
}
|
|
1500
1580
|
});
|
|
@@ -1502,7 +1582,6 @@ var updateCommand = defineCommand({
|
|
|
1502
1582
|
meta: { name: "update", description: "update a channel's title / description (topic + rules)" },
|
|
1503
1583
|
args: {
|
|
1504
1584
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1505
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1506
1585
|
title: { type: "string", description: "new title" },
|
|
1507
1586
|
desc: { type: "string", description: "new description (purpose / topic / rules)" },
|
|
1508
1587
|
...common
|
|
@@ -1511,7 +1590,7 @@ var updateCommand = defineCommand({
|
|
|
1511
1590
|
if (args.title === void 0 && args.desc === void 0)
|
|
1512
1591
|
throw new Error("nothing to update: pass --title and/or --desc");
|
|
1513
1592
|
const url = resolveBaseUrl(args.url);
|
|
1514
|
-
const ch = await channelClient(url).update(args.channel,
|
|
1593
|
+
const ch = await channelClient(url).update(args.channel, {
|
|
1515
1594
|
title: args.title,
|
|
1516
1595
|
description: args.desc
|
|
1517
1596
|
});
|
|
@@ -1520,7 +1599,7 @@ var updateCommand = defineCommand({
|
|
|
1520
1599
|
});
|
|
1521
1600
|
|
|
1522
1601
|
// src/commands/channel/listen.ts
|
|
1523
|
-
import { mkdirSync, writeFileSync as
|
|
1602
|
+
import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1524
1603
|
import { tmpdir } from "node:os";
|
|
1525
1604
|
import { join as join2 } from "node:path";
|
|
1526
1605
|
|
|
@@ -1552,6 +1631,12 @@ var unstartedUserMessages = (events) => {
|
|
|
1552
1631
|
const started = startedMessageIds(events);
|
|
1553
1632
|
return events.filter((e) => e.type === "user_message" && !started.has(e.id));
|
|
1554
1633
|
};
|
|
1634
|
+
var opensTurn = (e) => e.type === "user_message" || e.type === "turn_start";
|
|
1635
|
+
var closesTurn = (e) => e.type === "turn_complete" || e.type === "turn_error";
|
|
1636
|
+
var isAgentWorking = (events) => {
|
|
1637
|
+
const last = events.findLast((e) => opensTurn(e) || closesTurn(e));
|
|
1638
|
+
return last ? opensTurn(last) : false;
|
|
1639
|
+
};
|
|
1555
1640
|
|
|
1556
1641
|
// src/commands/channel/listen.ts
|
|
1557
1642
|
var PREVIEW = 280;
|
|
@@ -1569,7 +1654,7 @@ var runListen = async (o) => {
|
|
|
1569
1654
|
const surface = (m) => {
|
|
1570
1655
|
const text = typeof m.text === "string" ? m.text : "";
|
|
1571
1656
|
const file = join2(spillDir, `${m.seq}.txt`);
|
|
1572
|
-
|
|
1657
|
+
writeFileSync3(file, text);
|
|
1573
1658
|
const truncated = text.length > PREVIEW;
|
|
1574
1659
|
emit({
|
|
1575
1660
|
type: "channel.message",
|
|
@@ -1582,10 +1667,17 @@ var runListen = async (o) => {
|
|
|
1582
1667
|
chars: text.length,
|
|
1583
1668
|
truncated,
|
|
1584
1669
|
preview: truncated ? `${text.slice(0, PREVIEW)} \u2026` : text,
|
|
1670
|
+
// BASE-prefixed urls so the subscribing agent can fetch directly.
|
|
1671
|
+
attachments: m.attachments?.length ? m.attachments.map((a) => ({
|
|
1672
|
+
filename: a.filename,
|
|
1673
|
+
contentType: a.contentType,
|
|
1674
|
+
size: a.size,
|
|
1675
|
+
url: `${o.url}${a.url}`
|
|
1676
|
+
})) : void 0,
|
|
1585
1677
|
file
|
|
1586
1678
|
});
|
|
1587
1679
|
};
|
|
1588
|
-
const close = channelClient(o.url).listen(o.channel,
|
|
1680
|
+
const close = channelClient(o.url).listen(o.channel, {
|
|
1589
1681
|
since: o.since,
|
|
1590
1682
|
for: o.for,
|
|
1591
1683
|
as: o.from,
|
|
@@ -1612,8 +1704,10 @@ var listenCommand = defineCommand({
|
|
|
1612
1704
|
},
|
|
1613
1705
|
args: {
|
|
1614
1706
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1615
|
-
|
|
1616
|
-
|
|
1707
|
+
from: {
|
|
1708
|
+
type: "string",
|
|
1709
|
+
description: "your name; your own messages skipped, presence kept fresh"
|
|
1710
|
+
},
|
|
1617
1711
|
since: { type: "string", description: "replay messages after this seq (default 0 = full)" },
|
|
1618
1712
|
for: { type: "string", description: "only broadcasts + messages addressed to this name" },
|
|
1619
1713
|
...common
|
|
@@ -1622,7 +1716,6 @@ var listenCommand = defineCommand({
|
|
|
1622
1716
|
await runListen({
|
|
1623
1717
|
url: resolveBaseUrl(args.url),
|
|
1624
1718
|
channel: args.channel,
|
|
1625
|
-
token: args.token,
|
|
1626
1719
|
from: args.from ?? "",
|
|
1627
1720
|
since: args.since ? Number(args.since) : 0,
|
|
1628
1721
|
for: args.for
|
|
@@ -1632,30 +1725,35 @@ var listenCommand = defineCommand({
|
|
|
1632
1725
|
|
|
1633
1726
|
// src/commands/channel/join.ts
|
|
1634
1727
|
var joinCommand = defineCommand({
|
|
1635
|
-
meta: {
|
|
1728
|
+
meta: {
|
|
1729
|
+
name: "join",
|
|
1730
|
+
description: "join a channel (register presence); --listen to also stream"
|
|
1731
|
+
},
|
|
1636
1732
|
args: {
|
|
1637
1733
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1638
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1639
1734
|
from: { type: "string", required: true, description: "your participant name" },
|
|
1640
1735
|
kind: { type: "string", description: "your kind: agent (default) | human" },
|
|
1641
|
-
listen: {
|
|
1736
|
+
listen: {
|
|
1737
|
+
type: "boolean",
|
|
1738
|
+
description: "after joining, stream messages (background subscriber)"
|
|
1739
|
+
},
|
|
1642
1740
|
since: { type: "string", description: "with --listen: replay after this seq (default 0)" },
|
|
1643
1741
|
...common
|
|
1644
1742
|
},
|
|
1645
1743
|
run: async ({ args }) => {
|
|
1646
1744
|
const url = resolveBaseUrl(args.url);
|
|
1647
1745
|
const kind = args.kind === "human" ? "human" : "agent";
|
|
1648
|
-
const members = await channelClient(url).join(args.channel, args.
|
|
1746
|
+
const members = await channelClient(url).join(args.channel, args.from, kind);
|
|
1649
1747
|
if (!args.listen) {
|
|
1650
1748
|
if (args.json) console.log(toJson({ joined: args.from, members }));
|
|
1651
|
-
else
|
|
1749
|
+
else
|
|
1750
|
+
console.log(`joined as ${args.from} (${members.length} online)
|
|
1652
1751
|
${renderRoster(members)}`);
|
|
1653
1752
|
return;
|
|
1654
1753
|
}
|
|
1655
1754
|
await runListen({
|
|
1656
1755
|
url,
|
|
1657
1756
|
channel: args.channel,
|
|
1658
|
-
token: args.token,
|
|
1659
1757
|
from: args.from,
|
|
1660
1758
|
since: args.since ? Number(args.since) : 0
|
|
1661
1759
|
});
|
|
@@ -1663,11 +1761,16 @@ ${renderRoster(members)}`);
|
|
|
1663
1761
|
});
|
|
1664
1762
|
|
|
1665
1763
|
// src/commands/channel/read.ts
|
|
1764
|
+
var fmtMessage = (m, base) => {
|
|
1765
|
+
const head = `${m.seq} ${m.from} ${m.text}`;
|
|
1766
|
+
const atts = (m.attachments ?? []).map((a) => `
|
|
1767
|
+
\u{1F4CE} ${a.filename} \u2192 ${base}${a.url}`).join("");
|
|
1768
|
+
return head + atts;
|
|
1769
|
+
};
|
|
1666
1770
|
var readCommand = defineCommand({
|
|
1667
1771
|
meta: { name: "read", description: "poll channel messages once (curl-style, since a cursor)" },
|
|
1668
1772
|
args: {
|
|
1669
1773
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1670
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1671
1774
|
since: { type: "string", description: "only messages after this seq (default 0)" },
|
|
1672
1775
|
from: { type: "string", description: "skip messages from this name (no echo)" },
|
|
1673
1776
|
for: { type: "string", description: "only broadcasts + messages addressed to this name" },
|
|
@@ -1675,40 +1778,99 @@ var readCommand = defineCommand({
|
|
|
1675
1778
|
},
|
|
1676
1779
|
run: async ({ args }) => {
|
|
1677
1780
|
const url = resolveBaseUrl(args.url);
|
|
1678
|
-
const msgs = await channelClient(url).read(args.channel,
|
|
1781
|
+
const msgs = await channelClient(url).read(args.channel, {
|
|
1679
1782
|
since: args.since ? Number(args.since) : 0,
|
|
1680
1783
|
for: args.for
|
|
1681
1784
|
});
|
|
1682
1785
|
const out = args.from ? msgs.filter((m) => m.from !== args.from) : msgs;
|
|
1683
1786
|
if (args.json) console.log(toJson(out));
|
|
1684
|
-
else console.log(out.length ? out.map((m) =>
|
|
1787
|
+
else console.log(out.length ? out.map((m) => fmtMessage(m, url)).join("\n") : "(none)");
|
|
1685
1788
|
}
|
|
1686
1789
|
});
|
|
1687
1790
|
|
|
1688
1791
|
// src/commands/channel/send.ts
|
|
1689
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1792
|
+
import { openAsBlob as openAsBlob2, readFileSync as readFileSync2 } from "node:fs";
|
|
1793
|
+
import { basename as basename2 } from "node:path";
|
|
1794
|
+
|
|
1795
|
+
// src/mime.ts
|
|
1796
|
+
import { extname } from "node:path";
|
|
1797
|
+
var BY_EXT = {
|
|
1798
|
+
".png": "image/png",
|
|
1799
|
+
".jpg": "image/jpeg",
|
|
1800
|
+
".jpeg": "image/jpeg",
|
|
1801
|
+
".gif": "image/gif",
|
|
1802
|
+
".webp": "image/webp",
|
|
1803
|
+
".svg": "image/svg+xml",
|
|
1804
|
+
".pdf": "application/pdf",
|
|
1805
|
+
".txt": "text/plain",
|
|
1806
|
+
".md": "text/markdown",
|
|
1807
|
+
".json": "application/json",
|
|
1808
|
+
".csv": "text/csv"
|
|
1809
|
+
};
|
|
1810
|
+
var contentTypeForPath = (path) => BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
1811
|
+
|
|
1812
|
+
// src/commands/attach.ts
|
|
1813
|
+
import { openAsBlob } from "node:fs";
|
|
1814
|
+
import { basename } from "node:path";
|
|
1815
|
+
var attachPaths = (raw) => (raw === void 0 ? [] : Array.isArray(raw) ? raw : [raw]).flatMap((p) => p.split(",")).map((p) => p.trim()).filter(Boolean);
|
|
1816
|
+
var uploadAttachments = async (client, sessionId, paths) => {
|
|
1817
|
+
const out = [];
|
|
1818
|
+
for (const path of paths) {
|
|
1819
|
+
const body = await openAsBlob(path, { type: contentTypeForPath(path) });
|
|
1820
|
+
out.push(
|
|
1821
|
+
await client.sessions.uploadAttachment(sessionId, {
|
|
1822
|
+
filename: basename(path),
|
|
1823
|
+
contentType: contentTypeForPath(path),
|
|
1824
|
+
body
|
|
1825
|
+
})
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
return out;
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1831
|
+
// src/commands/channel/send.ts
|
|
1690
1832
|
var sendCommand = defineCommand({
|
|
1691
1833
|
meta: { name: "send", description: "post one message to a channel" },
|
|
1692
1834
|
args: {
|
|
1693
1835
|
channel: { type: "positional", required: true, description: "channel id" },
|
|
1694
1836
|
text: { type: "positional", required: false, description: "message text (short one-liner)" },
|
|
1695
|
-
token: { type: "string", required: true, description: "channel token" },
|
|
1696
1837
|
from: { type: "string", description: "your participant name (default: peer)" },
|
|
1697
|
-
to: {
|
|
1838
|
+
to: {
|
|
1839
|
+
type: "string",
|
|
1840
|
+
description: "direct it at these names (comma-separated); omit = broadcast"
|
|
1841
|
+
},
|
|
1698
1842
|
file: { type: "string", description: "read message body from this file (for large content)" },
|
|
1843
|
+
attach: {
|
|
1844
|
+
type: "string",
|
|
1845
|
+
description: "attach file(s) (comma-separated paths); uploaded + carried on the message"
|
|
1846
|
+
},
|
|
1699
1847
|
...common
|
|
1700
1848
|
},
|
|
1701
1849
|
run: async ({ args }) => {
|
|
1702
1850
|
const url = resolveBaseUrl(args.url);
|
|
1851
|
+
const client = channelClient(url);
|
|
1703
1852
|
let text = args.text;
|
|
1704
1853
|
if (text === void 0 && args.file) text = readFileSync2(args.file, "utf8");
|
|
1705
1854
|
if (text === void 0 && !process.stdin.isTTY) text = await readStdin();
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1855
|
+
const attachments = [];
|
|
1856
|
+
for (const path of attachPaths(args.attach)) {
|
|
1857
|
+
attachments.push(
|
|
1858
|
+
await client.uploadAttachment(args.channel, {
|
|
1859
|
+
filename: basename2(path),
|
|
1860
|
+
contentType: contentTypeForPath(path),
|
|
1861
|
+
body: await openAsBlob2(path, { type: contentTypeForPath(path) })
|
|
1862
|
+
})
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
if (!text && attachments.length === 0)
|
|
1866
|
+
throw new Error(
|
|
1867
|
+
"nothing to send: provide text, --file <path>, --attach <file>, or pipe stdin"
|
|
1868
|
+
);
|
|
1869
|
+
const msg = await client.send(args.channel, {
|
|
1709
1870
|
from: args.from ?? "peer",
|
|
1710
|
-
text,
|
|
1711
|
-
to: splitCsv(args.to)
|
|
1871
|
+
text: text ?? "",
|
|
1872
|
+
to: splitCsv(args.to),
|
|
1873
|
+
attachments: attachments.length ? attachments : void 0
|
|
1712
1874
|
});
|
|
1713
1875
|
if (args.json) console.log(toJson(msg));
|
|
1714
1876
|
else console.log(`sent (seq ${msg.seq})`);
|
|
@@ -1719,7 +1881,7 @@ var sendCommand = defineCommand({
|
|
|
1719
1881
|
var channel = defineCommand({
|
|
1720
1882
|
meta: {
|
|
1721
1883
|
name: "channel",
|
|
1722
|
-
description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / close"
|
|
1884
|
+
description: "multi-agent chat room: create / about / update / help / join / members / send / read / listen / download / close"
|
|
1723
1885
|
},
|
|
1724
1886
|
subCommands: {
|
|
1725
1887
|
create: createCommand,
|
|
@@ -1731,6 +1893,7 @@ var channel = defineCommand({
|
|
|
1731
1893
|
send: sendCommand,
|
|
1732
1894
|
read: readCommand,
|
|
1733
1895
|
listen: listenCommand,
|
|
1896
|
+
download: downloadCommand,
|
|
1734
1897
|
close: closeCommand
|
|
1735
1898
|
}
|
|
1736
1899
|
});
|
|
@@ -1874,7 +2037,7 @@ var project = defineCommand({
|
|
|
1874
2037
|
});
|
|
1875
2038
|
|
|
1876
2039
|
// src/commands/relay.ts
|
|
1877
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as
|
|
2040
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1878
2041
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
1879
2042
|
import { join as join3 } from "node:path";
|
|
1880
2043
|
|
|
@@ -2009,7 +2172,7 @@ var listenCommand2 = defineCommand({
|
|
|
2009
2172
|
const surface = (m) => {
|
|
2010
2173
|
const text = typeof m.text === "string" ? m.text : "";
|
|
2011
2174
|
const file = join3(spillDir, `${m.seq}.txt`);
|
|
2012
|
-
|
|
2175
|
+
writeFileSync4(file, text);
|
|
2013
2176
|
const truncated = text.length > PREVIEW2;
|
|
2014
2177
|
emit({
|
|
2015
2178
|
type: "relay.message",
|
|
@@ -2506,6 +2669,7 @@ var toTurnResult = (message) => {
|
|
|
2506
2669
|
|
|
2507
2670
|
// src/session/runner/turn.ts
|
|
2508
2671
|
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
2672
|
+
var heartbeatMs = () => Number(process.env.BATON_TURN_HEARTBEAT_MS) || 3e4;
|
|
2509
2673
|
var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
|
|
2510
2674
|
var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
|
|
2511
2675
|
var finalizeTurn = async (result, worker2, log) => {
|
|
@@ -2558,6 +2722,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2558
2722
|
const abort = new AbortController();
|
|
2559
2723
|
const ceiling = turnTimeoutMs();
|
|
2560
2724
|
let timer;
|
|
2725
|
+
let heartbeat;
|
|
2561
2726
|
const timeout = new Promise((resolve2) => {
|
|
2562
2727
|
timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
|
|
2563
2728
|
});
|
|
@@ -2566,6 +2731,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2566
2731
|
else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
|
|
2567
2732
|
});
|
|
2568
2733
|
try {
|
|
2734
|
+
heartbeat = setInterval(() => {
|
|
2735
|
+
void worker2.emitEvent("turn_heartbeat", {}).catch((e) => log(`[heartbeat] ${String(e)}`));
|
|
2736
|
+
}, heartbeatMs());
|
|
2569
2737
|
const messages = startQuery(config, text, resuming, queryFn, abort, log, {
|
|
2570
2738
|
envOverlay,
|
|
2571
2739
|
planMode,
|
|
@@ -2600,6 +2768,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
2600
2768
|
return -1;
|
|
2601
2769
|
} finally {
|
|
2602
2770
|
clearTimeout(timer);
|
|
2771
|
+
clearInterval(heartbeat);
|
|
2603
2772
|
}
|
|
2604
2773
|
};
|
|
2605
2774
|
|
|
@@ -2680,10 +2849,17 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2680
2849
|
busy = false;
|
|
2681
2850
|
}
|
|
2682
2851
|
};
|
|
2852
|
+
const closeOrphanedTurn = async (reason) => {
|
|
2853
|
+
const events = await deps.worker.listEvents();
|
|
2854
|
+
if (!isAgentWorking(events)) return;
|
|
2855
|
+
await deps.worker.emitEvent("turn_error", { message: reason, synthetic: true });
|
|
2856
|
+
log(`closed orphaned open turn (${reason})`);
|
|
2857
|
+
};
|
|
2683
2858
|
const reconcile = async () => {
|
|
2684
2859
|
reconciling = true;
|
|
2685
2860
|
try {
|
|
2686
|
-
const
|
|
2861
|
+
const events = await deps.worker.listEvents();
|
|
2862
|
+
const pending = unstartedUserMessages(events);
|
|
2687
2863
|
let added = 0;
|
|
2688
2864
|
for (const ev of pending) {
|
|
2689
2865
|
if (state.seen.has(ev.sequence)) continue;
|
|
@@ -2695,6 +2871,12 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2695
2871
|
log(`reconciled ${added} queued message(s) from transcript`);
|
|
2696
2872
|
lastActivity = Date.now();
|
|
2697
2873
|
void drain();
|
|
2874
|
+
} else if (pendingQueue.length === 0 && !busy && !currentTurn && isAgentWorking(events)) {
|
|
2875
|
+
await deps.worker.emitEvent("turn_error", {
|
|
2876
|
+
message: "turn abandoned \u2014 runner restarted",
|
|
2877
|
+
synthetic: true
|
|
2878
|
+
});
|
|
2879
|
+
log("reconcile: closed orphaned open turn from a prior child");
|
|
2698
2880
|
}
|
|
2699
2881
|
} catch (e) {
|
|
2700
2882
|
log(`reconcile failed: ${String(e)}`);
|
|
@@ -2719,6 +2901,11 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2719
2901
|
if (currentTurn) {
|
|
2720
2902
|
log("interrupt \u2014 aborting current turn");
|
|
2721
2903
|
currentTurn.abort();
|
|
2904
|
+
} else {
|
|
2905
|
+
log("interrupt \u2014 no live turn; closing any orphaned open turn");
|
|
2906
|
+
void closeOrphanedTurn("interrupted by user").catch(
|
|
2907
|
+
(e) => log(`orphan close failed: ${String(e)}`)
|
|
2908
|
+
);
|
|
2722
2909
|
}
|
|
2723
2910
|
},
|
|
2724
2911
|
// Report active only now that we're subscribed — before this, a message
|
|
@@ -2811,44 +2998,6 @@ var sessionRunCommand = defineCommand({
|
|
|
2811
2998
|
}
|
|
2812
2999
|
});
|
|
2813
3000
|
|
|
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
3001
|
// src/commands/session/send.ts
|
|
2853
3002
|
var sessionSendCommand = defineCommand({
|
|
2854
3003
|
meta: { name: "send", description: "post a chat message into a session (no streaming reply)" },
|
|
@@ -3229,6 +3378,17 @@ var removeWorktree = (repo, worktreePath) => {
|
|
|
3229
3378
|
} catch {
|
|
3230
3379
|
}
|
|
3231
3380
|
};
|
|
3381
|
+
var restoreWorktree = (repo, worktreePath, sessionCode) => {
|
|
3382
|
+
spawnSync2("git", ["-C", repo, "worktree", "prune"], { stdio: "pipe" });
|
|
3383
|
+
mkdirSync3(dirname(worktreePath), { recursive: true });
|
|
3384
|
+
const branch = `baton/${sessionCode}`;
|
|
3385
|
+
const reuse = spawnSync2("git", ["-C", repo, "worktree", "add", worktreePath, branch], {
|
|
3386
|
+
stdio: "pipe",
|
|
3387
|
+
encoding: "utf8"
|
|
3388
|
+
});
|
|
3389
|
+
if (reuse.status === 0) return;
|
|
3390
|
+
createWorktree({ repo, worktreePath, sessionCode, base: repoHeadBranch(repo) });
|
|
3391
|
+
};
|
|
3232
3392
|
|
|
3233
3393
|
// src/worker/watchdog.ts
|
|
3234
3394
|
var stale = (okAt, now, thresholdMs) => now - okAt > thresholdMs;
|
|
@@ -3316,6 +3476,9 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
3316
3476
|
});
|
|
3317
3477
|
await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
|
|
3318
3478
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
3479
|
+
} else if (!existsSync5(worktreePath)) {
|
|
3480
|
+
restoreWorktree(repo, worktreePath, session2.agentSessionId.slice(0, 8));
|
|
3481
|
+
log(`recreated session #${sessionId} worktree (was missing) \u2192 ${worktreePath}`);
|
|
3319
3482
|
}
|
|
3320
3483
|
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
3321
3484
|
saveProjectConfig(join7(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
@@ -3421,7 +3584,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
3421
3584
|
|
|
3422
3585
|
// src/worker/machine-id.ts
|
|
3423
3586
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3424
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as
|
|
3587
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
3425
3588
|
import { homedir as homedir3 } from "node:os";
|
|
3426
3589
|
import { dirname as dirname3, join as join8 } from "node:path";
|
|
3427
3590
|
var machineIdPath = (env = process.env) => join8(env.XDG_DATA_HOME ?? join8(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
|
|
@@ -3432,7 +3595,7 @@ var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
|
3432
3595
|
}
|
|
3433
3596
|
const id = randomUUID2();
|
|
3434
3597
|
mkdirSync4(dirname3(path), { recursive: true });
|
|
3435
|
-
|
|
3598
|
+
writeFileSync5(path, `${id}
|
|
3436
3599
|
`, "utf8");
|
|
3437
3600
|
return id;
|
|
3438
3601
|
};
|
|
@@ -3492,7 +3655,10 @@ var worker = defineCommand({
|
|
|
3492
3655
|
"no baton server \u2014 pass --url <url> (e.g. https://baton.fmap.dev/api), set BATON_URL, or run `baton init`"
|
|
3493
3656
|
);
|
|
3494
3657
|
if (!args.json) console.log(`registering against ${server}`);
|
|
3495
|
-
const
|
|
3658
|
+
const cfgPath = configPathFromArgs(args);
|
|
3659
|
+
const fileToken = loadProjectConfigOrNull(cfgPath)?.worker?.apiToken;
|
|
3660
|
+
const auth = resolveAuth(process.env, fileToken);
|
|
3661
|
+
const c = createClient(server, auth && auth !== "cookie" ? auth : void 0);
|
|
3496
3662
|
const hostname = osHostname();
|
|
3497
3663
|
const machineId = readOrCreateMachineId();
|
|
3498
3664
|
const name = args.name ?? hostname;
|
|
@@ -3505,7 +3671,7 @@ var worker = defineCommand({
|
|
|
3505
3671
|
hostname,
|
|
3506
3672
|
machineId
|
|
3507
3673
|
},
|
|
3508
|
-
|
|
3674
|
+
cfgPath
|
|
3509
3675
|
);
|
|
3510
3676
|
if (args.json) {
|
|
3511
3677
|
console.log(toJson({ ...out, configPath, machineIdPath: machineIdPath() }));
|