@lesscap/baton-cli 0.8.3 → 0.9.4
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/LICENSE +21 -0
- package/README.md +32 -17
- package/dist/baton.mjs +1810 -536
- package/package.json +18 -18
- package/skills/baton-publish-artifact/SKILL.md +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -14,7 +14,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
14
14
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
15
|
});
|
|
16
16
|
var __commonJS = (cb, mod) => function __require2() {
|
|
17
|
-
|
|
17
|
+
try {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw mod = 0, e;
|
|
21
|
+
}
|
|
18
22
|
};
|
|
19
23
|
var __copyProps = (to, from, except, desc) => {
|
|
20
24
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -2281,7 +2285,7 @@ var require_websocket = __commonJS({
|
|
|
2281
2285
|
var http = __require("http");
|
|
2282
2286
|
var net = __require("net");
|
|
2283
2287
|
var tls = __require("tls");
|
|
2284
|
-
var { randomBytes, createHash } = __require("crypto");
|
|
2288
|
+
var { randomBytes, createHash: createHash2 } = __require("crypto");
|
|
2285
2289
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
2286
2290
|
var { URL: URL2 } = __require("url");
|
|
2287
2291
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2949,7 +2953,7 @@ var require_websocket = __commonJS({
|
|
|
2949
2953
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2950
2954
|
return;
|
|
2951
2955
|
}
|
|
2952
|
-
const digest =
|
|
2956
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
2953
2957
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2954
2958
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2955
2959
|
return;
|
|
@@ -3230,7 +3234,7 @@ var require_stream = __commonJS({
|
|
|
3230
3234
|
};
|
|
3231
3235
|
duplex._final = function(callback) {
|
|
3232
3236
|
if (ws.readyState === ws.CONNECTING) {
|
|
3233
|
-
ws.once("open", function
|
|
3237
|
+
ws.once("open", function open3() {
|
|
3234
3238
|
duplex._final(callback);
|
|
3235
3239
|
});
|
|
3236
3240
|
return;
|
|
@@ -3251,7 +3255,7 @@ var require_stream = __commonJS({
|
|
|
3251
3255
|
};
|
|
3252
3256
|
duplex._write = function(chunk, encoding, callback) {
|
|
3253
3257
|
if (ws.readyState === ws.CONNECTING) {
|
|
3254
|
-
ws.once("open", function
|
|
3258
|
+
ws.once("open", function open3() {
|
|
3255
3259
|
duplex._write(chunk, encoding, callback);
|
|
3256
3260
|
});
|
|
3257
3261
|
return;
|
|
@@ -3318,7 +3322,7 @@ var require_websocket_server = __commonJS({
|
|
|
3318
3322
|
var EventEmitter = __require("events");
|
|
3319
3323
|
var http = __require("http");
|
|
3320
3324
|
var { Duplex } = __require("stream");
|
|
3321
|
-
var { createHash } = __require("crypto");
|
|
3325
|
+
var { createHash: createHash2 } = __require("crypto");
|
|
3322
3326
|
var extension2 = require_extension();
|
|
3323
3327
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3324
3328
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3625,7 +3629,7 @@ var require_websocket_server = __commonJS({
|
|
|
3625
3629
|
);
|
|
3626
3630
|
}
|
|
3627
3631
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3628
|
-
const digest =
|
|
3632
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
3629
3633
|
const headers = [
|
|
3630
3634
|
"HTTP/1.1 101 Switching Protocols",
|
|
3631
3635
|
"Upgrade: websocket",
|
|
@@ -3724,14 +3728,14 @@ function isUppercase(char = "") {
|
|
|
3724
3728
|
if (NUMBER_CHAR_RE.test(char)) return;
|
|
3725
3729
|
return char !== char.toLowerCase();
|
|
3726
3730
|
}
|
|
3727
|
-
function splitByCase(
|
|
3731
|
+
function splitByCase(str4, separators) {
|
|
3728
3732
|
const splitters = separators ?? STR_SPLITTERS;
|
|
3729
3733
|
const parts = [];
|
|
3730
|
-
if (!
|
|
3734
|
+
if (!str4 || typeof str4 !== "string") return parts;
|
|
3731
3735
|
let buff = "";
|
|
3732
3736
|
let previousUpper;
|
|
3733
3737
|
let previousSplitter;
|
|
3734
|
-
for (const char of
|
|
3738
|
+
for (const char of str4) {
|
|
3735
3739
|
const isSplitter = splitters.includes(char);
|
|
3736
3740
|
if (isSplitter === true) {
|
|
3737
3741
|
parts.push(buff);
|
|
@@ -3762,23 +3766,23 @@ function splitByCase(str3, separators) {
|
|
|
3762
3766
|
parts.push(buff);
|
|
3763
3767
|
return parts;
|
|
3764
3768
|
}
|
|
3765
|
-
function upperFirst(
|
|
3766
|
-
return
|
|
3769
|
+
function upperFirst(str4) {
|
|
3770
|
+
return str4 ? str4[0].toUpperCase() + str4.slice(1) : "";
|
|
3767
3771
|
}
|
|
3768
|
-
function lowerFirst(
|
|
3769
|
-
return
|
|
3772
|
+
function lowerFirst(str4) {
|
|
3773
|
+
return str4 ? str4[0].toLowerCase() + str4.slice(1) : "";
|
|
3770
3774
|
}
|
|
3771
|
-
function pascalCase(
|
|
3772
|
-
return
|
|
3775
|
+
function pascalCase(str4, opts) {
|
|
3776
|
+
return str4 ? (Array.isArray(str4) ? str4 : splitByCase(str4)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
|
|
3773
3777
|
}
|
|
3774
|
-
function camelCase(
|
|
3775
|
-
return lowerFirst(pascalCase(
|
|
3778
|
+
function camelCase(str4, opts) {
|
|
3779
|
+
return lowerFirst(pascalCase(str4 || "", opts));
|
|
3776
3780
|
}
|
|
3777
|
-
function kebabCase(
|
|
3778
|
-
return
|
|
3781
|
+
function kebabCase(str4, joiner) {
|
|
3782
|
+
return str4 ? (Array.isArray(str4) ? str4 : splitByCase(str4)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
|
|
3779
3783
|
}
|
|
3780
|
-
function snakeCase(
|
|
3781
|
-
return kebabCase(
|
|
3784
|
+
function snakeCase(str4) {
|
|
3785
|
+
return kebabCase(str4 || "", "_");
|
|
3782
3786
|
}
|
|
3783
3787
|
|
|
3784
3788
|
// ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
|
|
@@ -5370,11 +5374,13 @@ var contentTypeForPath = (path) => src_default.getType(path) ?? "application/oct
|
|
|
5370
5374
|
// src/output.ts
|
|
5371
5375
|
var toJson = (data) => JSON.stringify(data, null, 2);
|
|
5372
5376
|
var fmtWorkspace = (w) => `${w.id} ${w.name}`;
|
|
5377
|
+
var fmtWorkerTemplate = (t) => `${t.id} ${t.name} [${t.available ? "available" : "unavailable"}] ${t.agentKinds.join(", ")}`;
|
|
5378
|
+
var fmtWorkerCreation = (c) => `${c.id} [${c.status}/${c.step ?? "pending"}] ${c.name} ${c.agentKind}${c.workerId === null ? "" : ` W-${c.workerId}`}${c.error ? ` ${c.error}` : ""}`;
|
|
5373
5379
|
var fmtProject = (p) => `${p.id} ${p.name}`;
|
|
5374
5380
|
var fmtRequirement = (r) => `${r.code} [${r.status}] ${r.title}`;
|
|
5375
5381
|
var fmtTask = (t) => `${t.code} [${t.status}] ${t.title}`;
|
|
5376
5382
|
var fmtComment = (c) => `${c.workerId !== void 0 ? `worker#${c.workerId}` : "you"} ${c.body}`;
|
|
5377
|
-
var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.
|
|
5383
|
+
var fmtSession = (s) => `${s.id} ${s.name} ${s.attached ? "[active]" : "[inactive]"} ${s.cwd ?? "(pending)"}`;
|
|
5378
5384
|
var fmtWorker = (w) => {
|
|
5379
5385
|
const offline = w.connected === false ? " [offline]" : "";
|
|
5380
5386
|
return `W-${w.id} ${w.name} ${w.hostname} ${w.agentKind}${offline}`;
|
|
@@ -5486,6 +5492,11 @@ var primeLogin = (baseUrl) => {
|
|
|
5486
5492
|
);
|
|
5487
5493
|
};
|
|
5488
5494
|
|
|
5495
|
+
// src/client/files.ts
|
|
5496
|
+
var filesClient = (baseUrl) => ({
|
|
5497
|
+
report: (result) => request(`${baseUrl}/workers/me/files/results`, { method: "PUT", body: result })
|
|
5498
|
+
});
|
|
5499
|
+
|
|
5489
5500
|
// src/client/loops.ts
|
|
5490
5501
|
var loopsClient = (baseUrl) => {
|
|
5491
5502
|
const u = (p) => `${baseUrl}${p}`;
|
|
@@ -5550,7 +5561,7 @@ var servicesClient = (baseUrl) => {
|
|
|
5550
5561
|
};
|
|
5551
5562
|
|
|
5552
5563
|
// src/client/retry.ts
|
|
5553
|
-
var sleep = (ms) => new Promise((
|
|
5564
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5554
5565
|
var withRetry = async (fn, opts = {}) => {
|
|
5555
5566
|
const tries = Math.max(1, opts.tries ?? 4);
|
|
5556
5567
|
const baseMs = opts.baseMs ?? 300;
|
|
@@ -5665,7 +5676,8 @@ var sessionsClient = (baseUrl) => {
|
|
|
5665
5676
|
},
|
|
5666
5677
|
destroy: async (id) => {
|
|
5667
5678
|
await request(u(`/sessions/${id}`), { method: "DELETE" });
|
|
5668
|
-
}
|
|
5679
|
+
},
|
|
5680
|
+
notify: (id, text) => request(u(`/sessions/${id}/notify`), { method: "POST", body: { text } })
|
|
5669
5681
|
};
|
|
5670
5682
|
};
|
|
5671
5683
|
|
|
@@ -5685,6 +5697,14 @@ var taskClient = (baseUrl) => {
|
|
|
5685
5697
|
};
|
|
5686
5698
|
};
|
|
5687
5699
|
|
|
5700
|
+
// src/client/worker-creations.ts
|
|
5701
|
+
var workerCreationsClient = (baseUrl) => ({
|
|
5702
|
+
templates: (projectId) => request(`${baseUrl}/projects/${projectId}/worker-templates`, { method: "GET" }),
|
|
5703
|
+
create: (projectId, input) => request(`${baseUrl}/projects/${projectId}/worker-creations`, { method: "POST", body: input }),
|
|
5704
|
+
listByProject: (projectId) => request(`${baseUrl}/projects/${projectId}/worker-creations`, { method: "GET" }),
|
|
5705
|
+
get: (id) => request(`${baseUrl}/worker-creations/${id}`, { method: "GET" })
|
|
5706
|
+
});
|
|
5707
|
+
|
|
5688
5708
|
// src/client/workers.ts
|
|
5689
5709
|
var workersClient = (baseUrl) => {
|
|
5690
5710
|
const u = (p) => `${baseUrl}${p}`;
|
|
@@ -5725,6 +5745,7 @@ var createClient = (baseUrl, opts) => {
|
|
|
5725
5745
|
return clientFromBase(baseUrl);
|
|
5726
5746
|
};
|
|
5727
5747
|
var clientFromBase = (baseUrl) => ({
|
|
5748
|
+
workerCreations: workerCreationsClient(baseUrl),
|
|
5728
5749
|
artifacts: artifactsClient(baseUrl),
|
|
5729
5750
|
workspaces: workspaceClient(baseUrl),
|
|
5730
5751
|
projects: projectClient(baseUrl),
|
|
@@ -5732,6 +5753,7 @@ var clientFromBase = (baseUrl) => ({
|
|
|
5732
5753
|
tasks: taskClient(baseUrl),
|
|
5733
5754
|
sessions: sessionsClient(baseUrl),
|
|
5734
5755
|
services: servicesClient(baseUrl),
|
|
5756
|
+
files: filesClient(baseUrl),
|
|
5735
5757
|
loops: loopsClient(baseUrl),
|
|
5736
5758
|
workers: workersClient(baseUrl)
|
|
5737
5759
|
});
|
|
@@ -5783,7 +5805,6 @@ var viewWorker = (cfg) => {
|
|
|
5783
5805
|
return {
|
|
5784
5806
|
server,
|
|
5785
5807
|
projectId: project2,
|
|
5786
|
-
baseBranch: cfg.baseBranch,
|
|
5787
5808
|
workerId: worker2.id,
|
|
5788
5809
|
agentKind: worker2.agentKind ?? "claude-code",
|
|
5789
5810
|
name: worker2.name,
|
|
@@ -5791,29 +5812,40 @@ var viewWorker = (cfg) => {
|
|
|
5791
5812
|
apiToken: worker2.apiToken
|
|
5792
5813
|
};
|
|
5793
5814
|
};
|
|
5794
|
-
var
|
|
5815
|
+
var workerProjectConfig = (cfg) => ({
|
|
5795
5816
|
server: cfg.server,
|
|
5796
5817
|
project: cfg.projectId,
|
|
5797
|
-
baseBranch: cfg.baseBranch,
|
|
5798
5818
|
worker: {
|
|
5799
5819
|
id: cfg.workerId,
|
|
5800
5820
|
name: cfg.name,
|
|
5801
5821
|
agentKind: cfg.agentKind,
|
|
5802
5822
|
machineId: cfg.machineId,
|
|
5803
5823
|
apiToken: cfg.apiToken
|
|
5804
|
-
}
|
|
5805
|
-
|
|
5824
|
+
}
|
|
5825
|
+
});
|
|
5826
|
+
var workerEnv = (cfg) => ({
|
|
5827
|
+
...Object.fromEntries(
|
|
5828
|
+
Object.entries(process.env).filter(
|
|
5829
|
+
(entry) => entry[1] !== void 0
|
|
5830
|
+
)
|
|
5831
|
+
),
|
|
5832
|
+
BATON_SERVER: cfg.server,
|
|
5833
|
+
BATON_PROJECT_ID: String(cfg.projectId),
|
|
5834
|
+
BATON_WORKER_ID: String(cfg.workerId),
|
|
5835
|
+
BATON_WORKER_TOKEN: cfg.apiToken
|
|
5806
5836
|
});
|
|
5807
5837
|
|
|
5808
5838
|
// src/config.ts
|
|
5809
5839
|
var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
5810
5840
|
if (urlArg) return urlArg;
|
|
5841
|
+
if (env.BATON_SERVER) return env.BATON_SERVER;
|
|
5811
5842
|
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
5812
5843
|
if (cfg?.server) return cfg.server;
|
|
5813
5844
|
return env.BATON_URL ?? "http://localhost:3280";
|
|
5814
5845
|
};
|
|
5815
5846
|
var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
|
|
5816
5847
|
if (urlArg) return urlArg;
|
|
5848
|
+
if (env.BATON_SERVER) return env.BATON_SERVER;
|
|
5817
5849
|
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
5818
5850
|
if (cfg?.server) return cfg.server;
|
|
5819
5851
|
return env.BATON_URL ?? null;
|
|
@@ -5823,7 +5855,7 @@ var resolveBaseUrlOrNull = (urlArg, env = process.env) => {
|
|
|
5823
5855
|
var common = {
|
|
5824
5856
|
url: {
|
|
5825
5857
|
type: "string",
|
|
5826
|
-
description: "baton server url (--url > .baton.json > BATON_URL > http://localhost:3280)"
|
|
5858
|
+
description: "baton server url (--url > BATON_SERVER > .baton.json > BATON_URL > http://localhost:3280)"
|
|
5827
5859
|
},
|
|
5828
5860
|
json: { type: "boolean", description: "output JSON" }
|
|
5829
5861
|
};
|
|
@@ -5846,6 +5878,8 @@ var resolveBearer = () => {
|
|
|
5846
5878
|
};
|
|
5847
5879
|
var resolveProjectId = (args) => {
|
|
5848
5880
|
if (args.project !== void 0 && args.project !== "") return Number(args.project);
|
|
5881
|
+
if (process.env.BATON_PROJECT_ID !== void 0)
|
|
5882
|
+
return contextId(process.env.BATON_PROJECT_ID, "BATON_PROJECT_ID");
|
|
5849
5883
|
const cfg = loadProjectConfigOrNull(projectConfigPath());
|
|
5850
5884
|
if (cfg?.project !== void 0) return cfg.project;
|
|
5851
5885
|
throw new Error("no project in scope. pass --project <id> or run `baton init` in this directory.");
|
|
@@ -5870,6 +5904,18 @@ var parseDuration = (s) => {
|
|
|
5870
5904
|
const mult = m[2] === "d" ? 86400 : m[2] === "h" ? 3600 : m[2] === "m" ? 60 : 1;
|
|
5871
5905
|
return n * mult;
|
|
5872
5906
|
};
|
|
5907
|
+
var contextId = (value, source) => {
|
|
5908
|
+
const id = Number(value);
|
|
5909
|
+
if (!Number.isSafeInteger(id) || id <= 0) throw new Error(`invalid ${source}`);
|
|
5910
|
+
return id;
|
|
5911
|
+
};
|
|
5912
|
+
var resolveWorkerId = () => {
|
|
5913
|
+
if (process.env.BATON_WORKER_ID !== void 0)
|
|
5914
|
+
return contextId(process.env.BATON_WORKER_ID, "BATON_WORKER_ID");
|
|
5915
|
+
const id = loadProjectConfigOrNull(projectConfigPath())?.worker?.id;
|
|
5916
|
+
if (id === void 0) throw new Error("no worker in scope");
|
|
5917
|
+
return id;
|
|
5918
|
+
};
|
|
5873
5919
|
|
|
5874
5920
|
// src/commands/artifact.ts
|
|
5875
5921
|
var formatArtifact = (artifact2) => `${artifact2.id} ${artifact2.size} B ${artifact2.filename} ${artifact2.url}`;
|
|
@@ -6227,11 +6273,11 @@ var EventSource = class extends EventTarget {
|
|
|
6227
6273
|
return;
|
|
6228
6274
|
}
|
|
6229
6275
|
const decoder = new TextDecoder(), reader = body.getReader();
|
|
6230
|
-
let
|
|
6276
|
+
let open3 = true;
|
|
6231
6277
|
do {
|
|
6232
6278
|
const { done, value } = await reader.read();
|
|
6233
|
-
value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (
|
|
6234
|
-
} while (
|
|
6279
|
+
value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (open3 = false, __privateGet(this, _parser).reset(), __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this));
|
|
6280
|
+
} while (open3);
|
|
6235
6281
|
}), __privateAdd(this, _onFetchError, (err) => {
|
|
6236
6282
|
__privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
|
|
6237
6283
|
}), __privateAdd(this, _onEvent, (event) => {
|
|
@@ -6686,11 +6732,10 @@ import { tmpdir } from "node:os";
|
|
|
6686
6732
|
import { join as join2 } from "node:path";
|
|
6687
6733
|
|
|
6688
6734
|
// ../shared/src/domain/attachment.ts
|
|
6689
|
-
var isImageAttachment = (att) => att.contentType.startsWith("image/");
|
|
6690
6735
|
var labelAttachments = (attachments) => {
|
|
6691
6736
|
const counters = { image: 0, file: 0 };
|
|
6692
6737
|
return attachments.map((att) => {
|
|
6693
|
-
const kind =
|
|
6738
|
+
const kind = att.contentType.startsWith("image/") ? "image" : "file";
|
|
6694
6739
|
counters[kind] += 1;
|
|
6695
6740
|
return `${kind}-${counters[kind]}`;
|
|
6696
6741
|
});
|
|
@@ -6699,6 +6744,77 @@ var labelAttachments = (attachments) => {
|
|
|
6699
6744
|
// ../shared/src/domain/channel.ts
|
|
6700
6745
|
var isMessageFor = (msg, name) => !msg.to || msg.to.length === 0 || msg.to.includes(name);
|
|
6701
6746
|
|
|
6747
|
+
// ../shared/src/domain/file.ts
|
|
6748
|
+
var FILE_TEXT_MAX_BYTES = 5 * 1024 * 1024;
|
|
6749
|
+
var FILE_DIRECTORY_MAX_ENTRIES = 2e3;
|
|
6750
|
+
var FILE_SEARCH_MAX_RESULTS = 500;
|
|
6751
|
+
var FILE_SEARCH_DEADLINE_MS = 5e3;
|
|
6752
|
+
var FILE_SEARCH_QUERY_MAX_LENGTH = 200;
|
|
6753
|
+
var FILE_RAW_MAX_BYTES = 5 * 1024 * 1024;
|
|
6754
|
+
var IMAGE_MEDIA_TYPES = {
|
|
6755
|
+
png: "image/png",
|
|
6756
|
+
jpg: "image/jpeg",
|
|
6757
|
+
jpeg: "image/jpeg",
|
|
6758
|
+
gif: "image/gif",
|
|
6759
|
+
webp: "image/webp",
|
|
6760
|
+
avif: "image/avif",
|
|
6761
|
+
bmp: "image/bmp",
|
|
6762
|
+
ico: "image/x-icon",
|
|
6763
|
+
svg: "image/svg+xml"
|
|
6764
|
+
};
|
|
6765
|
+
var RAW_MEDIA_TYPES = {
|
|
6766
|
+
...IMAGE_MEDIA_TYPES,
|
|
6767
|
+
css: "text/css",
|
|
6768
|
+
woff: "font/woff",
|
|
6769
|
+
woff2: "font/woff2",
|
|
6770
|
+
ttf: "font/ttf",
|
|
6771
|
+
otf: "font/otf"
|
|
6772
|
+
};
|
|
6773
|
+
var mediaTypeIn = (types3) => (path) => {
|
|
6774
|
+
const extension2 = /\.([a-z\d]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
6775
|
+
return extension2 && types3[extension2] || null;
|
|
6776
|
+
};
|
|
6777
|
+
var imageMediaType = mediaTypeIn(IMAGE_MEDIA_TYPES);
|
|
6778
|
+
var rawMediaType = mediaTypeIn(RAW_MEDIA_TYPES);
|
|
6779
|
+
var normalizeFilePath = (value, allowRoot = false) => {
|
|
6780
|
+
if (value === "") return allowRoot ? "" : null;
|
|
6781
|
+
if (value.includes("\0") || value.includes("\\") || value.startsWith("/")) return null;
|
|
6782
|
+
if (/^[A-Za-z]:/.test(value)) return null;
|
|
6783
|
+
return value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..") ? value : null;
|
|
6784
|
+
};
|
|
6785
|
+
var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([".git", ".ssh", ".aws", ".kube", ".gnupg"]);
|
|
6786
|
+
var SENSITIVE_NAMES = /* @__PURE__ */ new Set([
|
|
6787
|
+
".baton.json",
|
|
6788
|
+
".envrc",
|
|
6789
|
+
".npmrc",
|
|
6790
|
+
".netrc",
|
|
6791
|
+
".pypirc",
|
|
6792
|
+
".git-credentials",
|
|
6793
|
+
".pgpass",
|
|
6794
|
+
".htpasswd",
|
|
6795
|
+
".credentials.json"
|
|
6796
|
+
]);
|
|
6797
|
+
var SENSITIVE_EXTENSIONS = [".pem", ".key", ".p12", ".pfx", ".jks", ".keystore"];
|
|
6798
|
+
var isSensitiveName = (name) => SENSITIVE_NAMES.has(name) || /^\.env(\..+)?$/.test(name) && !/\.(example|sample|template)$/.test(name) || /^id_(rsa|dsa|ecdsa|ed25519)$/.test(name) || SENSITIVE_EXTENSIONS.some((extension2) => name.endsWith(extension2));
|
|
6799
|
+
var isSensitiveFilePath = (path) => {
|
|
6800
|
+
const segments = path.toLowerCase().split("/");
|
|
6801
|
+
return segments.some((segment) => SENSITIVE_SEGMENTS.has(segment)) || isSensitiveName(segments.at(-1) ?? "");
|
|
6802
|
+
};
|
|
6803
|
+
var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
6804
|
+
"node_modules",
|
|
6805
|
+
".pnpm-store",
|
|
6806
|
+
"dist",
|
|
6807
|
+
"build",
|
|
6808
|
+
"coverage",
|
|
6809
|
+
".next",
|
|
6810
|
+
".turbo",
|
|
6811
|
+
".cache",
|
|
6812
|
+
".venv",
|
|
6813
|
+
"__pycache__",
|
|
6814
|
+
"target"
|
|
6815
|
+
]);
|
|
6816
|
+
var isExcludedDirectoryName = (name) => EXCLUDED_DIRECTORIES.has(name);
|
|
6817
|
+
|
|
6702
6818
|
// ../shared/src/domain/service.ts
|
|
6703
6819
|
var SERVICE_PUBLIC_URL_MAX_LENGTH = 2048;
|
|
6704
6820
|
var SERVICE_NOTE_MAX_LENGTH = 500;
|
|
@@ -6713,6 +6829,12 @@ var isServicePublicUrl = (value) => {
|
|
|
6713
6829
|
};
|
|
6714
6830
|
var isServiceNote = (value) => value.length > 0 && value.length <= SERVICE_NOTE_MAX_LENGTH;
|
|
6715
6831
|
|
|
6832
|
+
// ../shared/src/domain/session.ts
|
|
6833
|
+
var AGENT_KINDS = ["claude-code", "codex", "pi"];
|
|
6834
|
+
var isAgentKind = (v) => typeof v === "string" && AGENT_KINDS.includes(v);
|
|
6835
|
+
var EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
6836
|
+
var isAgentEffort = (v) => typeof v === "string" && EFFORTS.includes(v);
|
|
6837
|
+
|
|
6716
6838
|
// ../shared/src/domain/session-event.ts
|
|
6717
6839
|
var agentMessageText = (payload) => {
|
|
6718
6840
|
if (typeof payload !== "object" || payload === null) return null;
|
|
@@ -6726,6 +6848,15 @@ var agentMessageText = (payload) => {
|
|
|
6726
6848
|
return trimmed ? { id, text: trimmed } : null;
|
|
6727
6849
|
};
|
|
6728
6850
|
|
|
6851
|
+
// ../shared/src/domain/worker-creation.ts
|
|
6852
|
+
var WORKER_TEMPLATES = [
|
|
6853
|
+
{ id: "mini-glm-codeplan", name: "GLM Code Plan", agentKinds: ["claude-code", "pi"] },
|
|
6854
|
+
{ id: "mini-dashscope-codeplan", name: "DashScope Code Plan", agentKinds: ["claude-code", "pi"] },
|
|
6855
|
+
{ id: "mini-codex", name: "\u672C\u673A Codex", agentKinds: ["codex"] }
|
|
6856
|
+
];
|
|
6857
|
+
var workerTemplateAgentKinds = (id) => id === "mini-local" ? AGENT_KINDS : WORKER_TEMPLATES.find((template) => template.id === id)?.agentKinds ?? [];
|
|
6858
|
+
var isWorkerTemplateId = (value) => typeof value === "string" && workerTemplateAgentKinds(value).length > 0;
|
|
6859
|
+
|
|
6729
6860
|
// src/commands/channel/listen.ts
|
|
6730
6861
|
var PREVIEW = 280;
|
|
6731
6862
|
var describe = (e) => {
|
|
@@ -6776,10 +6907,10 @@ var runListen = async (o) => {
|
|
|
6776
6907
|
onError: (e) => emit({ type: "channel.error", error: describe(e) })
|
|
6777
6908
|
});
|
|
6778
6909
|
emit({ type: "channel.listening", channel: o.channel, from: o.from, url: o.url });
|
|
6779
|
-
await new Promise((
|
|
6910
|
+
await new Promise((resolve4) => {
|
|
6780
6911
|
const stop = () => {
|
|
6781
6912
|
close();
|
|
6782
|
-
|
|
6913
|
+
resolve4();
|
|
6783
6914
|
};
|
|
6784
6915
|
process.on("SIGINT", stop);
|
|
6785
6916
|
process.on("SIGTERM", stop);
|
|
@@ -7034,10 +7165,6 @@ var init = defineCommand({
|
|
|
7034
7165
|
});
|
|
7035
7166
|
|
|
7036
7167
|
// src/commands/session/shared.ts
|
|
7037
|
-
import { homedir } from "node:os";
|
|
7038
|
-
import { join as join3 } from "node:path";
|
|
7039
|
-
var defaultWorktreeDir = (env = process.env) => env.BATON_WORKTREE_DIR ?? join3(env.XDG_DATA_HOME ?? join3(env.HOME ?? homedir(), ".local/share"), "baton", "worktrees");
|
|
7040
|
-
var slug = (s) => s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
|
|
7041
7168
|
var resolveSession = async (client, projectId, handle) => {
|
|
7042
7169
|
const asInt = Number(handle);
|
|
7043
7170
|
if (Number.isInteger(asInt) && asInt > 0) {
|
|
@@ -7190,6 +7317,34 @@ var loop = defineCommand({
|
|
|
7190
7317
|
}
|
|
7191
7318
|
});
|
|
7192
7319
|
|
|
7320
|
+
// src/commands/notify.ts
|
|
7321
|
+
var describe2 = (result) => {
|
|
7322
|
+
if (result.status === "sent") return `notified ${result.devices} device(s)`;
|
|
7323
|
+
if (result.status === "throttled")
|
|
7324
|
+
return `throttled: this session can notify again in ${result.retryAfterSec}s`;
|
|
7325
|
+
return "nobody to notify: no human has messaged this session, or they have not enabled notifications";
|
|
7326
|
+
};
|
|
7327
|
+
var notify = defineCommand({
|
|
7328
|
+
meta: {
|
|
7329
|
+
name: "notify",
|
|
7330
|
+
description: "Push a phone notification to the person working with this session. Use it only when a human needs to act now: something is wrong, a decision is needed, or the work is blocked. Do not use it for routine progress, and not to announce that a turn finished (that is already pushed). At most one notification per session every 10 minutes; text is cut at 200 characters."
|
|
7331
|
+
},
|
|
7332
|
+
args: {
|
|
7333
|
+
text: { type: "positional", required: true, description: "one short line for the lock screen" },
|
|
7334
|
+
...common
|
|
7335
|
+
},
|
|
7336
|
+
run: async ({ args }) => {
|
|
7337
|
+
const raw = process.env.BATON_SESSION_ID;
|
|
7338
|
+
if (raw === void 0)
|
|
7339
|
+
throw new Error("baton notify only works inside a baton session (BATON_SESSION_ID is unset)");
|
|
7340
|
+
const sessionId = Number(raw);
|
|
7341
|
+
if (!Number.isSafeInteger(sessionId) || sessionId <= 0)
|
|
7342
|
+
throw new Error("invalid BATON_SESSION_ID");
|
|
7343
|
+
const result = await clientFor(args).sessions.notify(sessionId, args.text);
|
|
7344
|
+
console.log(args.json ? toJson(result) : describe2(result));
|
|
7345
|
+
}
|
|
7346
|
+
});
|
|
7347
|
+
|
|
7193
7348
|
// src/commands/project.ts
|
|
7194
7349
|
var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
|
|
7195
7350
|
var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
|
|
@@ -7264,10 +7419,519 @@ var project = defineCommand({
|
|
|
7264
7419
|
}
|
|
7265
7420
|
});
|
|
7266
7421
|
|
|
7422
|
+
// src/provisioner/config.ts
|
|
7423
|
+
import { execFile } from "node:child_process";
|
|
7424
|
+
import { constants } from "node:fs";
|
|
7425
|
+
import { access, readFile, stat as stat2 } from "node:fs/promises";
|
|
7426
|
+
import { isAbsolute } from "node:path";
|
|
7427
|
+
import { promisify } from "node:util";
|
|
7428
|
+
var forbiddenEnv = /* @__PURE__ */ new Set([
|
|
7429
|
+
"BATON_PROVISIONER_TOKEN",
|
|
7430
|
+
"BATON_TOKEN",
|
|
7431
|
+
"BATON_WORKER_TOKEN",
|
|
7432
|
+
"BATON_USER",
|
|
7433
|
+
"BATON_PASS",
|
|
7434
|
+
"HOME",
|
|
7435
|
+
"XDG_DATA_HOME"
|
|
7436
|
+
]);
|
|
7437
|
+
var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7438
|
+
var absolutePath = (value, field) => {
|
|
7439
|
+
if (typeof value !== "string" || !isAbsolute(value) || value.includes("\0"))
|
|
7440
|
+
throw new Error(`${field} must be an absolute path`);
|
|
7441
|
+
return value;
|
|
7442
|
+
};
|
|
7443
|
+
var environment = (value) => {
|
|
7444
|
+
if (!object(value)) throw new Error("workerEnv must be an object");
|
|
7445
|
+
return Object.fromEntries(
|
|
7446
|
+
Object.entries(value).map(([key, entry]) => {
|
|
7447
|
+
if (forbiddenEnv.has(key) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof entry !== "string" || entry.includes("\0"))
|
|
7448
|
+
throw new Error(`invalid worker environment key: ${key}`);
|
|
7449
|
+
return [key, entry];
|
|
7450
|
+
})
|
|
7451
|
+
);
|
|
7452
|
+
};
|
|
7453
|
+
var templates = (value) => {
|
|
7454
|
+
if (!object(value)) throw new Error("templates must be an object");
|
|
7455
|
+
return Object.fromEntries(
|
|
7456
|
+
WORKER_TEMPLATES.map((template) => {
|
|
7457
|
+
const raw = value[template.id];
|
|
7458
|
+
if (!object(raw)) throw new Error(`template ${template.id} must be configured`);
|
|
7459
|
+
const workerEnv2 = environment(raw.workerEnv);
|
|
7460
|
+
if (template.id === "mini-codex") {
|
|
7461
|
+
if (raw.pi !== void 0) throw new Error("mini-codex does not support pi");
|
|
7462
|
+
return [template.id, { workerEnv: workerEnv2 }];
|
|
7463
|
+
}
|
|
7464
|
+
const provider = template.id === "mini-glm-codeplan" ? "zai-coding-cn" : "qwen-token-plan-cn";
|
|
7465
|
+
const apiKey = template.id === "mini-glm-codeplan" ? "ZAI_CODING_CN_API_KEY" : "QWEN_TOKEN_PLAN_CN_API_KEY";
|
|
7466
|
+
if (!workerEnv2.ANTHROPIC_BASE_URL || !(workerEnv2.ANTHROPIC_AUTH_TOKEN || workerEnv2.ANTHROPIC_API_KEY) || !workerEnv2[apiKey])
|
|
7467
|
+
throw new Error(`template ${template.id} requires Claude and Pi credentials`);
|
|
7468
|
+
if (!object(raw.pi) || raw.pi.provider !== provider || typeof raw.pi.model !== "string" || !raw.pi.model.trim())
|
|
7469
|
+
throw new Error(`template ${template.id} requires its Pi provider and model`);
|
|
7470
|
+
return [template.id, { workerEnv: workerEnv2, pi: { provider, model: raw.pi.model.trim() } }];
|
|
7471
|
+
})
|
|
7472
|
+
);
|
|
7473
|
+
};
|
|
7474
|
+
var commandEnvironment = (config) => ({
|
|
7475
|
+
...process.env,
|
|
7476
|
+
PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin"
|
|
7477
|
+
});
|
|
7478
|
+
var loadProvisionerConfig = async (path) => {
|
|
7479
|
+
if (process.platform !== "darwin") throw new Error("provisioner requires macOS");
|
|
7480
|
+
absolutePath(path, "config");
|
|
7481
|
+
if ((await stat2(path)).mode & 63) throw new Error("config must be private: chmod 600 <config>");
|
|
7482
|
+
const text = await readFile(path, "utf8");
|
|
7483
|
+
let raw;
|
|
7484
|
+
try {
|
|
7485
|
+
raw = JSON.parse(text);
|
|
7486
|
+
} catch (error) {
|
|
7487
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
7488
|
+
throw new Error("invalid provisioner JSON config");
|
|
7489
|
+
}
|
|
7490
|
+
if (!object(raw) || typeof raw.server !== "string" || typeof raw.token !== "string")
|
|
7491
|
+
throw new Error("server and token required");
|
|
7492
|
+
let server;
|
|
7493
|
+
try {
|
|
7494
|
+
server = new URL(raw.server);
|
|
7495
|
+
} catch (error) {
|
|
7496
|
+
if (!(error instanceof TypeError)) throw error;
|
|
7497
|
+
throw new Error("server must be a valid HTTP(S) URL");
|
|
7498
|
+
}
|
|
7499
|
+
if (!["http:", "https:"].includes(server.protocol) || server.username || server.password || server.search || server.hash)
|
|
7500
|
+
throw new Error("server must be an HTTP(S) URL without credentials, query, or fragment");
|
|
7501
|
+
if (!raw.token.trim() || /[\r\n]/.test(raw.token))
|
|
7502
|
+
throw new Error("valid provisioner token required");
|
|
7503
|
+
const config = {
|
|
7504
|
+
server: server.toString().replace(/\/$/, ""),
|
|
7505
|
+
token: raw.token.trim(),
|
|
7506
|
+
projectsRoot: absolutePath(raw.projectsRoot, "projectsRoot"),
|
|
7507
|
+
cliPath: absolutePath(raw.cliPath, "cliPath"),
|
|
7508
|
+
nodePath: absolutePath(raw.nodePath ?? process.execPath, "nodePath"),
|
|
7509
|
+
workerEnv: environment(raw.workerEnv),
|
|
7510
|
+
templates: templates(raw.templates)
|
|
7511
|
+
};
|
|
7512
|
+
await access(config.cliPath, constants.R_OK);
|
|
7513
|
+
await access(config.nodePath ?? process.execPath, constants.X_OK);
|
|
7514
|
+
const env = commandEnvironment(config);
|
|
7515
|
+
await promisify(execFile)("git", ["--version"], { env, timeout: 1e4 });
|
|
7516
|
+
await promisify(execFile)("launchctl", ["help"], { env, timeout: 1e4 });
|
|
7517
|
+
await promisify(execFile)("/usr/bin/plutil", ["-help"], { timeout: 1e4 });
|
|
7518
|
+
return config;
|
|
7519
|
+
};
|
|
7520
|
+
|
|
7521
|
+
// src/provisioner/client.ts
|
|
7522
|
+
var createProvisionerClient = (config) => {
|
|
7523
|
+
const request2 = async (path, token, body, signal) => {
|
|
7524
|
+
const timeout = AbortSignal.timeout(1e4);
|
|
7525
|
+
const response = await fetch(`${config.server}${path}`, {
|
|
7526
|
+
method: body === void 0 ? "GET" : "POST",
|
|
7527
|
+
headers: {
|
|
7528
|
+
authorization: `Bearer ${token}`,
|
|
7529
|
+
...body === void 0 ? {} : { "content-type": "application/json" }
|
|
7530
|
+
},
|
|
7531
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
7532
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
7533
|
+
});
|
|
7534
|
+
if (!response.ok) {
|
|
7535
|
+
await response.body?.cancel();
|
|
7536
|
+
throw new HttpError(response.status, `provisioner request ${path}: HTTP ${response.status}`);
|
|
7537
|
+
}
|
|
7538
|
+
return await response.json();
|
|
7539
|
+
};
|
|
7540
|
+
return {
|
|
7541
|
+
get: (id, signal) => request2(`/provisioner/worker-creations/${id}`, config.token, void 0, signal),
|
|
7542
|
+
report: (id, report, signal) => request2(`/provisioner/worker-creations/${id}/progress`, config.token, report, signal),
|
|
7543
|
+
register: (id, hostname2, signal) => request2(`/provisioner/worker-creations/${id}/worker`, config.token, { hostname: hostname2 }, signal),
|
|
7544
|
+
worker: (id, token, signal) => request2(`/workers/${id}`, token, void 0, signal)
|
|
7545
|
+
};
|
|
7546
|
+
};
|
|
7547
|
+
|
|
7548
|
+
// src/provisioner/executor.ts
|
|
7549
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
7550
|
+
import { lstat, mkdir as mkdir3, realpath, writeFile as writeFile2 } from "node:fs/promises";
|
|
7551
|
+
import { hostname } from "node:os";
|
|
7552
|
+
import { join as join5 } from "node:path";
|
|
7553
|
+
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
7554
|
+
import { promisify as promisify3 } from "node:util";
|
|
7555
|
+
|
|
7556
|
+
// src/provisioner/launchd.ts
|
|
7557
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
7558
|
+
import { mkdir, open } from "node:fs/promises";
|
|
7559
|
+
import { homedir } from "node:os";
|
|
7560
|
+
import { dirname, join as join3 } from "node:path";
|
|
7561
|
+
import { promisify as promisify2 } from "node:util";
|
|
7562
|
+
var workerStatePath = (config, creation) => join3(
|
|
7563
|
+
config.projectsRoot,
|
|
7564
|
+
".worker-state",
|
|
7565
|
+
`project-${creation.projectId}`,
|
|
7566
|
+
`creation-${creation.id}`
|
|
7567
|
+
);
|
|
7568
|
+
var workerLogPath = (creation) => join3(homedir(), "Library/Logs", `baton-worker-creation-${creation.id}.log`);
|
|
7569
|
+
var startAgent = async (config, agent, signal) => {
|
|
7570
|
+
signal?.throwIfAborted();
|
|
7571
|
+
const plist = join3(homedir(), "Library/LaunchAgents", `${agent.label}.plist`);
|
|
7572
|
+
await mkdir(dirname(plist), { recursive: true });
|
|
7573
|
+
await mkdir(dirname(agent.log), { recursive: true });
|
|
7574
|
+
const domain = `gui/${process.getuid?.()}`;
|
|
7575
|
+
const env = commandEnvironment(config);
|
|
7576
|
+
try {
|
|
7577
|
+
await promisify2(execFile2)("launchctl", ["print", `${domain}/${agent.label}`], {
|
|
7578
|
+
env,
|
|
7579
|
+
timeout: 1e4,
|
|
7580
|
+
signal
|
|
7581
|
+
});
|
|
7582
|
+
throw new Error(
|
|
7583
|
+
`launchd service already exists: ${agent.label}; stop it manually before installing`
|
|
7584
|
+
);
|
|
7585
|
+
} catch (error) {
|
|
7586
|
+
if (!(error instanceof Error && "code" in error && error.code === 113 && "stderr" in error && typeof error.stderr === "string" && error.stderr.includes("Could not find service")))
|
|
7587
|
+
throw error;
|
|
7588
|
+
}
|
|
7589
|
+
const handle = await open(plist, "wx", 384);
|
|
7590
|
+
try {
|
|
7591
|
+
const payload = {
|
|
7592
|
+
Label: agent.label,
|
|
7593
|
+
ProgramArguments: agent.args,
|
|
7594
|
+
WorkingDirectory: agent.cwd,
|
|
7595
|
+
EnvironmentVariables: agent.env,
|
|
7596
|
+
RunAtLoad: true,
|
|
7597
|
+
KeepAlive: true,
|
|
7598
|
+
ThrottleInterval: 10,
|
|
7599
|
+
StandardOutPath: agent.log,
|
|
7600
|
+
StandardErrorPath: agent.log
|
|
7601
|
+
};
|
|
7602
|
+
await handle.writeFile(JSON.stringify(payload));
|
|
7603
|
+
} finally {
|
|
7604
|
+
await handle.close();
|
|
7605
|
+
}
|
|
7606
|
+
const log = await open(agent.log, "a", 384);
|
|
7607
|
+
await log.close();
|
|
7608
|
+
try {
|
|
7609
|
+
await promisify2(execFile2)("/usr/bin/plutil", ["-convert", "xml1", plist], {
|
|
7610
|
+
timeout: 1e4,
|
|
7611
|
+
signal
|
|
7612
|
+
});
|
|
7613
|
+
await promisify2(execFile2)("/usr/bin/plutil", ["-lint", plist], { timeout: 1e4, signal });
|
|
7614
|
+
await promisify2(execFile2)("launchctl", ["bootstrap", domain, plist], {
|
|
7615
|
+
env,
|
|
7616
|
+
timeout: 1e4,
|
|
7617
|
+
signal
|
|
7618
|
+
});
|
|
7619
|
+
} catch {
|
|
7620
|
+
throw new Error(`launchd start failed; inspect ${plist} and ${agent.log}`);
|
|
7621
|
+
}
|
|
7622
|
+
};
|
|
7623
|
+
var launchWorkerAgent = async (config, creation, repoPath, workerEnv2, signal) => startAgent(
|
|
7624
|
+
config,
|
|
7625
|
+
{
|
|
7626
|
+
label: `dev.fmap.baton-worker-creation-${creation.id}`,
|
|
7627
|
+
args: [config.nodePath ?? process.execPath, config.cliPath, "worker", "run"],
|
|
7628
|
+
cwd: repoPath,
|
|
7629
|
+
env: {
|
|
7630
|
+
PATH: workerEnv2.PATH ?? process.env.PATH ?? "/usr/bin:/bin",
|
|
7631
|
+
...workerEnv2,
|
|
7632
|
+
HOME: homedir(),
|
|
7633
|
+
XDG_DATA_HOME: workerStatePath(config, creation)
|
|
7634
|
+
},
|
|
7635
|
+
log: workerLogPath(creation)
|
|
7636
|
+
},
|
|
7637
|
+
signal
|
|
7638
|
+
);
|
|
7639
|
+
var installProvisionerAgent = async (config, configPath) => startAgent(config, {
|
|
7640
|
+
label: "dev.fmap.baton-provisioner",
|
|
7641
|
+
args: [
|
|
7642
|
+
config.nodePath ?? process.execPath,
|
|
7643
|
+
config.cliPath,
|
|
7644
|
+
"provisioner",
|
|
7645
|
+
"run",
|
|
7646
|
+
"--config",
|
|
7647
|
+
configPath
|
|
7648
|
+
],
|
|
7649
|
+
cwd: dirname(configPath),
|
|
7650
|
+
env: { HOME: homedir(), PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin" },
|
|
7651
|
+
log: join3(homedir(), "Library/Logs/baton-provisioner.log")
|
|
7652
|
+
});
|
|
7653
|
+
|
|
7654
|
+
// src/provisioner/worker-template.ts
|
|
7655
|
+
import { mkdir as mkdir2, writeFile } from "node:fs/promises";
|
|
7656
|
+
import { join as join4 } from "node:path";
|
|
7657
|
+
var workerTemplateEnvironment = async (config, creation, state) => {
|
|
7658
|
+
if (!workerTemplateAgentKinds(creation.templateId).includes(creation.agentKind))
|
|
7659
|
+
throw new Error("agentKind is not supported by this template");
|
|
7660
|
+
if (creation.templateId === "mini-local") return config.workerEnv;
|
|
7661
|
+
const profile = config.templates[creation.templateId];
|
|
7662
|
+
if (!profile) throw new Error(`template ${creation.templateId} is not configured`);
|
|
7663
|
+
if (creation.agentKind !== "pi") return profile.workerEnv;
|
|
7664
|
+
if (!profile.pi) throw new Error("Pi provider and model are not configured");
|
|
7665
|
+
const agentDir = join4(state, "pi");
|
|
7666
|
+
await mkdir2(agentDir, { mode: 448 });
|
|
7667
|
+
await writeFile(
|
|
7668
|
+
join4(agentDir, "settings.json"),
|
|
7669
|
+
JSON.stringify({
|
|
7670
|
+
defaultProvider: profile.pi.provider,
|
|
7671
|
+
defaultModel: profile.pi.model
|
|
7672
|
+
}),
|
|
7673
|
+
{ flag: "wx", mode: 384 }
|
|
7674
|
+
);
|
|
7675
|
+
return { ...profile.workerEnv, PI_CODING_AGENT_DIR: agentDir };
|
|
7676
|
+
};
|
|
7677
|
+
|
|
7678
|
+
// src/provisioner/executor.ts
|
|
7679
|
+
var directory = async (path) => {
|
|
7680
|
+
try {
|
|
7681
|
+
await mkdir3(path, { mode: 448 });
|
|
7682
|
+
} catch (error) {
|
|
7683
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
7684
|
+
}
|
|
7685
|
+
const info = await lstat(path);
|
|
7686
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`unsafe directory: ${path}`);
|
|
7687
|
+
};
|
|
7688
|
+
var provisionWorker = async (config, creation, client, signal) => {
|
|
7689
|
+
const repoPath = join5(
|
|
7690
|
+
config.projectsRoot,
|
|
7691
|
+
`project-${creation.projectId}`,
|
|
7692
|
+
`creation-${creation.id}`
|
|
7693
|
+
);
|
|
7694
|
+
const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
|
|
7695
|
+
await progress("directory");
|
|
7696
|
+
signal.throwIfAborted();
|
|
7697
|
+
await mkdir3(config.projectsRoot, { recursive: true, mode: 448 });
|
|
7698
|
+
const root = await realpath(config.projectsRoot);
|
|
7699
|
+
await directory(join5(root, `project-${creation.projectId}`));
|
|
7700
|
+
await mkdir3(join5(root, `project-${creation.projectId}`, `creation-${creation.id}`), {
|
|
7701
|
+
mode: 448
|
|
7702
|
+
});
|
|
7703
|
+
await progress("git");
|
|
7704
|
+
const git2 = async (args) => {
|
|
7705
|
+
try {
|
|
7706
|
+
await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
|
|
7707
|
+
env: commandEnvironment(config),
|
|
7708
|
+
signal,
|
|
7709
|
+
timeout: 1e4
|
|
7710
|
+
});
|
|
7711
|
+
} catch (error) {
|
|
7712
|
+
signal.throwIfAborted();
|
|
7713
|
+
throw new Error("Git repository initialization failed", { cause: error });
|
|
7714
|
+
}
|
|
7715
|
+
};
|
|
7716
|
+
await git2(["init", "-b", "main"]);
|
|
7717
|
+
await writeFile2(join5(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
|
|
7718
|
+
await git2([
|
|
7719
|
+
"-c",
|
|
7720
|
+
"user.name=Baton",
|
|
7721
|
+
"-c",
|
|
7722
|
+
"user.email=baton@localhost",
|
|
7723
|
+
"commit",
|
|
7724
|
+
"--allow-empty",
|
|
7725
|
+
"-m",
|
|
7726
|
+
"chore: initialize repository"
|
|
7727
|
+
]);
|
|
7728
|
+
await progress("register");
|
|
7729
|
+
const result = await client.register(creation.id, hostname(), signal);
|
|
7730
|
+
const state = workerStatePath(config, creation);
|
|
7731
|
+
await directory(join5(root, ".worker-state"));
|
|
7732
|
+
await directory(join5(root, ".worker-state", `project-${creation.projectId}`));
|
|
7733
|
+
await mkdir3(state, { mode: 448 });
|
|
7734
|
+
await mkdir3(join5(state, "baton"), { mode: 448 });
|
|
7735
|
+
await writeFile2(join5(state, "baton/machine-id"), `${creation.machineId}
|
|
7736
|
+
`, {
|
|
7737
|
+
flag: "wx",
|
|
7738
|
+
mode: 384
|
|
7739
|
+
});
|
|
7740
|
+
const cfg = workerProjectConfig({
|
|
7741
|
+
server: config.server,
|
|
7742
|
+
projectId: creation.projectId,
|
|
7743
|
+
workerId: result.worker.id,
|
|
7744
|
+
name: result.worker.name,
|
|
7745
|
+
agentKind: result.worker.agentKind,
|
|
7746
|
+
machineId: result.worker.machineId,
|
|
7747
|
+
apiToken: result.apiToken
|
|
7748
|
+
});
|
|
7749
|
+
await writeFile2(join5(repoPath, ".baton.json"), `${JSON.stringify(cfg, null, 2)}
|
|
7750
|
+
`, {
|
|
7751
|
+
flag: "wx",
|
|
7752
|
+
mode: 384
|
|
7753
|
+
});
|
|
7754
|
+
await progress("start");
|
|
7755
|
+
signal.throwIfAborted();
|
|
7756
|
+
const workerEnv2 = await workerTemplateEnvironment(config, creation, state);
|
|
7757
|
+
await launchWorkerAgent(config, creation, repoPath, workerEnv2, signal);
|
|
7758
|
+
await progress("connect");
|
|
7759
|
+
const deadline = Date.now() + 6e4;
|
|
7760
|
+
while (Date.now() < deadline) {
|
|
7761
|
+
const worker2 = await client.worker(result.worker.id, result.apiToken, signal);
|
|
7762
|
+
if (worker2.connected) {
|
|
7763
|
+
await client.report(creation.id, { status: "succeeded" }, signal);
|
|
7764
|
+
return;
|
|
7765
|
+
}
|
|
7766
|
+
await setTimeout2(1e3, void 0, { signal });
|
|
7767
|
+
}
|
|
7768
|
+
throw new Error("Worker did not connect within 60 seconds");
|
|
7769
|
+
};
|
|
7770
|
+
|
|
7771
|
+
// src/provisioner/daemon.ts
|
|
7772
|
+
var taskError = (error, config) => {
|
|
7773
|
+
const message = error instanceof Error ? error.message : "unknown creation error";
|
|
7774
|
+
return [
|
|
7775
|
+
config.token,
|
|
7776
|
+
...Object.values(config.workerEnv),
|
|
7777
|
+
...Object.values(config.templates).flatMap((template) => Object.values(template.workerEnv))
|
|
7778
|
+
].filter((value) => value.length > 0).reduce((safe, secret) => safe.replaceAll(secret, "[redacted]"), message);
|
|
7779
|
+
};
|
|
7780
|
+
var processCreation = async (id, config, client, signal) => {
|
|
7781
|
+
let creation;
|
|
7782
|
+
try {
|
|
7783
|
+
creation = await client.get(id, signal);
|
|
7784
|
+
} catch (error) {
|
|
7785
|
+
if (error instanceof HttpError && error.status === 404) {
|
|
7786
|
+
console.error(`[provisioner] creation ${id} no longer exists`);
|
|
7787
|
+
return;
|
|
7788
|
+
}
|
|
7789
|
+
throw error;
|
|
7790
|
+
}
|
|
7791
|
+
if (creation.status === "succeeded" || creation.status === "failed") return;
|
|
7792
|
+
if (creation.status === "running") {
|
|
7793
|
+
await client.report(
|
|
7794
|
+
id,
|
|
7795
|
+
{
|
|
7796
|
+
status: "failed",
|
|
7797
|
+
error: "Provisioner interrupted; inspect retained resources before creating again"
|
|
7798
|
+
},
|
|
7799
|
+
signal
|
|
7800
|
+
);
|
|
7801
|
+
return;
|
|
7802
|
+
}
|
|
7803
|
+
try {
|
|
7804
|
+
await provisionWorker(config, creation, client, signal);
|
|
7805
|
+
} catch (error) {
|
|
7806
|
+
if (signal.aborted) {
|
|
7807
|
+
console.error(`[provisioner] creation ${id} interrupted: ${taskError(error, config)}`);
|
|
7808
|
+
return;
|
|
7809
|
+
}
|
|
7810
|
+
if (!(error instanceof HttpError) || ![401, 403].includes(error.status)) {
|
|
7811
|
+
const message = `${taskError(error, config)}; worker log: ${workerLogPath(creation)}`;
|
|
7812
|
+
console.error(`[provisioner] creation ${id}: ${message}`);
|
|
7813
|
+
await client.report(id, { status: "failed", error: message }, signal);
|
|
7814
|
+
return;
|
|
7815
|
+
}
|
|
7816
|
+
throw error;
|
|
7817
|
+
}
|
|
7818
|
+
};
|
|
7819
|
+
var runProvisioner = async (config, signal) => {
|
|
7820
|
+
const client = createProvisionerClient(config);
|
|
7821
|
+
const abort = new AbortController();
|
|
7822
|
+
const queued = /* @__PURE__ */ new Set();
|
|
7823
|
+
let tail = Promise.resolve();
|
|
7824
|
+
let fatal;
|
|
7825
|
+
let finish = () => {
|
|
7826
|
+
};
|
|
7827
|
+
const ended = new Promise((resolve4) => {
|
|
7828
|
+
finish = resolve4;
|
|
7829
|
+
});
|
|
7830
|
+
const stream = new EventSource(`${config.server}/provisioner/stream`, {
|
|
7831
|
+
fetch: (url, init2) => fetch(url, {
|
|
7832
|
+
...init2,
|
|
7833
|
+
headers: { ...init2?.headers, authorization: `Bearer ${config.token}` },
|
|
7834
|
+
signal: abort.signal
|
|
7835
|
+
})
|
|
7836
|
+
});
|
|
7837
|
+
const stop = () => {
|
|
7838
|
+
stream.close();
|
|
7839
|
+
abort.abort();
|
|
7840
|
+
finish();
|
|
7841
|
+
};
|
|
7842
|
+
const fail = (error) => {
|
|
7843
|
+
fatal = error;
|
|
7844
|
+
console.error(`[provisioner] ${taskError(error, config)}`);
|
|
7845
|
+
stop();
|
|
7846
|
+
};
|
|
7847
|
+
stream.onmessage = (event) => {
|
|
7848
|
+
try {
|
|
7849
|
+
const message = JSON.parse(event.data);
|
|
7850
|
+
if (!message || !Number.isSafeInteger(message.creationId) || message.creationId <= 0 || !["create", "inspect"].includes(message.action))
|
|
7851
|
+
throw new Error("invalid provisioner signal");
|
|
7852
|
+
if (queued.has(message.creationId)) return;
|
|
7853
|
+
queued.add(message.creationId);
|
|
7854
|
+
tail = tail.then(async () => {
|
|
7855
|
+
try {
|
|
7856
|
+
if (!abort.signal.aborted)
|
|
7857
|
+
await processCreation(message.creationId, config, client, abort.signal);
|
|
7858
|
+
} finally {
|
|
7859
|
+
queued.delete(message.creationId);
|
|
7860
|
+
}
|
|
7861
|
+
}).catch((error) => {
|
|
7862
|
+
if (!abort.signal.aborted) fail(error);
|
|
7863
|
+
else console.error(`[provisioner] stopped: ${taskError(error, config)}`);
|
|
7864
|
+
});
|
|
7865
|
+
} catch (error) {
|
|
7866
|
+
fail(error);
|
|
7867
|
+
}
|
|
7868
|
+
};
|
|
7869
|
+
stream.onerror = (event) => {
|
|
7870
|
+
if (stream.readyState === EventSource.CLOSED || [401, 403, 409].includes(event.code ?? 0))
|
|
7871
|
+
fail(new Error(`provisioner stream rejected: HTTP ${event.code}`));
|
|
7872
|
+
else
|
|
7873
|
+
console.error(`[provisioner] stream disconnected (${event.code ?? "network"}); reconnecting`);
|
|
7874
|
+
};
|
|
7875
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
7876
|
+
if (signal.aborted) stop();
|
|
7877
|
+
console.log("[provisioner] waiting for worker creation requests");
|
|
7878
|
+
try {
|
|
7879
|
+
await ended;
|
|
7880
|
+
await tail;
|
|
7881
|
+
} finally {
|
|
7882
|
+
stop();
|
|
7883
|
+
signal.removeEventListener("abort", stop);
|
|
7884
|
+
}
|
|
7885
|
+
if (fatal !== void 0) throw new Error(taskError(fatal, config));
|
|
7886
|
+
};
|
|
7887
|
+
|
|
7888
|
+
// src/commands/provisioner.ts
|
|
7889
|
+
var configArg = {
|
|
7890
|
+
config: {
|
|
7891
|
+
type: "string",
|
|
7892
|
+
required: true,
|
|
7893
|
+
description: "absolute path to private provisioner JSON config"
|
|
7894
|
+
}
|
|
7895
|
+
};
|
|
7896
|
+
var provisioner = defineCommand({
|
|
7897
|
+
meta: {
|
|
7898
|
+
name: "provisioner",
|
|
7899
|
+
description: "prepare local repositories and launch workers on macOS"
|
|
7900
|
+
},
|
|
7901
|
+
subCommands: {
|
|
7902
|
+
run: defineCommand({
|
|
7903
|
+
args: configArg,
|
|
7904
|
+
run: async ({ args }) => {
|
|
7905
|
+
const config = await loadProvisionerConfig(args.config);
|
|
7906
|
+
const abort = new AbortController();
|
|
7907
|
+
const stop = () => abort.abort();
|
|
7908
|
+
process.on("SIGINT", stop);
|
|
7909
|
+
process.on("SIGTERM", stop);
|
|
7910
|
+
try {
|
|
7911
|
+
await runProvisioner(config, abort.signal);
|
|
7912
|
+
} finally {
|
|
7913
|
+
process.off("SIGINT", stop);
|
|
7914
|
+
process.off("SIGTERM", stop);
|
|
7915
|
+
}
|
|
7916
|
+
}
|
|
7917
|
+
}),
|
|
7918
|
+
install: defineCommand({
|
|
7919
|
+
args: configArg,
|
|
7920
|
+
run: async ({ args }) => {
|
|
7921
|
+
const config = await loadProvisionerConfig(args.config);
|
|
7922
|
+
await installProvisionerAgent(config, args.config);
|
|
7923
|
+
console.log(
|
|
7924
|
+
"installed dev.fmap.baton-provisioner; inspect ~/Library/Logs/baton-provisioner.log"
|
|
7925
|
+
);
|
|
7926
|
+
}
|
|
7927
|
+
})
|
|
7928
|
+
}
|
|
7929
|
+
});
|
|
7930
|
+
|
|
7267
7931
|
// src/commands/relay.ts
|
|
7268
7932
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7269
7933
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
7270
|
-
import { join as
|
|
7934
|
+
import { join as join6 } from "node:path";
|
|
7271
7935
|
|
|
7272
7936
|
// src/client/relay.ts
|
|
7273
7937
|
var relayClient = (baseUrl) => {
|
|
@@ -7395,11 +8059,11 @@ var listenCommand2 = defineCommand({
|
|
|
7395
8059
|
process.stdout.write(`${JSON.stringify(o)}
|
|
7396
8060
|
`);
|
|
7397
8061
|
};
|
|
7398
|
-
const spillDir =
|
|
8062
|
+
const spillDir = join6(tmpdir2(), "hotline", args.channel);
|
|
7399
8063
|
mkdirSync2(spillDir, { recursive: true });
|
|
7400
8064
|
const surface = (m) => {
|
|
7401
8065
|
const text = typeof m.text === "string" ? m.text : "";
|
|
7402
|
-
const file =
|
|
8066
|
+
const file = join6(spillDir, `${m.seq}.txt`);
|
|
7403
8067
|
writeFileSync4(file, text);
|
|
7404
8068
|
const truncated = text.length > PREVIEW2;
|
|
7405
8069
|
emit({
|
|
@@ -7413,7 +8077,7 @@ var listenCommand2 = defineCommand({
|
|
|
7413
8077
|
file
|
|
7414
8078
|
});
|
|
7415
8079
|
};
|
|
7416
|
-
const
|
|
8080
|
+
const describe3 = (e) => {
|
|
7417
8081
|
const ev = e;
|
|
7418
8082
|
return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
|
|
7419
8083
|
};
|
|
@@ -7423,13 +8087,13 @@ var listenCommand2 = defineCommand({
|
|
|
7423
8087
|
onMessage: (m) => {
|
|
7424
8088
|
if (m.from !== me) surface(m);
|
|
7425
8089
|
},
|
|
7426
|
-
onError: (e) => emit({ type: "relay.error", error:
|
|
8090
|
+
onError: (e) => emit({ type: "relay.error", error: describe3(e) })
|
|
7427
8091
|
});
|
|
7428
8092
|
emit({ type: "relay.listening", channel: args.channel, from: me, url });
|
|
7429
|
-
await new Promise((
|
|
8093
|
+
await new Promise((resolve4) => {
|
|
7430
8094
|
const stop = () => {
|
|
7431
8095
|
close();
|
|
7432
|
-
|
|
8096
|
+
resolve4();
|
|
7433
8097
|
};
|
|
7434
8098
|
process.on("SIGINT", stop);
|
|
7435
8099
|
process.on("SIGTERM", stop);
|
|
@@ -7553,6 +8217,9 @@ var requirement = defineCommand({
|
|
|
7553
8217
|
}
|
|
7554
8218
|
});
|
|
7555
8219
|
|
|
8220
|
+
// src/commands/service.ts
|
|
8221
|
+
import { resolve as resolve2 } from "node:path";
|
|
8222
|
+
|
|
7556
8223
|
// src/worker/service-child.ts
|
|
7557
8224
|
import { spawn } from "node:child_process";
|
|
7558
8225
|
|
|
@@ -7633,25 +8300,15 @@ var formatUptime = (startedAt, now = Date.now()) => {
|
|
|
7633
8300
|
if (seconds >= 60) return `${Math.floor(seconds / 60)}m`;
|
|
7634
8301
|
return `${seconds}s`;
|
|
7635
8302
|
};
|
|
7636
|
-
var currentContext = () => {
|
|
7637
|
-
const config = loadProjectConfig(projectConfigPath());
|
|
7638
|
-
if (!config.worker || !config.session)
|
|
7639
|
-
throw new Error("service run requires a Baton session worktree");
|
|
7640
|
-
return { workerId: config.worker.id, sessionId: config.session };
|
|
7641
|
-
};
|
|
7642
|
-
var currentWorker = () => {
|
|
7643
|
-
const worker2 = loadProjectConfig(projectConfigPath()).worker;
|
|
7644
|
-
if (!worker2) throw new Error("no worker in scope");
|
|
7645
|
-
return worker2.id;
|
|
7646
|
-
};
|
|
7647
8303
|
var parseTarget = (target) => {
|
|
7648
8304
|
const explicit = target.match(/^W-(\d+)\/(.+)$/i);
|
|
7649
|
-
return explicit ? { workerId: Number(explicit[1]), name: explicit[2] ?? "" } : { workerId:
|
|
8305
|
+
return explicit ? { workerId: Number(explicit[1]), name: explicit[2] ?? "" } : { workerId: resolveWorkerId(), name: target };
|
|
7650
8306
|
};
|
|
7651
8307
|
var runCommand2 = defineCommand({
|
|
7652
8308
|
meta: { name: "run", description: "run a service on the current worker" },
|
|
7653
8309
|
args: {
|
|
7654
8310
|
name: { type: "positional", required: true, description: "service name" },
|
|
8311
|
+
cwd: { type: "string", required: true, description: "service working directory on the Worker" },
|
|
7655
8312
|
"public-url": { type: "string", description: "public HTTP(S) URL for this service" },
|
|
7656
8313
|
note: { type: "string", description: "short note describing this service" },
|
|
7657
8314
|
...common
|
|
@@ -7666,9 +8323,9 @@ var runCommand2 = defineCommand({
|
|
|
7666
8323
|
throw new Error("--public-url must be an absolute HTTP(S) URL");
|
|
7667
8324
|
if (note && !isServiceNote(note))
|
|
7668
8325
|
throw new Error("service note must be 500 characters or fewer");
|
|
7669
|
-
const
|
|
7670
|
-
const result = await clientFor(args).services.run(
|
|
7671
|
-
|
|
8326
|
+
const workerId = resolveWorkerId();
|
|
8327
|
+
const result = await clientFor(args).services.run(workerId, {
|
|
8328
|
+
cwd: resolve2(args.cwd),
|
|
7672
8329
|
name: args.name,
|
|
7673
8330
|
argv,
|
|
7674
8331
|
...publicUrl ? { publicUrl } : {},
|
|
@@ -7676,7 +8333,7 @@ var runCommand2 = defineCommand({
|
|
|
7676
8333
|
});
|
|
7677
8334
|
if (!result.ok) throw new Error(result.error);
|
|
7678
8335
|
if (args.json) return console.log(toJson(result));
|
|
7679
|
-
console.log(`running W-${
|
|
8336
|
+
console.log(`running W-${workerId}/${args.name}`);
|
|
7680
8337
|
if (publicUrl) console.log(`public: ${publicUrl}`);
|
|
7681
8338
|
if (result.logPath) console.log(`logs: ${result.logPath}`);
|
|
7682
8339
|
}
|
|
@@ -7693,7 +8350,7 @@ var lsCommand = defineCommand({
|
|
|
7693
8350
|
const workers = await client.workers.listByProject(projectId);
|
|
7694
8351
|
const names = new Map(workers.map((worker2) => [worker2.id, worker2.name]));
|
|
7695
8352
|
services.sort((a, b) => a.workerId - b.workerId || a.name.localeCompare(b.name)).map(
|
|
7696
|
-
(item) => `${serviceHandle(item)} ${names.get(item.workerId) ?? "unknown"}
|
|
8353
|
+
(item) => `${serviceHandle(item)} ${names.get(item.workerId) ?? "unknown"} cwd=${item.cwd} uptime=${formatUptime(item.startedAt)}`
|
|
7697
8354
|
).forEach((line) => {
|
|
7698
8355
|
console.log(line);
|
|
7699
8356
|
});
|
|
@@ -7743,7 +8400,7 @@ var sessionAbortCommand = defineCommand({
|
|
|
7743
8400
|
var sessionClearCommand = defineCommand({
|
|
7744
8401
|
meta: {
|
|
7745
8402
|
name: "clear",
|
|
7746
|
-
description: "reset the conversation context (keeps the session +
|
|
8403
|
+
description: "reset the conversation context (keeps the session + directory)"
|
|
7747
8404
|
},
|
|
7748
8405
|
args: {
|
|
7749
8406
|
session: { type: "positional", required: true, description: "session int id or name" },
|
|
@@ -7852,7 +8509,7 @@ var sessionResumeCommand = defineCommand({
|
|
|
7852
8509
|
|
|
7853
8510
|
// src/commands/session/rm.ts
|
|
7854
8511
|
var sessionRmCommand = defineCommand({
|
|
7855
|
-
meta: { name: "rm", description: "delete a session (irreversible; removes its
|
|
8512
|
+
meta: { name: "rm", description: "delete a session (irreversible; removes its runtime files)" },
|
|
7856
8513
|
args: {
|
|
7857
8514
|
session: { type: "positional", required: true, description: "session int id or name" },
|
|
7858
8515
|
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
@@ -7863,7 +8520,9 @@ var sessionRmCommand = defineCommand({
|
|
|
7863
8520
|
const c = clientFor(args);
|
|
7864
8521
|
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
7865
8522
|
if (!args.confirm) {
|
|
7866
|
-
console.log(
|
|
8523
|
+
console.log(
|
|
8524
|
+
`[dry-run] would delete session ${handle.name} (#${handle.id}) + its runtime files`
|
|
8525
|
+
);
|
|
7867
8526
|
console.log("re-run with --confirm to proceed.");
|
|
7868
8527
|
return;
|
|
7869
8528
|
}
|
|
@@ -7879,12 +8538,12 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
7879
8538
|
// src/session/runner/transcript.ts
|
|
7880
8539
|
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
7881
8540
|
import { homedir as homedir2 } from "node:os";
|
|
7882
|
-
import { join as
|
|
8541
|
+
import { join as join7 } from "node:path";
|
|
7883
8542
|
var findTranscriptPath = (agentSessionId) => {
|
|
7884
|
-
const root =
|
|
8543
|
+
const root = join7(homedir2(), ".claude", "projects");
|
|
7885
8544
|
try {
|
|
7886
8545
|
for (const dir of readdirSync(root)) {
|
|
7887
|
-
const candidate =
|
|
8546
|
+
const candidate = join7(root, dir, `${agentSessionId}.jsonl`);
|
|
7888
8547
|
try {
|
|
7889
8548
|
if (statSync(candidate).isFile()) return candidate;
|
|
7890
8549
|
} catch {
|
|
@@ -7962,16 +8621,15 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
7962
8621
|
};
|
|
7963
8622
|
|
|
7964
8623
|
// src/session/runner/turn.ts
|
|
7965
|
-
import { mkdir as
|
|
7966
|
-
import { join as
|
|
8624
|
+
import { mkdir as mkdir5, writeFile as writeFile3 } from "node:fs/promises";
|
|
8625
|
+
import { join as join12 } from "node:path";
|
|
7967
8626
|
|
|
7968
8627
|
// src/session/runner/attachments.ts
|
|
7969
8628
|
import { createWriteStream } from "node:fs";
|
|
7970
|
-
import { mkdir
|
|
7971
|
-
import { join as
|
|
8629
|
+
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
8630
|
+
import { join as join8 } from "node:path";
|
|
7972
8631
|
import { Readable } from "node:stream";
|
|
7973
8632
|
import { pipeline } from "node:stream/promises";
|
|
7974
|
-
var ATTACH_DIR = "attachments";
|
|
7975
8633
|
var dedupe = (name, taken) => {
|
|
7976
8634
|
if (!taken.has(name)) {
|
|
7977
8635
|
taken.add(name);
|
|
@@ -7991,25 +8649,24 @@ var dedupe = (name, taken) => {
|
|
|
7991
8649
|
};
|
|
7992
8650
|
var materializeAttachments = async (opts) => {
|
|
7993
8651
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
7994
|
-
const dir =
|
|
7995
|
-
await
|
|
7996
|
-
|
|
7997
|
-
const
|
|
7998
|
-
const relPaths = [];
|
|
8652
|
+
const dir = opts.directory;
|
|
8653
|
+
await mkdir4(dir, { recursive: true });
|
|
8654
|
+
const taken = /* @__PURE__ */ new Set();
|
|
8655
|
+
const paths = [];
|
|
7999
8656
|
for (const att of opts.attachments) {
|
|
8000
8657
|
const name = dedupe(att.filename, taken);
|
|
8001
8658
|
const res = await fetchImpl(`${opts.serverBase}${att.url}`);
|
|
8002
8659
|
if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
|
|
8003
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(
|
|
8004
|
-
|
|
8660
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(join8(dir, name)));
|
|
8661
|
+
paths.push(join8(dir, name));
|
|
8005
8662
|
}
|
|
8006
|
-
return
|
|
8663
|
+
return paths;
|
|
8007
8664
|
};
|
|
8008
|
-
var augmentPrompt = (text,
|
|
8009
|
-
if (
|
|
8665
|
+
var augmentPrompt = (text, paths, labels) => {
|
|
8666
|
+
if (paths.length === 0) return text;
|
|
8010
8667
|
const header = [
|
|
8011
|
-
"[Attached files \u2014 already saved
|
|
8012
|
-
...
|
|
8668
|
+
"[Attached files \u2014 already saved locally. Reference them by label:]",
|
|
8669
|
+
...paths.map((p, i) => `- {${labels[i]}} ${p}`)
|
|
8013
8670
|
].join("\n");
|
|
8014
8671
|
return text.length > 0 ? `${header}
|
|
8015
8672
|
|
|
@@ -8017,19 +8674,20 @@ ${text}` : header;
|
|
|
8017
8674
|
};
|
|
8018
8675
|
|
|
8019
8676
|
// src/session/runner/codex.ts
|
|
8677
|
+
import { join as join9 } from "node:path";
|
|
8020
8678
|
import { Codex } from "@openai/codex-sdk";
|
|
8021
8679
|
|
|
8022
8680
|
// src/session/runner/sdk-env.ts
|
|
8023
8681
|
import { existsSync as existsSync3 } from "node:fs";
|
|
8024
|
-
import { isAbsolute } from "node:path";
|
|
8682
|
+
import { isAbsolute as isAbsolute2 } from "node:path";
|
|
8025
8683
|
var buildSdkEnv = (overlay) => overlay ? { ...process.env, ...overlay } : void 0;
|
|
8026
8684
|
var claudeExecutable = () => process.env.BATON_CLAUDE_BIN;
|
|
8027
|
-
var additionalDirs = () => {
|
|
8685
|
+
var additionalDirs = (required = []) => {
|
|
8028
8686
|
const raw = process.env.BATON_ADD_DIRS;
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
return dirs.length > 0 ? dirs : void 0;
|
|
8687
|
+
const dirs = [...required, ...(raw ?? "").split(":")].map((s) => s.trim()).filter((s) => s !== "" && isAbsolute2(s) && existsSync3(s));
|
|
8688
|
+
return dirs.length > 0 ? [...new Set(dirs)] : void 0;
|
|
8032
8689
|
};
|
|
8690
|
+
var PLAN_MODE_PREFIX = "Plan mode: inspect and propose a plan only. Do not edit files or run mutating commands.\n\n";
|
|
8033
8691
|
var truthy = (value) => {
|
|
8034
8692
|
if (value === void 0) return void 0;
|
|
8035
8693
|
const normalized = value.trim().toLowerCase();
|
|
@@ -8050,17 +8708,8 @@ var codexApprovalPolicy = () => {
|
|
|
8050
8708
|
return raw;
|
|
8051
8709
|
return "never";
|
|
8052
8710
|
};
|
|
8053
|
-
var claudeEffort = (raw) =>
|
|
8054
|
-
|
|
8055
|
-
if (raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "max")
|
|
8056
|
-
return raw;
|
|
8057
|
-
return void 0;
|
|
8058
|
-
};
|
|
8059
|
-
var codexEffort = (raw) => {
|
|
8060
|
-
if (raw === "minimal" || raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "max")
|
|
8061
|
-
return raw;
|
|
8062
|
-
return void 0;
|
|
8063
|
-
};
|
|
8711
|
+
var claudeEffort = (raw) => raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "max" ? raw : void 0;
|
|
8712
|
+
var codexEffort = (raw) => isAgentEffort(raw) ? raw : void 0;
|
|
8064
8713
|
|
|
8065
8714
|
// src/session/runner/codex.ts
|
|
8066
8715
|
var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -8178,17 +8827,16 @@ var toAgentEvent = (raw) => {
|
|
|
8178
8827
|
return { type: "raw", raw };
|
|
8179
8828
|
};
|
|
8180
8829
|
var withPlanPrefix = (input) => {
|
|
8181
|
-
|
|
8182
|
-
if (typeof input === "string") return `${prefix}${input}`;
|
|
8830
|
+
if (typeof input === "string") return `${PLAN_MODE_PREFIX}${input}`;
|
|
8183
8831
|
return input.map(
|
|
8184
|
-
(entry, i) => i === 0 && entry.type === "text" ? { ...entry, text: `${
|
|
8832
|
+
(entry, i) => i === 0 && entry.type === "text" ? { ...entry, text: `${PLAN_MODE_PREFIX}${entry.text}` } : entry
|
|
8185
8833
|
);
|
|
8186
8834
|
};
|
|
8187
8835
|
async function* startCodexEvents(config, input, worker2, opts) {
|
|
8188
8836
|
const env = compactEnv(buildSdkEnv(opts.envOverlay));
|
|
8189
8837
|
const codex = new Codex(env ? { env } : void 0);
|
|
8190
8838
|
const resuming = !config.agentSessionId.startsWith("pending:");
|
|
8191
|
-
const addDirs = additionalDirs();
|
|
8839
|
+
const addDirs = additionalDirs([join9(config.runtimeDir, "attachments")]);
|
|
8192
8840
|
const sandboxMode = codexSandboxMode(opts.planMode ?? false);
|
|
8193
8841
|
const approvalPolicy = codexApprovalPolicy();
|
|
8194
8842
|
const networkAccessEnabled = codexNetworkAccess();
|
|
@@ -8199,7 +8847,7 @@ async function* startCodexEvents(config, input, worker2, opts) {
|
|
|
8199
8847
|
if (opts.model) opts.log(`[model] ${opts.model}`);
|
|
8200
8848
|
if (reasoningEffort) opts.log(`[effort] ${reasoningEffort}`);
|
|
8201
8849
|
const threadOptions = {
|
|
8202
|
-
workingDirectory: config.
|
|
8850
|
+
workingDirectory: config.cwd,
|
|
8203
8851
|
skipGitRepoCheck: true,
|
|
8204
8852
|
sandboxMode,
|
|
8205
8853
|
approvalPolicy,
|
|
@@ -8218,8 +8866,7 @@ async function* startCodexEvents(config, input, worker2, opts) {
|
|
|
8218
8866
|
if (event.type === "thread.started" && !resuming && event.sessionId) {
|
|
8219
8867
|
config.agentSessionId = event.sessionId;
|
|
8220
8868
|
await worker2.materialize({
|
|
8221
|
-
agentSessionId: event.sessionId
|
|
8222
|
-
worktreePath: config.worktreePath
|
|
8869
|
+
agentSessionId: event.sessionId
|
|
8223
8870
|
});
|
|
8224
8871
|
opts.log(`[codex] thread ${event.sessionId}`);
|
|
8225
8872
|
}
|
|
@@ -8227,18 +8874,241 @@ async function* startCodexEvents(config, input, worker2, opts) {
|
|
|
8227
8874
|
}
|
|
8228
8875
|
}
|
|
8229
8876
|
|
|
8877
|
+
// src/session/runner/pi.ts
|
|
8878
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
8879
|
+
import { homedir as homedir3 } from "node:os";
|
|
8880
|
+
import { join as join10 } from "node:path";
|
|
8881
|
+
var newPiTurnState = (startedAt = Date.now()) => ({
|
|
8882
|
+
startedAt,
|
|
8883
|
+
usage: {},
|
|
8884
|
+
toolArgs: {}
|
|
8885
|
+
});
|
|
8886
|
+
var READ_ONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
8887
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8888
|
+
var str2 = (v) => typeof v === "string" ? v : void 0;
|
|
8889
|
+
var resultText = (result) => isRecord3(result) && Array.isArray(result.content) ? result.content.filter(isRecord3).map((block) => str2(block.text) ?? "").join("") : "";
|
|
8890
|
+
var startedItem = (id, name, args) => {
|
|
8891
|
+
const input = isRecord3(args) ? args : {};
|
|
8892
|
+
if (name === "bash") {
|
|
8893
|
+
return {
|
|
8894
|
+
type: "command_execution",
|
|
8895
|
+
id,
|
|
8896
|
+
status: "in_progress",
|
|
8897
|
+
command: str2(input.command) ?? "",
|
|
8898
|
+
output: ""
|
|
8899
|
+
};
|
|
8900
|
+
}
|
|
8901
|
+
if (name === "write" || name === "edit") {
|
|
8902
|
+
return {
|
|
8903
|
+
type: "file_change",
|
|
8904
|
+
id,
|
|
8905
|
+
status: "in_progress",
|
|
8906
|
+
changes: [{ path: str2(input.path) ?? "", kind: "update" }]
|
|
8907
|
+
};
|
|
8908
|
+
}
|
|
8909
|
+
return { type: "tool_call", id, status: "in_progress", name, input: args ?? null };
|
|
8910
|
+
};
|
|
8911
|
+
var completedItem = (id, name, args, result, isError) => {
|
|
8912
|
+
const status = isError ? "failed" : "completed";
|
|
8913
|
+
const started = startedItem(id, name, args);
|
|
8914
|
+
if (started.type === "command_execution") {
|
|
8915
|
+
const structured = isRecord3(result) && isRecord3(result.structuredContent) ? result.structuredContent : {};
|
|
8916
|
+
return {
|
|
8917
|
+
...started,
|
|
8918
|
+
status,
|
|
8919
|
+
output: str2(structured.output) ?? resultText(result),
|
|
8920
|
+
exitCode: typeof structured.exit_code === "number" ? structured.exit_code : void 0
|
|
8921
|
+
};
|
|
8922
|
+
}
|
|
8923
|
+
if (started.type === "tool_call") {
|
|
8924
|
+
return { ...started, status, output: resultText(result), isError };
|
|
8925
|
+
}
|
|
8926
|
+
return { ...started, status };
|
|
8927
|
+
};
|
|
8928
|
+
var messageItems = (message) => message.content.flatMap((block, index) => {
|
|
8929
|
+
const id = `${message.responseId ?? message.timestamp}:${index}`;
|
|
8930
|
+
if (block.type === "text" && block.text.trim()) {
|
|
8931
|
+
return [{ type: "agent_message", id, status: "completed", text: block.text }];
|
|
8932
|
+
}
|
|
8933
|
+
if (block.type === "thinking" && block.thinking.trim()) {
|
|
8934
|
+
return [{ type: "reasoning", id, status: "completed", text: block.thinking }];
|
|
8935
|
+
}
|
|
8936
|
+
return [];
|
|
8937
|
+
});
|
|
8938
|
+
var addUsage = (total, usage) => ({
|
|
8939
|
+
inputTokens: (total.inputTokens ?? 0) + usage.input,
|
|
8940
|
+
cachedInputTokens: (total.cachedInputTokens ?? 0) + usage.cacheRead,
|
|
8941
|
+
outputTokens: (total.outputTokens ?? 0) + usage.output,
|
|
8942
|
+
reasoningOutputTokens: (total.reasoningOutputTokens ?? 0) + (usage.reasoning ?? 0),
|
|
8943
|
+
totalCostUsd: (total.totalCostUsd ?? 0) + usage.cost.total,
|
|
8944
|
+
numTurns: (total.numTurns ?? 0) + 1
|
|
8945
|
+
});
|
|
8946
|
+
var piToAgentEvents = (event, state, now = Date.now()) => {
|
|
8947
|
+
if (event.type === "tool_execution_start") {
|
|
8948
|
+
return {
|
|
8949
|
+
events: [
|
|
8950
|
+
{ type: "item.started", item: startedItem(event.toolCallId, event.toolName, event.args) }
|
|
8951
|
+
],
|
|
8952
|
+
state: { ...state, toolArgs: { ...state.toolArgs, [event.toolCallId]: event.args } }
|
|
8953
|
+
};
|
|
8954
|
+
}
|
|
8955
|
+
if (event.type === "tool_execution_end") {
|
|
8956
|
+
const args = state.toolArgs[event.toolCallId];
|
|
8957
|
+
return {
|
|
8958
|
+
events: [
|
|
8959
|
+
{
|
|
8960
|
+
type: "item.completed",
|
|
8961
|
+
item: completedItem(event.toolCallId, event.toolName, args, event.result, event.isError)
|
|
8962
|
+
}
|
|
8963
|
+
],
|
|
8964
|
+
state
|
|
8965
|
+
};
|
|
8966
|
+
}
|
|
8967
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
8968
|
+
const message = event.message;
|
|
8969
|
+
return {
|
|
8970
|
+
events: messageItems(message).map((item) => ({ type: "item.completed", item })),
|
|
8971
|
+
// Reset on every assistant message: pi auto-retries, and only the last
|
|
8972
|
+
// response decides whether the turn failed.
|
|
8973
|
+
state: {
|
|
8974
|
+
...state,
|
|
8975
|
+
usage: addUsage(state.usage, message.usage),
|
|
8976
|
+
error: message.stopReason === "error" ? message.errorMessage ?? "pi turn failed" : void 0
|
|
8977
|
+
}
|
|
8978
|
+
};
|
|
8979
|
+
}
|
|
8980
|
+
if (event.type === "agent_settled") {
|
|
8981
|
+
const usage = { ...state.usage, durationMs: now - state.startedAt };
|
|
8982
|
+
return {
|
|
8983
|
+
events: [
|
|
8984
|
+
state.error ? { type: "turn.failed", error: { message: state.error }, usage } : { type: "turn.completed", usage }
|
|
8985
|
+
],
|
|
8986
|
+
state
|
|
8987
|
+
};
|
|
8988
|
+
}
|
|
8989
|
+
return { events: [], state };
|
|
8990
|
+
};
|
|
8991
|
+
var resolveModel = (runtime, raw) => {
|
|
8992
|
+
const slash = raw.indexOf("/");
|
|
8993
|
+
const model = slash > 0 ? runtime.getModel(raw.slice(0, slash), raw.slice(slash + 1)) : void 0;
|
|
8994
|
+
if (!model) throw new Error(`unknown pi model "${raw}" (expected provider/model)`);
|
|
8995
|
+
return model;
|
|
8996
|
+
};
|
|
8997
|
+
var promptParts = async (input) => {
|
|
8998
|
+
if (typeof input === "string") return { text: input, images: [] };
|
|
8999
|
+
const text = input.flatMap((entry) => entry.type === "text" ? [entry.text] : []).join("\n");
|
|
9000
|
+
const images = await Promise.all(
|
|
9001
|
+
input.flatMap(
|
|
9002
|
+
(entry) => entry.type === "local_image" ? [
|
|
9003
|
+
readFile2(entry.path).then((data) => ({
|
|
9004
|
+
type: "image",
|
|
9005
|
+
data: data.toString("base64"),
|
|
9006
|
+
mimeType: contentTypeForPath(entry.path)
|
|
9007
|
+
}))
|
|
9008
|
+
] : []
|
|
9009
|
+
)
|
|
9010
|
+
);
|
|
9011
|
+
return { text, images };
|
|
9012
|
+
};
|
|
9013
|
+
async function* startPiEvents(config, input, opts) {
|
|
9014
|
+
const pi = await import("@earendil-works/pi-coding-agent");
|
|
9015
|
+
const modelRuntime = await pi.ModelRuntime.create();
|
|
9016
|
+
const model = opts.model ? resolveModel(modelRuntime, opts.model) : void 0;
|
|
9017
|
+
const effort = isAgentEffort(opts.effort) ? opts.effort : void 0;
|
|
9018
|
+
const thinkingLevel = effort === "ultra" ? void 0 : effort;
|
|
9019
|
+
const agentDir = pi.getAgentDir();
|
|
9020
|
+
const resourceLoader = new pi.DefaultResourceLoader({
|
|
9021
|
+
cwd: config.cwd,
|
|
9022
|
+
agentDir,
|
|
9023
|
+
// pi discovers .pi/ and .agents/ skills natively; Baton workers deploy theirs
|
|
9024
|
+
// under .claude/skills (project and user level).
|
|
9025
|
+
additionalSkillPaths: [join10(config.cwd, ".claude/skills"), join10(homedir3(), ".claude/skills")]
|
|
9026
|
+
});
|
|
9027
|
+
await resourceLoader.reload();
|
|
9028
|
+
const existing = pi.SessionManager.findById(config.cwd, config.agentSessionId);
|
|
9029
|
+
const sessionManager = existing ? pi.SessionManager.open(existing) : pi.SessionManager.create(config.cwd, void 0, { id: config.agentSessionId });
|
|
9030
|
+
const { session: session2 } = await pi.createAgentSession({
|
|
9031
|
+
cwd: config.cwd,
|
|
9032
|
+
agentDir,
|
|
9033
|
+
modelRuntime,
|
|
9034
|
+
resourceLoader,
|
|
9035
|
+
sessionManager,
|
|
9036
|
+
...model ? { model } : {},
|
|
9037
|
+
...thinkingLevel ? { thinkingLevel } : {},
|
|
9038
|
+
...opts.planMode ? { tools: READ_ONLY_TOOLS } : {}
|
|
9039
|
+
});
|
|
9040
|
+
const modelId = session2.model ? `${session2.model.provider}/${session2.model.id}` : void 0;
|
|
9041
|
+
opts.log(`[pi] ${existing ? "resume" : "new"} session ${session2.sessionId}`);
|
|
9042
|
+
if (modelId) opts.log(`[model] ${modelId}`);
|
|
9043
|
+
if (thinkingLevel) opts.log(`[effort] ${thinkingLevel}`);
|
|
9044
|
+
if (opts.planMode) opts.log("[plan] read-only tools");
|
|
9045
|
+
const queue = [];
|
|
9046
|
+
let wake = () => {
|
|
9047
|
+
};
|
|
9048
|
+
let failure;
|
|
9049
|
+
let finished = false;
|
|
9050
|
+
let settled = false;
|
|
9051
|
+
const unsubscribe = session2.subscribe((event) => {
|
|
9052
|
+
queue.push(event);
|
|
9053
|
+
wake();
|
|
9054
|
+
});
|
|
9055
|
+
const abort = () => {
|
|
9056
|
+
void session2.abort().catch((error) => opts.log(`[pi] abort: ${String(error)}`));
|
|
9057
|
+
};
|
|
9058
|
+
opts.signal?.addEventListener("abort", abort, { once: true });
|
|
9059
|
+
try {
|
|
9060
|
+
yield { type: "thread.started", sessionId: session2.sessionId, model: modelId };
|
|
9061
|
+
yield { type: "turn.started" };
|
|
9062
|
+
const { text, images } = await promptParts(input);
|
|
9063
|
+
if (opts.signal?.aborted) return;
|
|
9064
|
+
const running = session2.prompt(opts.planMode ? `${PLAN_MODE_PREFIX}${text}` : text, images?.length ? { images } : {}).then(
|
|
9065
|
+
() => {
|
|
9066
|
+
finished = true;
|
|
9067
|
+
wake();
|
|
9068
|
+
},
|
|
9069
|
+
(error) => {
|
|
9070
|
+
failure = error;
|
|
9071
|
+
wake();
|
|
9072
|
+
}
|
|
9073
|
+
);
|
|
9074
|
+
let state = newPiTurnState();
|
|
9075
|
+
while (!settled) {
|
|
9076
|
+
const event = queue.shift();
|
|
9077
|
+
if (!event) {
|
|
9078
|
+
if (failure !== void 0) throw failure;
|
|
9079
|
+
if (finished) break;
|
|
9080
|
+
await new Promise((resolve4) => {
|
|
9081
|
+
wake = resolve4;
|
|
9082
|
+
});
|
|
9083
|
+
continue;
|
|
9084
|
+
}
|
|
9085
|
+
const folded = piToAgentEvents(event, state);
|
|
9086
|
+
state = folded.state;
|
|
9087
|
+
settled = event.type === "agent_settled";
|
|
9088
|
+
yield* folded.events;
|
|
9089
|
+
}
|
|
9090
|
+
await running;
|
|
9091
|
+
if (failure !== void 0) throw failure;
|
|
9092
|
+
} finally {
|
|
9093
|
+
opts.signal?.removeEventListener("abort", abort);
|
|
9094
|
+
unsubscribe();
|
|
9095
|
+
session2.dispose();
|
|
9096
|
+
}
|
|
9097
|
+
}
|
|
9098
|
+
|
|
8230
9099
|
// src/session/runner/query.ts
|
|
9100
|
+
import { join as join11 } from "node:path";
|
|
8231
9101
|
var startQuery = (config, text, resuming, queryFn, abortController, log, { envOverlay, planMode = false, model, effort } = {}) => {
|
|
8232
9102
|
const env = buildSdkEnv(envOverlay);
|
|
8233
9103
|
const exe = claudeExecutable();
|
|
8234
|
-
const addDirs = additionalDirs();
|
|
9104
|
+
const addDirs = additionalDirs([join11(config.runtimeDir, "attachments")]);
|
|
8235
9105
|
const level = claudeEffort(effort);
|
|
8236
9106
|
if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
|
|
8237
9107
|
if (planMode) log("[plan] read-only planning turn");
|
|
8238
9108
|
if (model) log(`[model] ${model}`);
|
|
8239
9109
|
if (level) log(`[effort] ${level}`);
|
|
8240
9110
|
const options = {
|
|
8241
|
-
cwd: config.
|
|
9111
|
+
cwd: config.cwd,
|
|
8242
9112
|
permissionMode: planMode ? "plan" : "bypassPermissions",
|
|
8243
9113
|
// Headless relay: no TTY/TUI can answer an interactive ask, so AskUserQuestion
|
|
8244
9114
|
// would fail the whole turn. Block it — the model asks in plain text instead,
|
|
@@ -8279,34 +9149,34 @@ var streamAgentEvents = async (events, worker2) => {
|
|
|
8279
9149
|
return result;
|
|
8280
9150
|
};
|
|
8281
9151
|
var streamClaudeSdkEvents = (messages, worker2) => streamAgentEvents(claudeToAgentEvents(messages), worker2);
|
|
8282
|
-
var
|
|
8283
|
-
var
|
|
9152
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
9153
|
+
var str3 = (v) => typeof v === "string" ? v : void 0;
|
|
8284
9154
|
var messageContent = (payload) => {
|
|
8285
9155
|
const msg = payload.message;
|
|
8286
|
-
if (!
|
|
8287
|
-
return msg.content.filter(
|
|
9156
|
+
if (!isRecord4(msg) || !Array.isArray(msg.content)) return [];
|
|
9157
|
+
return msg.content.filter(isRecord4);
|
|
8288
9158
|
};
|
|
8289
9159
|
var textFromToolResult = (raw) => {
|
|
8290
9160
|
const c = raw.content;
|
|
8291
9161
|
if (typeof c === "string") return c;
|
|
8292
9162
|
if (Array.isArray(c)) {
|
|
8293
|
-
return c.map((b) =>
|
|
9163
|
+
return c.map((b) => isRecord4(b) && typeof b.text === "string" ? b.text : JSON.stringify(b)).join("\n");
|
|
8294
9164
|
}
|
|
8295
9165
|
return JSON.stringify(c ?? "");
|
|
8296
9166
|
};
|
|
8297
9167
|
var commandOf = (input) => {
|
|
8298
|
-
if (!
|
|
8299
|
-
return
|
|
9168
|
+
if (!isRecord4(input)) return "";
|
|
9169
|
+
return str3(input.command) ?? str3(input.cmd) ?? "";
|
|
8300
9170
|
};
|
|
8301
9171
|
var pathOf = (input) => {
|
|
8302
|
-
if (!
|
|
8303
|
-
return
|
|
9172
|
+
if (!isRecord4(input)) return void 0;
|
|
9173
|
+
return str3(input.file_path) ?? str3(input.path);
|
|
8304
9174
|
};
|
|
8305
9175
|
var fileKindOf = (name) => name === "Delete" ? "delete" : "update";
|
|
8306
9176
|
var todoItems2 = (input) => {
|
|
8307
|
-
if (!
|
|
8308
|
-
return input.todos.filter(
|
|
8309
|
-
text:
|
|
9177
|
+
if (!isRecord4(input) || !Array.isArray(input.todos)) return [];
|
|
9178
|
+
return input.todos.filter(isRecord4).map((todo) => ({
|
|
9179
|
+
text: str3(todo.content) ?? str3(todo.text) ?? "",
|
|
8310
9180
|
completed: todo.status === "completed" || todo.completed === true
|
|
8311
9181
|
}));
|
|
8312
9182
|
};
|
|
@@ -8315,8 +9185,8 @@ var mcpParts = (name) => {
|
|
|
8315
9185
|
return match ? { server: match[1] ?? "", tool: match[2] ?? "" } : null;
|
|
8316
9186
|
};
|
|
8317
9187
|
var startedItemFromToolUse = (block) => {
|
|
8318
|
-
const id =
|
|
8319
|
-
const name =
|
|
9188
|
+
const id = str3(block.id) ?? `tool-${Math.random().toString(36).slice(2)}`;
|
|
9189
|
+
const name = str3(block.name) ?? "tool";
|
|
8320
9190
|
const input = block.input ?? null;
|
|
8321
9191
|
if (name === "Bash") {
|
|
8322
9192
|
return {
|
|
@@ -8352,7 +9222,7 @@ var startedItemFromToolUse = (block) => {
|
|
|
8352
9222
|
type: "web_search",
|
|
8353
9223
|
id,
|
|
8354
9224
|
status: "in_progress",
|
|
8355
|
-
query:
|
|
9225
|
+
query: isRecord4(input) ? str3(input.query) ?? "" : ""
|
|
8356
9226
|
};
|
|
8357
9227
|
}
|
|
8358
9228
|
if (name === "TodoWrite") {
|
|
@@ -8371,9 +9241,9 @@ var completeItem = (item, output, isError) => {
|
|
|
8371
9241
|
};
|
|
8372
9242
|
var resultEvent = (message) => {
|
|
8373
9243
|
const rec = message;
|
|
8374
|
-
const subtype =
|
|
9244
|
+
const subtype = str3(rec.subtype) ?? "unknown";
|
|
8375
9245
|
const isError = rec.is_error === true || subtype !== "success";
|
|
8376
|
-
const
|
|
9246
|
+
const resultText2 = str3(rec.result) ?? "";
|
|
8377
9247
|
const usage = {
|
|
8378
9248
|
numTurns: typeof rec.num_turns === "number" ? rec.num_turns : void 0,
|
|
8379
9249
|
totalCostUsd: typeof rec.total_cost_usd === "number" ? rec.total_cost_usd : void 0,
|
|
@@ -8381,7 +9251,7 @@ var resultEvent = (message) => {
|
|
|
8381
9251
|
};
|
|
8382
9252
|
return isError ? {
|
|
8383
9253
|
type: "turn.failed",
|
|
8384
|
-
error: { message:
|
|
9254
|
+
error: { message: resultText2 || `claude result: ${subtype}`, subtype },
|
|
8385
9255
|
usage,
|
|
8386
9256
|
raw: message
|
|
8387
9257
|
} : { type: "turn.completed", subtype, usage, raw: message };
|
|
@@ -8398,8 +9268,8 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8398
9268
|
systemEmitted = true;
|
|
8399
9269
|
yield {
|
|
8400
9270
|
type: "thread.started",
|
|
8401
|
-
sessionId:
|
|
8402
|
-
model:
|
|
9271
|
+
sessionId: str3(p.session_id) ?? "",
|
|
9272
|
+
model: str3(p.model) ?? (isRecord4(p.model_info) ? str3(p.model_info.id) : void 0),
|
|
8403
9273
|
raw: message
|
|
8404
9274
|
};
|
|
8405
9275
|
yield { type: "turn.started", raw: message };
|
|
@@ -8438,7 +9308,7 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8438
9308
|
pendingTools.set(item.id, item);
|
|
8439
9309
|
yield { type: "item.started", item, raw: block };
|
|
8440
9310
|
} else if (block.type === "tool_result") {
|
|
8441
|
-
const id =
|
|
9311
|
+
const id = str3(block.tool_use_id) ?? "";
|
|
8442
9312
|
const item = pendingTools.get(id);
|
|
8443
9313
|
if (item) {
|
|
8444
9314
|
const completed = completeItem(item, textFromToolResult(block), block.is_error === true);
|
|
@@ -8456,7 +9326,7 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8456
9326
|
if (message.type === "user") {
|
|
8457
9327
|
for (const block of messageContent(p)) {
|
|
8458
9328
|
if (block.type !== "tool_result") continue;
|
|
8459
|
-
const id =
|
|
9329
|
+
const id = str3(block.tool_use_id) ?? "";
|
|
8460
9330
|
const item = pendingTools.get(id);
|
|
8461
9331
|
if (!item) {
|
|
8462
9332
|
yield { type: "raw", raw: block };
|
|
@@ -8482,15 +9352,19 @@ var UnsettledExecution = class extends Error {
|
|
|
8482
9352
|
var materializeImages = async (config, execution) => {
|
|
8483
9353
|
const images = execution.message.payload.images ?? [];
|
|
8484
9354
|
if (!images.length) return [];
|
|
8485
|
-
const
|
|
8486
|
-
|
|
8487
|
-
|
|
9355
|
+
const directory2 = join12(
|
|
9356
|
+
config.runtimeDir,
|
|
9357
|
+
"attachments",
|
|
9358
|
+
`message-${execution.message.id}`,
|
|
9359
|
+
"pasted"
|
|
9360
|
+
);
|
|
9361
|
+
await mkdir5(directory2, { recursive: true });
|
|
8488
9362
|
return Promise.all(
|
|
8489
9363
|
images.map(async (image, index) => {
|
|
8490
9364
|
const match = /^data:image\/(png|jpeg|webp|gif);base64,([A-Za-z0-9+/=\r\n]+)$/.exec(image);
|
|
8491
9365
|
if (!match) throw new Error("unsupported pasted image");
|
|
8492
|
-
const path = `
|
|
8493
|
-
await
|
|
9366
|
+
const path = join12(directory2, `pasted-${index + 1}.${match[1]}`);
|
|
9367
|
+
await writeFile3(path, Buffer.from(match[2] ?? "", "base64"));
|
|
8494
9368
|
return path;
|
|
8495
9369
|
})
|
|
8496
9370
|
);
|
|
@@ -8498,7 +9372,12 @@ var materializeImages = async (config, execution) => {
|
|
|
8498
9372
|
var buildPrompt = async (config, execution, fetchImpl) => {
|
|
8499
9373
|
const { text: rawText, attachments = [] } = execution.message.payload;
|
|
8500
9374
|
const paths = attachments.length ? await materializeAttachments({
|
|
8501
|
-
|
|
9375
|
+
directory: join12(
|
|
9376
|
+
config.runtimeDir,
|
|
9377
|
+
"attachments",
|
|
9378
|
+
`message-${execution.message.id}`,
|
|
9379
|
+
"files"
|
|
9380
|
+
),
|
|
8502
9381
|
serverBase: config.server,
|
|
8503
9382
|
attachments,
|
|
8504
9383
|
fetchImpl
|
|
@@ -8515,7 +9394,7 @@ var buildPrompt = async (config, execution, fetchImpl) => {
|
|
|
8515
9394
|
];
|
|
8516
9395
|
const images = imagePaths.map((path) => ({
|
|
8517
9396
|
type: "local_image",
|
|
8518
|
-
path
|
|
9397
|
+
path
|
|
8519
9398
|
}));
|
|
8520
9399
|
return { text, input: images.length ? [{ type: "text", text }, ...images] : text };
|
|
8521
9400
|
};
|
|
@@ -8576,25 +9455,27 @@ var runTurn = async (config, worker2, execution, resuming, queryFn, log, envOver
|
|
|
8576
9455
|
const built = await buildPrompt(config, execution, fetchImpl);
|
|
8577
9456
|
if (abort.signal.aborted) throw new Error("interrupted before SDK start");
|
|
8578
9457
|
const { planMode, model, effort } = execution.message.payload;
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
9458
|
+
const turn = { planMode, model, effort };
|
|
9459
|
+
const run4 = {
|
|
9460
|
+
"claude-code": () => streamClaudeSdkEvents(
|
|
9461
|
+
startQuery(config, built.text, resuming, queryFn, abort, log, { envOverlay, ...turn }),
|
|
9462
|
+
output
|
|
9463
|
+
),
|
|
9464
|
+
codex: () => streamAgentEvents(
|
|
9465
|
+
startCodexEvents(config, built.input, worker2, {
|
|
9466
|
+
envOverlay,
|
|
9467
|
+
...turn,
|
|
9468
|
+
signal: abort.signal,
|
|
9469
|
+
log
|
|
9470
|
+
}),
|
|
9471
|
+
output
|
|
9472
|
+
),
|
|
9473
|
+
pi: () => streamAgentEvents(
|
|
9474
|
+
startPiEvents(config, built.input, { ...turn, signal: abort.signal, log }),
|
|
9475
|
+
output
|
|
9476
|
+
)
|
|
9477
|
+
};
|
|
9478
|
+
return run4[config.agentKind]();
|
|
8598
9479
|
};
|
|
8599
9480
|
const consuming = consume().then(
|
|
8600
9481
|
(value) => ({ kind: "result", value }),
|
|
@@ -8618,7 +9499,7 @@ var runTurn = async (config, worker2, execution, resuming, queryFn, log, envOver
|
|
|
8618
9499
|
};
|
|
8619
9500
|
|
|
8620
9501
|
// src/session/runner.ts
|
|
8621
|
-
var isAgentConversationResumable = (config, transcriptPath = findTranscriptPath) => config.agentKind === "
|
|
9502
|
+
var isAgentConversationResumable = (config, transcriptPath = findTranscriptPath) => config.agentKind === "claude-code" ? transcriptPath(config.agentSessionId) !== null : !config.agentSessionId.startsWith("pending:");
|
|
8622
9503
|
var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
|
|
8623
9504
|
var runDaemon = async (config, deps, signal) => {
|
|
8624
9505
|
const log = deps.log ?? console.log;
|
|
@@ -8632,12 +9513,12 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
8632
9513
|
let lastActivity = Date.now();
|
|
8633
9514
|
let stop = () => {
|
|
8634
9515
|
};
|
|
8635
|
-
const stopped = new Promise((
|
|
8636
|
-
stop =
|
|
9516
|
+
const stopped = new Promise((resolve4) => {
|
|
9517
|
+
stop = resolve4;
|
|
8637
9518
|
});
|
|
8638
9519
|
const drain = async () => {
|
|
8639
9520
|
if (draining || closed || stranded) return;
|
|
8640
|
-
const
|
|
9521
|
+
const run4 = async () => {
|
|
8641
9522
|
while (!closed && !stranded && !signal.aborted) {
|
|
8642
9523
|
let claimed;
|
|
8643
9524
|
try {
|
|
@@ -8680,7 +9561,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
8680
9561
|
}
|
|
8681
9562
|
}
|
|
8682
9563
|
};
|
|
8683
|
-
draining =
|
|
9564
|
+
draining = run4();
|
|
8684
9565
|
try {
|
|
8685
9566
|
await draining;
|
|
8686
9567
|
} finally {
|
|
@@ -8745,6 +9626,19 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
8745
9626
|
}
|
|
8746
9627
|
};
|
|
8747
9628
|
|
|
9629
|
+
// src/worker/runtime-paths.ts
|
|
9630
|
+
import { createHash } from "node:crypto";
|
|
9631
|
+
import { homedir as homedir4 } from "node:os";
|
|
9632
|
+
import { join as join13 } from "node:path";
|
|
9633
|
+
var workerRuntimeDir = (owner, env = process.env) => join13(
|
|
9634
|
+
env.XDG_DATA_HOME ?? join13(env.HOME ?? homedir4(), ".local/share"),
|
|
9635
|
+
"baton",
|
|
9636
|
+
"runtime",
|
|
9637
|
+
createHash("sha256").update(owner.server).digest("hex"),
|
|
9638
|
+
`worker-${owner.workerId}`
|
|
9639
|
+
);
|
|
9640
|
+
var sessionRuntimeDir = (owner, sessionId, env = process.env) => join13(workerRuntimeDir(owner, env), "sessions", String(sessionId));
|
|
9641
|
+
|
|
8748
9642
|
// src/commands/session/run.ts
|
|
8749
9643
|
var bearerFetch = (token) => (input, init2) => fetch(input, {
|
|
8750
9644
|
...init2,
|
|
@@ -8774,13 +9668,13 @@ var sessionRunCommand = defineCommand({
|
|
|
8774
9668
|
},
|
|
8775
9669
|
run: async ({ args }) => {
|
|
8776
9670
|
if (!process.connected) throw new Error("session runner requires supervisor IPC");
|
|
8777
|
-
await new Promise((
|
|
9671
|
+
await new Promise((resolve4, reject) => {
|
|
8778
9672
|
const onMessage = (message) => {
|
|
8779
9673
|
if (typeof message !== "object" || message === null || !("type" in message) || message.type !== "execute")
|
|
8780
9674
|
return;
|
|
8781
9675
|
process.off("message", onMessage);
|
|
8782
9676
|
process.off("disconnect", onDisconnect);
|
|
8783
|
-
|
|
9677
|
+
resolve4();
|
|
8784
9678
|
};
|
|
8785
9679
|
const onDisconnect = () => {
|
|
8786
9680
|
process.off("message", onMessage);
|
|
@@ -8797,9 +9691,10 @@ var sessionRunCommand = defineCommand({
|
|
|
8797
9691
|
const sessionId = Number(args.session);
|
|
8798
9692
|
if (!Number.isInteger(sessionId) || sessionId <= 0)
|
|
8799
9693
|
throw new Error(`invalid session id: ${args.session}`);
|
|
9694
|
+
process.env.BATON_SESSION_ID = String(sessionId);
|
|
8800
9695
|
const client = createClient(server, { bearer: workerToken });
|
|
8801
9696
|
const s = await client.sessions.get(sessionId);
|
|
8802
|
-
if (!s.agentSessionId || !s.
|
|
9697
|
+
if (!s.agentSessionId || !s.cwd)
|
|
8803
9698
|
throw new Error(`session #${sessionId} is not materialized yet`);
|
|
8804
9699
|
const config = {
|
|
8805
9700
|
server,
|
|
@@ -8807,7 +9702,8 @@ var sessionRunCommand = defineCommand({
|
|
|
8807
9702
|
name: s.name,
|
|
8808
9703
|
agentKind: s.agentKind,
|
|
8809
9704
|
agentSessionId: s.agentSessionId,
|
|
8810
|
-
|
|
9705
|
+
cwd: s.cwd,
|
|
9706
|
+
runtimeDir: sessionRuntimeDir({ server, workerId: s.workerId }, sessionId)
|
|
8811
9707
|
};
|
|
8812
9708
|
const worker2 = createWorkerClient(server, workerToken, sessionId);
|
|
8813
9709
|
const ac = new AbortController();
|
|
@@ -9100,185 +9996,480 @@ var task = defineCommand({
|
|
|
9100
9996
|
// src/commands/worker/index.ts
|
|
9101
9997
|
import { hostname as osHostname } from "node:os";
|
|
9102
9998
|
|
|
9103
|
-
// src/
|
|
9104
|
-
import {
|
|
9999
|
+
// src/worker/bundled-skills.ts
|
|
10000
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
10001
|
+
import { cpSync, existsSync as existsSync5 } from "node:fs";
|
|
10002
|
+
import { dirname as dirname3, join as join14 } from "node:path";
|
|
10003
|
+
import { fileURLToPath } from "node:url";
|
|
10004
|
+
|
|
10005
|
+
// src/worker/git-exclude.ts
|
|
10006
|
+
import { spawnSync } from "node:child_process";
|
|
9105
10007
|
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
9106
|
-
import { dirname, isAbsolute as
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
});
|
|
9112
|
-
var commandError = (result) => {
|
|
9113
|
-
const raw = result.stderr || result.stdout || result.error?.message || `exit ${result.status}`;
|
|
9114
|
-
return raw.trim().replace(/(https?:\/\/)[^/@\s]+@/g, "$1***@");
|
|
9115
|
-
};
|
|
9116
|
-
var execFileAsync = promisify(execFile);
|
|
9117
|
-
var DEFAULT_SYNC_TIMEOUT_MS = 6e4;
|
|
9118
|
-
var asyncCommandError = (error, timeoutMs) => {
|
|
9119
|
-
const failure = error;
|
|
9120
|
-
if (failure.killed || failure.code === "ETIMEDOUT") return `timed out after ${timeoutMs}ms`;
|
|
9121
|
-
const raw = failure.stderr || failure.stdout || failure.message || String(error);
|
|
9122
|
-
return raw.trim().replace(/(https?:\/\/)[^/@\s]+@/g, "$1***@");
|
|
9123
|
-
};
|
|
9124
|
-
var validateBaseBranch = (branch) => {
|
|
9125
|
-
const normalized = branch.trim();
|
|
9126
|
-
const valid = spawnSync("git", ["check-ref-format", "--branch", normalized], {
|
|
9127
|
-
stdio: "pipe",
|
|
9128
|
-
encoding: "utf8"
|
|
9129
|
-
});
|
|
9130
|
-
if (valid.status !== 0)
|
|
9131
|
-
throw new Error(`invalid base branch "${normalized}": ${commandError(valid)}`);
|
|
9132
|
-
return normalized;
|
|
9133
|
-
};
|
|
9134
|
-
var createWorktree = (input) => {
|
|
9135
|
-
if (existsSync4(input.worktreePath))
|
|
9136
|
-
throw new Error(`worktree path already exists: ${input.worktreePath}`);
|
|
9137
|
-
const head = spawnSync("git", ["-C", input.repo, "rev-parse", "--verify", "-q", "HEAD"], {
|
|
9138
|
-
stdio: "pipe",
|
|
9139
|
-
encoding: "utf8"
|
|
9140
|
-
});
|
|
9141
|
-
if (head.status !== 0)
|
|
9142
|
-
throw new Error(
|
|
9143
|
-
`repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
|
|
9144
|
-
);
|
|
9145
|
-
mkdirSync3(dirname(input.worktreePath), { recursive: true });
|
|
9146
|
-
const branch = `baton/${input.sessionCode}`;
|
|
9147
|
-
const r = spawnSync(
|
|
9148
|
-
"git",
|
|
9149
|
-
["-C", input.repo, "worktree", "add", "-b", branch, input.worktreePath, input.base],
|
|
9150
|
-
{ stdio: "pipe", encoding: "utf8" }
|
|
9151
|
-
);
|
|
9152
|
-
if (r.status !== 0) {
|
|
9153
|
-
throw new Error(`git worktree add failed: ${r.stderr || r.stdout || `exit ${r.status}`}`);
|
|
9154
|
-
}
|
|
9155
|
-
};
|
|
9156
|
-
var repoHeadBranch = (repo) => {
|
|
9157
|
-
const r = git(repo, ["symbolic-ref", "--short", "HEAD"]);
|
|
9158
|
-
const branch = r.status === 0 ? r.stdout.trim() : "";
|
|
9159
|
-
return branch || "main";
|
|
9160
|
-
};
|
|
9161
|
-
var syncBaseBranch = async (repo, branch, options = {}) => {
|
|
9162
|
-
const checkedBranch = validateBaseBranch(branch);
|
|
9163
|
-
const env = options.env ?? process.env;
|
|
9164
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_SYNC_TIMEOUT_MS;
|
|
9165
|
-
const origin = git(repo, ["remote", "get-url", "origin"]);
|
|
9166
|
-
if (origin.status !== 0) {
|
|
9167
|
-
const localRef = `refs/heads/${checkedBranch}`;
|
|
9168
|
-
const verified2 = git(repo, ["rev-parse", "--verify", "-q", localRef]);
|
|
9169
|
-
if (verified2.status !== 0) throw new Error(`local base branch does not exist: ${checkedBranch}`);
|
|
9170
|
-
return localRef;
|
|
9171
|
-
}
|
|
9172
|
-
const helper = env.BATON_GIT_SYNC_BIN?.trim();
|
|
9173
|
-
const command = helper ?? "git";
|
|
9174
|
-
const args = helper ? [repo, checkedBranch] : [
|
|
9175
|
-
"-C",
|
|
9176
|
-
repo,
|
|
9177
|
-
"fetch",
|
|
9178
|
-
"--no-tags",
|
|
9179
|
-
"origin",
|
|
9180
|
-
`+refs/heads/${checkedBranch}:refs/remotes/origin/${checkedBranch}`
|
|
9181
|
-
];
|
|
9182
|
-
let synced;
|
|
9183
|
-
try {
|
|
9184
|
-
synced = await execFileAsync(command, args, {
|
|
9185
|
-
encoding: "utf8",
|
|
9186
|
-
env: { ...env, GIT_TERMINAL_PROMPT: "0" },
|
|
9187
|
-
timeout: timeoutMs
|
|
9188
|
-
});
|
|
9189
|
-
} catch (error) {
|
|
9190
|
-
const via = helper ? ` via ${helper}` : "";
|
|
9191
|
-
throw new Error(
|
|
9192
|
-
`git sync failed for origin/${checkedBranch}${via}: ${asyncCommandError(error, timeoutMs)}`
|
|
9193
|
-
);
|
|
9194
|
-
}
|
|
9195
|
-
const remoteRef = `refs/remotes/origin/${checkedBranch}`;
|
|
9196
|
-
const verified = git(repo, ["rev-parse", "--verify", "-q", remoteRef]);
|
|
9197
|
-
if (verified.status !== 0) throw new Error(`git sync succeeded but ${remoteRef} does not exist`);
|
|
9198
|
-
if (helper) {
|
|
9199
|
-
const reportedCommit = synced.stdout.trim();
|
|
9200
|
-
const actualCommit = verified.stdout.trim();
|
|
9201
|
-
if (!/^[0-9a-f]{40,64}$/i.test(reportedCommit))
|
|
9202
|
-
throw new Error("git sync helper did not report the synced commit on stdout");
|
|
9203
|
-
if (reportedCommit !== actualCommit)
|
|
9204
|
-
throw new Error(`git sync helper reported a commit that does not match ${remoteRef}`);
|
|
9205
|
-
}
|
|
9206
|
-
return remoteRef;
|
|
9207
|
-
};
|
|
9208
|
-
var ensureExcluded = (repo, pattern) => {
|
|
9209
|
-
try {
|
|
9210
|
-
const r = spawnSync("git", ["-C", repo, "rev-parse", "--git-path", "info/exclude"], {
|
|
9211
|
-
stdio: "pipe",
|
|
9212
|
-
encoding: "utf8"
|
|
9213
|
-
});
|
|
9214
|
-
if (r.status !== 0) return;
|
|
9215
|
-
const rel = r.stdout.trim();
|
|
9216
|
-
const path = isAbsolute2(rel) ? rel : resolve2(repo, rel);
|
|
9217
|
-
const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
9218
|
-
if (current.split("\n").includes(pattern)) return;
|
|
9219
|
-
mkdirSync3(dirname(path), { recursive: true });
|
|
9220
|
-
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
9221
|
-
appendFileSync(path, `${sep}${pattern}
|
|
9222
|
-
`, "utf8");
|
|
9223
|
-
} catch {
|
|
9224
|
-
}
|
|
9225
|
-
};
|
|
9226
|
-
var removeWorktree = (repo, worktreePath) => {
|
|
9227
|
-
if (!existsSync4(worktreePath)) return;
|
|
9228
|
-
try {
|
|
9229
|
-
execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
|
|
9230
|
-
stdio: "pipe"
|
|
9231
|
-
});
|
|
9232
|
-
} catch {
|
|
9233
|
-
}
|
|
9234
|
-
};
|
|
9235
|
-
var restoreWorktree = async (repo, worktreePath, sessionCode, baseBranch = repoHeadBranch(repo)) => {
|
|
9236
|
-
spawnSync("git", ["-C", repo, "worktree", "prune"], { stdio: "pipe" });
|
|
9237
|
-
mkdirSync3(dirname(worktreePath), { recursive: true });
|
|
9238
|
-
const branch = `baton/${sessionCode}`;
|
|
9239
|
-
const reuse = spawnSync("git", ["-C", repo, "worktree", "add", worktreePath, branch], {
|
|
9240
|
-
stdio: "pipe",
|
|
9241
|
-
encoding: "utf8"
|
|
9242
|
-
});
|
|
9243
|
-
if (reuse.status === 0) return;
|
|
9244
|
-
createWorktree({
|
|
9245
|
-
repo,
|
|
9246
|
-
worktreePath,
|
|
9247
|
-
sessionCode,
|
|
9248
|
-
base: await syncBaseBranch(repo, baseBranch)
|
|
10008
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, resolve as resolve3 } from "node:path";
|
|
10009
|
+
var ensureExcluded = (cwd, pattern) => {
|
|
10010
|
+
const result = spawnSync("git", ["-C", cwd, "rev-parse", "--git-path", "info/exclude"], {
|
|
10011
|
+
encoding: "utf8",
|
|
10012
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
9249
10013
|
});
|
|
10014
|
+
if (result.error) throw result.error;
|
|
10015
|
+
if (result.status !== 0) {
|
|
10016
|
+
if (result.stderr.includes("not a git repository")) return;
|
|
10017
|
+
throw new Error(`cannot locate Git exclude file: ${result.stderr.trim()}`);
|
|
10018
|
+
}
|
|
10019
|
+
const location = result.stdout.trim();
|
|
10020
|
+
const path = isAbsolute3(location) ? location : resolve3(cwd, location);
|
|
10021
|
+
const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
10022
|
+
if (current.split("\n").includes(pattern)) return;
|
|
10023
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
10024
|
+
appendFileSync(path, `${current && !current.endsWith("\n") ? "\n" : ""}${pattern}
|
|
10025
|
+
`);
|
|
9250
10026
|
};
|
|
9251
10027
|
|
|
9252
10028
|
// src/worker/bundled-skills.ts
|
|
9253
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9254
|
-
import { cpSync, existsSync as existsSync5 } from "node:fs";
|
|
9255
|
-
import { dirname as dirname2, join as join8 } from "node:path";
|
|
9256
|
-
import { fileURLToPath } from "node:url";
|
|
9257
10029
|
var SKILL_NAME = "baton-publish-artifact";
|
|
9258
10030
|
var TARGETS = [`.claude/skills/${SKILL_NAME}`, `.agents/skills/${SKILL_NAME}`];
|
|
9259
10031
|
var bundledSkillPath = () => {
|
|
9260
|
-
const here =
|
|
10032
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
9261
10033
|
const candidates = [
|
|
9262
|
-
|
|
9263
|
-
|
|
10034
|
+
join14(here, "..", "skills", SKILL_NAME),
|
|
10035
|
+
join14(here, "..", "..", "skills", SKILL_NAME)
|
|
9264
10036
|
];
|
|
9265
10037
|
const found = candidates.find(existsSync5);
|
|
9266
10038
|
if (!found) throw new Error(`bundled skill missing: ${SKILL_NAME}`);
|
|
9267
10039
|
return found;
|
|
9268
10040
|
};
|
|
9269
|
-
var isTracked = (
|
|
9270
|
-
const result = spawnSync2("git", ["-C",
|
|
10041
|
+
var isTracked = (cwd, relativePath) => {
|
|
10042
|
+
const result = spawnSync2("git", ["-C", cwd, "ls-files", "--", relativePath], {
|
|
9271
10043
|
encoding: "utf8",
|
|
9272
10044
|
stdio: "pipe"
|
|
9273
10045
|
});
|
|
9274
10046
|
return result.status === 0 && result.stdout.trim().length > 0;
|
|
9275
10047
|
};
|
|
9276
|
-
var syncBundledArtifactSkill = (
|
|
10048
|
+
var syncBundledArtifactSkill = (cwd) => {
|
|
9277
10049
|
const source = bundledSkillPath();
|
|
9278
|
-
TARGETS.filter((target) => !isTracked(
|
|
9279
|
-
ensureExcluded(
|
|
9280
|
-
cpSync(source,
|
|
10050
|
+
TARGETS.filter((target) => !isTracked(cwd, target)).forEach((target) => {
|
|
10051
|
+
ensureExcluded(cwd, `${target}/`);
|
|
10052
|
+
cpSync(source, join14(cwd, target), { recursive: true, force: true });
|
|
10053
|
+
});
|
|
10054
|
+
};
|
|
10055
|
+
|
|
10056
|
+
// src/worker/files/branches.ts
|
|
10057
|
+
import { realpath as realpath2 } from "node:fs/promises";
|
|
10058
|
+
|
|
10059
|
+
// src/worker/files/git.ts
|
|
10060
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
10061
|
+
import { promisify as promisify4 } from "node:util";
|
|
10062
|
+
var run = promisify4(execFile4);
|
|
10063
|
+
var GIT_TIMEOUT_MS = 3e3;
|
|
10064
|
+
var runGit = async (cwd, args, maxBuffer = 4 * 1024 * 1024) => {
|
|
10065
|
+
const { stdout } = await run("git", ["--literal-pathspecs", "-C", cwd, ...args], {
|
|
10066
|
+
encoding: "buffer",
|
|
10067
|
+
timeout: GIT_TIMEOUT_MS,
|
|
10068
|
+
maxBuffer,
|
|
10069
|
+
env: { ...process.env, LC_ALL: "C", GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
|
|
9281
10070
|
});
|
|
10071
|
+
return stdout;
|
|
10072
|
+
};
|
|
10073
|
+
|
|
10074
|
+
// src/worker/files/branches.ts
|
|
10075
|
+
var NOT_A_WORK_TREE_EXIT = 128;
|
|
10076
|
+
var git = async (cwd, args) => (await runGit(cwd, args)).toString("utf8");
|
|
10077
|
+
var workTreeTop = async (dir) => {
|
|
10078
|
+
try {
|
|
10079
|
+
return await realpath2((await git(dir, ["rev-parse", "--show-toplevel"])).trim());
|
|
10080
|
+
} catch (error) {
|
|
10081
|
+
if (error.code !== NOT_A_WORK_TREE_EXIT)
|
|
10082
|
+
console.warn("[files] git rev-parse failed; no branches to browse", error);
|
|
10083
|
+
return null;
|
|
10084
|
+
}
|
|
10085
|
+
};
|
|
10086
|
+
var parseWorktrees = (stdout) => stdout.split("\0\0").map((record) => record.split("\0").filter(Boolean)).flatMap((fields) => {
|
|
10087
|
+
const path = fields.find((field) => field.startsWith("worktree "))?.slice("worktree ".length);
|
|
10088
|
+
const ref = fields.find((field) => field.startsWith("branch "))?.slice("branch ".length);
|
|
10089
|
+
const skipped = fields.some((field) => field === "bare" || field.startsWith("prunable"));
|
|
10090
|
+
return path && ref && !skipped ? [{ path, name: ref.replace(/^refs\/heads\//, "") }] : [];
|
|
10091
|
+
});
|
|
10092
|
+
var parseTips = (stdout) => new Map(
|
|
10093
|
+
stdout.split("\n").map((line) => line.split("\0")).flatMap(
|
|
10094
|
+
([ref, commit]) => ref && commit ? [[ref.replace(/^refs\/heads\//, ""), commit]] : []
|
|
10095
|
+
)
|
|
10096
|
+
);
|
|
10097
|
+
var existingPath = async (path) => {
|
|
10098
|
+
try {
|
|
10099
|
+
return await realpath2(path);
|
|
10100
|
+
} catch (error) {
|
|
10101
|
+
if (error.code !== "ENOENT")
|
|
10102
|
+
console.warn(`[files] cannot resolve worktree ${path}`, error);
|
|
10103
|
+
return null;
|
|
10104
|
+
}
|
|
10105
|
+
};
|
|
10106
|
+
var rank = (branch) => branch.current ? 0 : branch.worktree ? 1 : 2;
|
|
10107
|
+
var discoverBranches = async (baseDir) => {
|
|
10108
|
+
const base = await realpath2(baseDir);
|
|
10109
|
+
if (await workTreeTop(base) !== base) return [];
|
|
10110
|
+
let worktrees;
|
|
10111
|
+
let tips;
|
|
10112
|
+
try {
|
|
10113
|
+
const [listed, refs] = await Promise.all([
|
|
10114
|
+
git(base, ["worktree", "list", "--porcelain", "-z"]),
|
|
10115
|
+
git(base, ["for-each-ref", "--format=%(refname)%00%(objectname)", "refs/heads"])
|
|
10116
|
+
]);
|
|
10117
|
+
worktrees = parseWorktrees(listed);
|
|
10118
|
+
tips = parseTips(refs);
|
|
10119
|
+
} catch (error) {
|
|
10120
|
+
console.warn("[files] listing git branches failed; no branches to browse", error);
|
|
10121
|
+
return [];
|
|
10122
|
+
}
|
|
10123
|
+
const checkedOut = new Map(
|
|
10124
|
+
(await Promise.all(
|
|
10125
|
+
worktrees.map(async ({ path, name }) => [name, await existingPath(path)])
|
|
10126
|
+
)).filter((entry) => entry[1] !== null)
|
|
10127
|
+
);
|
|
10128
|
+
return [.../* @__PURE__ */ new Set([...checkedOut.keys(), ...tips.keys()])].map((name) => {
|
|
10129
|
+
const worktree = checkedOut.get(name) ?? null;
|
|
10130
|
+
return { name, worktree, commit: tips.get(name) ?? null, current: worktree === base };
|
|
10131
|
+
}).sort((a, b) => rank(a) - rank(b) || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
10132
|
+
};
|
|
10133
|
+
|
|
10134
|
+
// src/worker/files/common.ts
|
|
10135
|
+
var FileError = class extends Error {
|
|
10136
|
+
constructor(code, message) {
|
|
10137
|
+
super(message);
|
|
10138
|
+
this.code = code;
|
|
10139
|
+
}
|
|
10140
|
+
code;
|
|
10141
|
+
};
|
|
10142
|
+
var notFound = (path) => new FileError("file_not_found", `not found: ${path}`);
|
|
10143
|
+
var checkedPath = (path, allowRoot) => {
|
|
10144
|
+
const rel = normalizeFilePath(path, allowRoot);
|
|
10145
|
+
if (rel === null) throw new FileError("invalid_request", `invalid path: ${path}`);
|
|
10146
|
+
if (isSensitiveFilePath(rel)) throw new FileError("forbidden_path", `protected file: ${rel}`);
|
|
10147
|
+
return rel;
|
|
10148
|
+
};
|
|
10149
|
+
var searchNeedle = (query3) => {
|
|
10150
|
+
const trimmed = query3.trim();
|
|
10151
|
+
if (trimmed === "" || trimmed.length > FILE_SEARCH_QUERY_MAX_LENGTH)
|
|
10152
|
+
throw new FileError(
|
|
10153
|
+
"invalid_request",
|
|
10154
|
+
`search query must be 1-${FILE_SEARCH_QUERY_MAX_LENGTH} characters`
|
|
10155
|
+
);
|
|
10156
|
+
return trimmed.toLowerCase();
|
|
10157
|
+
};
|
|
10158
|
+
var byKindThenName = (a, b) => {
|
|
10159
|
+
if (a.kind !== b.kind) return a.kind === "directory" ? -1 : 1;
|
|
10160
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
10161
|
+
};
|
|
10162
|
+
var decodeText = (bytes) => {
|
|
10163
|
+
if (bytes.includes(0)) return null;
|
|
10164
|
+
try {
|
|
10165
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
10166
|
+
} catch (error) {
|
|
10167
|
+
if (error instanceof TypeError) return null;
|
|
10168
|
+
throw error;
|
|
10169
|
+
}
|
|
10170
|
+
};
|
|
10171
|
+
var hasBytes = (bytes, signature, offset = 0) => signature.every((byte, index) => bytes[offset + index] === byte);
|
|
10172
|
+
var hasText = (bytes, text, offset = 0) => bytes.subarray(offset, offset + text.length).toString("latin1") === text;
|
|
10173
|
+
var SVG_START = /^\s*(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*(?:<!DOCTYPE[^>]*>\s*)?<svg[\s>]/i;
|
|
10174
|
+
var RAW_SIGNATURES = {
|
|
10175
|
+
"image/png": (bytes) => hasBytes(bytes, [137, 80, 78, 71, 13, 10, 26, 10]),
|
|
10176
|
+
"image/jpeg": (bytes) => hasBytes(bytes, [255, 216, 255]),
|
|
10177
|
+
"image/gif": (bytes) => hasText(bytes, "GIF8"),
|
|
10178
|
+
"image/webp": (bytes) => hasText(bytes, "RIFF") && hasText(bytes, "WEBP", 8),
|
|
10179
|
+
"image/avif": (bytes) => hasText(bytes, "ftyp", 4) && bytes.subarray(8, 32).toString("latin1").includes("avif"),
|
|
10180
|
+
"image/bmp": (bytes) => hasText(bytes, "BM"),
|
|
10181
|
+
"image/x-icon": (bytes) => hasBytes(bytes, [0, 0, 1, 0]),
|
|
10182
|
+
"image/svg+xml": (bytes) => {
|
|
10183
|
+
const text = decodeText(bytes);
|
|
10184
|
+
return text !== null && SVG_START.test(text.slice(0, 4096));
|
|
10185
|
+
},
|
|
10186
|
+
"text/css": (bytes) => decodeText(bytes) !== null,
|
|
10187
|
+
"font/woff": (bytes) => hasText(bytes, "wOFF"),
|
|
10188
|
+
"font/woff2": (bytes) => hasText(bytes, "wOF2"),
|
|
10189
|
+
"font/ttf": (bytes) => hasBytes(bytes, [0, 1, 0, 0]) || hasText(bytes, "true"),
|
|
10190
|
+
"font/otf": (bytes) => hasText(bytes, "OTTO")
|
|
10191
|
+
};
|
|
10192
|
+
var rawPayload = (path, mediaType, bytes) => {
|
|
10193
|
+
if (!RAW_SIGNATURES[mediaType]?.(bytes))
|
|
10194
|
+
throw new FileError("unsupported", `not a valid ${mediaType} file: ${path}`);
|
|
10195
|
+
return { mediaType, base64: bytes.toString("base64") };
|
|
10196
|
+
};
|
|
10197
|
+
|
|
10198
|
+
// src/worker/files/commit.ts
|
|
10199
|
+
var MISSING_OBJECT_EXIT = 128;
|
|
10200
|
+
var SEARCH_MAX_OUTPUT = 64 * 1024 * 1024;
|
|
10201
|
+
var REGULAR_FILE_MODES = /* @__PURE__ */ new Set(["100644", "100755"]);
|
|
10202
|
+
var parseTree = (stdout) => stdout.toString("utf8").split("\0").flatMap((record) => {
|
|
10203
|
+
const tab = record.indexOf(" ");
|
|
10204
|
+
if (tab < 0) return [];
|
|
10205
|
+
const [mode = "", type = "", oid = "", size = ""] = record.slice(0, tab).split(/ +/);
|
|
10206
|
+
const bytes = /^\d+$/.test(size) ? Number(size) : null;
|
|
10207
|
+
return [{ mode, type, oid, size: bytes, path: record.slice(tab + 1) }];
|
|
10208
|
+
});
|
|
10209
|
+
var isRegularFile = (entry) => entry.type === "blob" && REGULAR_FILE_MODES.has(entry.mode);
|
|
10210
|
+
var lsTree = async (repo, args) => {
|
|
10211
|
+
try {
|
|
10212
|
+
return parseTree(await runGit(repo, ["ls-tree", "-z", ...args]));
|
|
10213
|
+
} catch (error) {
|
|
10214
|
+
if (error.code === MISSING_OBJECT_EXIT) return null;
|
|
10215
|
+
throw error;
|
|
10216
|
+
}
|
|
10217
|
+
};
|
|
10218
|
+
var listCommitDirectory = async (repo, commit, path) => {
|
|
10219
|
+
const rel = checkedPath(path, true);
|
|
10220
|
+
const children = await lsTree(repo, [`${commit}:${rel}`]);
|
|
10221
|
+
if (children === null) throw notFound(rel || ".");
|
|
10222
|
+
const entries = children.flatMap((entry) => {
|
|
10223
|
+
const child = rel ? `${rel}/${entry.path}` : entry.path;
|
|
10224
|
+
if (isSensitiveFilePath(child)) return [];
|
|
10225
|
+
if (entry.type === "tree")
|
|
10226
|
+
return isExcludedDirectoryName(entry.path) ? [] : [{ name: entry.path, path: child, kind: "directory" }];
|
|
10227
|
+
return isRegularFile(entry) ? [{ name: entry.path, path: child, kind: "file" }] : [];
|
|
10228
|
+
}).sort(byKindThenName);
|
|
10229
|
+
return {
|
|
10230
|
+
path: rel,
|
|
10231
|
+
entries: entries.slice(0, FILE_DIRECTORY_MAX_ENTRIES),
|
|
10232
|
+
truncated: entries.length > FILE_DIRECTORY_MAX_ENTRIES
|
|
10233
|
+
};
|
|
10234
|
+
};
|
|
10235
|
+
var searchCommit = async (repo, commit, query3) => {
|
|
10236
|
+
const needle = searchNeedle(query3);
|
|
10237
|
+
let stdout;
|
|
10238
|
+
try {
|
|
10239
|
+
stdout = await runGit(repo, ["ls-tree", "-r", "-z", commit], SEARCH_MAX_OUTPUT);
|
|
10240
|
+
} catch (error) {
|
|
10241
|
+
if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER")
|
|
10242
|
+
throw new FileError("unavailable", "the branch has too many files to search");
|
|
10243
|
+
throw error;
|
|
10244
|
+
}
|
|
10245
|
+
const matches = parseTree(stdout).filter(
|
|
10246
|
+
(entry) => isRegularFile(entry) && !entry.path.split("/").slice(0, -1).some(isExcludedDirectoryName) && !isSensitiveFilePath(entry.path) && entry.path.toLowerCase().includes(needle)
|
|
10247
|
+
);
|
|
10248
|
+
return {
|
|
10249
|
+
entries: matches.slice(0, FILE_SEARCH_MAX_RESULTS).map((entry) => ({
|
|
10250
|
+
name: entry.path.split("/").at(-1) ?? entry.path,
|
|
10251
|
+
path: entry.path,
|
|
10252
|
+
kind: "file"
|
|
10253
|
+
})),
|
|
10254
|
+
truncated: matches.length > FILE_SEARCH_MAX_RESULTS
|
|
10255
|
+
};
|
|
10256
|
+
};
|
|
10257
|
+
var regularEntry = async (repo, commit, rel) => {
|
|
10258
|
+
const entry = (await lsTree(repo, ["-l", commit, "--", rel]))?.find((item) => item.path === rel);
|
|
10259
|
+
if (!entry || !isRegularFile(entry) || entry.size === null) throw notFound(rel);
|
|
10260
|
+
return { oid: entry.oid, size: entry.size };
|
|
10261
|
+
};
|
|
10262
|
+
var viewCommitFile = async (repo, commit, path) => {
|
|
10263
|
+
const rel = checkedPath(path, false);
|
|
10264
|
+
const entry = await regularEntry(repo, commit, rel);
|
|
10265
|
+
const meta = { path: rel, name: rel.split("/").at(-1) ?? rel, commit };
|
|
10266
|
+
const mediaType = imageMediaType(rel);
|
|
10267
|
+
if (mediaType)
|
|
10268
|
+
return entry.size > FILE_RAW_MAX_BYTES ? { ...meta, size: entry.size, kind: "too_large", limit: FILE_RAW_MAX_BYTES } : { ...meta, size: entry.size, kind: "image", mediaType };
|
|
10269
|
+
if (entry.size > FILE_TEXT_MAX_BYTES)
|
|
10270
|
+
return { ...meta, size: entry.size, kind: "too_large", limit: FILE_TEXT_MAX_BYTES };
|
|
10271
|
+
const bytes = await runGit(repo, ["cat-file", "blob", entry.oid], FILE_TEXT_MAX_BYTES + 1);
|
|
10272
|
+
const content = decodeText(bytes);
|
|
10273
|
+
return content === null ? { ...meta, size: bytes.length, kind: "binary" } : { ...meta, size: bytes.length, kind: "text", content };
|
|
10274
|
+
};
|
|
10275
|
+
var readRawCommitFile = async (repo, commit, path) => {
|
|
10276
|
+
const rel = checkedPath(path, false);
|
|
10277
|
+
const mediaType = rawMediaType(rel);
|
|
10278
|
+
if (!mediaType) throw new FileError("unsupported", `not served raw: ${rel}`);
|
|
10279
|
+
const entry = await regularEntry(repo, commit, rel);
|
|
10280
|
+
if (entry.size > FILE_RAW_MAX_BYTES)
|
|
10281
|
+
throw new FileError("unsupported", `too large to serve raw: ${rel}`);
|
|
10282
|
+
const bytes = await runGit(repo, ["cat-file", "blob", entry.oid], FILE_RAW_MAX_BYTES + 1);
|
|
10283
|
+
return rawPayload(rel, mediaType, bytes);
|
|
10284
|
+
};
|
|
10285
|
+
|
|
10286
|
+
// src/worker/files/disk.ts
|
|
10287
|
+
import { constants as constants2 } from "node:fs";
|
|
10288
|
+
import { open as open2, readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
|
|
10289
|
+
import { isAbsolute as isAbsolute4, join as join15, relative, sep } from "node:path";
|
|
10290
|
+
var isMissing = (error) => ["ENOENT", "ENOTDIR", "ELOOP"].includes(error.code ?? "");
|
|
10291
|
+
var childPath = (parent, name) => parent ? `${parent}/${name}` : name;
|
|
10292
|
+
var inside = (root, real) => {
|
|
10293
|
+
const rel = relative(root, real);
|
|
10294
|
+
if (isAbsolute4(rel) || rel === ".." || rel.startsWith(`..${sep}`)) return null;
|
|
10295
|
+
return rel.split(sep).join("/");
|
|
10296
|
+
};
|
|
10297
|
+
var resolveInside = async (root, path, allowRoot) => {
|
|
10298
|
+
const rel = checkedPath(path, allowRoot);
|
|
10299
|
+
let real;
|
|
10300
|
+
try {
|
|
10301
|
+
real = await realpath3(join15(root, rel));
|
|
10302
|
+
} catch (error) {
|
|
10303
|
+
if (isMissing(error)) throw notFound(rel);
|
|
10304
|
+
throw error;
|
|
10305
|
+
}
|
|
10306
|
+
const target = inside(root, real);
|
|
10307
|
+
if (target === null || isSensitiveFilePath(target)) throw notFound(rel);
|
|
10308
|
+
return { path: rel, real };
|
|
10309
|
+
};
|
|
10310
|
+
var symlinkEntry = async (root, linkPath, name, path) => {
|
|
10311
|
+
let real;
|
|
10312
|
+
try {
|
|
10313
|
+
real = await realpath3(linkPath);
|
|
10314
|
+
} catch (error) {
|
|
10315
|
+
if (!isMissing(error)) console.warn(`[files] cannot resolve symlink ${linkPath}`, error);
|
|
10316
|
+
return null;
|
|
10317
|
+
}
|
|
10318
|
+
const target = inside(root, real);
|
|
10319
|
+
if (target === null || isSensitiveFilePath(target)) return null;
|
|
10320
|
+
const info = await stat3(real);
|
|
10321
|
+
if (info.isDirectory())
|
|
10322
|
+
return isExcludedDirectoryName(name) ? null : { name, path, kind: "directory" };
|
|
10323
|
+
return info.isFile() ? { name, path, kind: "file" } : null;
|
|
10324
|
+
};
|
|
10325
|
+
var toEntry = async (root, dir, dirent) => {
|
|
10326
|
+
const path = childPath(dir.path, dirent.name);
|
|
10327
|
+
if (isSensitiveFilePath(path)) return null;
|
|
10328
|
+
if (dirent.isDirectory())
|
|
10329
|
+
return isExcludedDirectoryName(dirent.name) ? null : { name: dirent.name, path, kind: "directory" };
|
|
10330
|
+
if (dirent.isFile()) return { name: dirent.name, path, kind: "file" };
|
|
10331
|
+
return dirent.isSymbolicLink() ? symlinkEntry(root, join15(dir.real, dirent.name), dirent.name, path) : null;
|
|
10332
|
+
};
|
|
10333
|
+
var listDirectory = async (root, path) => {
|
|
10334
|
+
const dir = await resolveInside(root, path, true);
|
|
10335
|
+
if (!(await stat3(dir.real)).isDirectory()) throw notFound(dir.path);
|
|
10336
|
+
const dirents = await readdir(dir.real, { withFileTypes: true });
|
|
10337
|
+
const entries = (await Promise.all(dirents.map((dirent) => toEntry(root, dir, dirent)))).filter((entry) => entry !== null).sort(byKindThenName);
|
|
10338
|
+
return {
|
|
10339
|
+
path: dir.path,
|
|
10340
|
+
entries: entries.slice(0, FILE_DIRECTORY_MAX_ENTRIES),
|
|
10341
|
+
truncated: entries.length > FILE_DIRECTORY_MAX_ENTRIES
|
|
10342
|
+
};
|
|
10343
|
+
};
|
|
10344
|
+
var searchFiles = async (root, query3, now = Date.now) => {
|
|
10345
|
+
const needle = searchNeedle(query3);
|
|
10346
|
+
const deadline = now() + FILE_SEARCH_DEADLINE_MS;
|
|
10347
|
+
const entries = [];
|
|
10348
|
+
const pending = [""];
|
|
10349
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
10350
|
+
if (now() > deadline) return { entries, truncated: true };
|
|
10351
|
+
const dir = pending[index] ?? "";
|
|
10352
|
+
let dirents;
|
|
10353
|
+
try {
|
|
10354
|
+
dirents = await readdir(join15(root, dir), { withFileTypes: true });
|
|
10355
|
+
} catch (error) {
|
|
10356
|
+
console.warn(`[files] search skipped unreadable directory ${dir || "."}`, error);
|
|
10357
|
+
continue;
|
|
10358
|
+
}
|
|
10359
|
+
for (const dirent of dirents) {
|
|
10360
|
+
const path = childPath(dir, dirent.name);
|
|
10361
|
+
if (isSensitiveFilePath(path)) continue;
|
|
10362
|
+
if (dirent.isDirectory()) {
|
|
10363
|
+
if (!isExcludedDirectoryName(dirent.name)) pending.push(path);
|
|
10364
|
+
} else if (dirent.isFile() && path.toLowerCase().includes(needle)) {
|
|
10365
|
+
if (entries.length === FILE_SEARCH_MAX_RESULTS) return { entries, truncated: true };
|
|
10366
|
+
entries.push({ name: dirent.name, path, kind: "file" });
|
|
10367
|
+
}
|
|
10368
|
+
}
|
|
10369
|
+
}
|
|
10370
|
+
return { entries, truncated: false };
|
|
10371
|
+
};
|
|
10372
|
+
var readWithin = async (file, limit) => {
|
|
10373
|
+
const info = await stat3(file.real);
|
|
10374
|
+
if (!info.isFile()) throw notFound(file.path);
|
|
10375
|
+
if (info.size > limit) return { oversize: info.size };
|
|
10376
|
+
const handle = await open2(file.real, constants2.O_RDONLY | constants2.O_NONBLOCK);
|
|
10377
|
+
try {
|
|
10378
|
+
const opened = await handle.stat();
|
|
10379
|
+
if (!opened.isFile()) throw notFound(file.path);
|
|
10380
|
+
if (opened.size > limit) return { oversize: opened.size };
|
|
10381
|
+
const bytes = await handle.readFile();
|
|
10382
|
+
return bytes.length > limit ? { oversize: bytes.length } : { bytes };
|
|
10383
|
+
} finally {
|
|
10384
|
+
await handle.close();
|
|
10385
|
+
}
|
|
10386
|
+
};
|
|
10387
|
+
var viewFile = async (root, path) => {
|
|
10388
|
+
const file = await resolveInside(root, path, false);
|
|
10389
|
+
const meta = { path: file.path, name: file.path.split("/").at(-1) ?? file.path, commit: null };
|
|
10390
|
+
const mediaType = imageMediaType(file.path);
|
|
10391
|
+
if (mediaType) {
|
|
10392
|
+
const info = await stat3(file.real);
|
|
10393
|
+
if (!info.isFile()) throw notFound(file.path);
|
|
10394
|
+
return info.size > FILE_RAW_MAX_BYTES ? { ...meta, size: info.size, kind: "too_large", limit: FILE_RAW_MAX_BYTES } : { ...meta, size: info.size, kind: "image", mediaType };
|
|
10395
|
+
}
|
|
10396
|
+
const read = await readWithin(file, FILE_TEXT_MAX_BYTES);
|
|
10397
|
+
if ("oversize" in read)
|
|
10398
|
+
return { ...meta, size: read.oversize, kind: "too_large", limit: FILE_TEXT_MAX_BYTES };
|
|
10399
|
+
const content = decodeText(read.bytes);
|
|
10400
|
+
return content === null ? { ...meta, size: read.bytes.length, kind: "binary" } : { ...meta, size: read.bytes.length, kind: "text", content };
|
|
10401
|
+
};
|
|
10402
|
+
var readRawFile = async (root, path) => {
|
|
10403
|
+
const file = await resolveInside(root, path, false);
|
|
10404
|
+
const mediaType = rawMediaType(file.path);
|
|
10405
|
+
if (!mediaType) throw new FileError("unsupported", `not served raw: ${file.path}`);
|
|
10406
|
+
const read = await readWithin(file, FILE_RAW_MAX_BYTES);
|
|
10407
|
+
if ("oversize" in read) throw new FileError("unsupported", `too large to serve raw: ${file.path}`);
|
|
10408
|
+
return rawPayload(file.path, mediaType, read.bytes);
|
|
10409
|
+
};
|
|
10410
|
+
|
|
10411
|
+
// src/worker/files/handler.ts
|
|
10412
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
10413
|
+
var readerFor = (baseDir, branch) => {
|
|
10414
|
+
const { worktree, commit } = branch;
|
|
10415
|
+
if (worktree !== null)
|
|
10416
|
+
return {
|
|
10417
|
+
list: (path) => listDirectory(worktree, path),
|
|
10418
|
+
search: (query3) => searchFiles(worktree, query3),
|
|
10419
|
+
view: (path) => viewFile(worktree, path),
|
|
10420
|
+
raw: (path) => readRawFile(worktree, path)
|
|
10421
|
+
};
|
|
10422
|
+
if (commit === null) throw new FileError("branch_not_found", `branch ${branch.name} is empty`);
|
|
10423
|
+
return {
|
|
10424
|
+
list: (path) => listCommitDirectory(baseDir, commit, path),
|
|
10425
|
+
search: (query3) => searchCommit(baseDir, commit, query3),
|
|
10426
|
+
view: (path) => viewCommitFile(baseDir, commit, path),
|
|
10427
|
+
raw: (path) => readRawCommitFile(baseDir, commit, path)
|
|
10428
|
+
};
|
|
10429
|
+
};
|
|
10430
|
+
var run2 = async (baseDir, query3) => {
|
|
10431
|
+
const branches = await discoverBranches(baseDir);
|
|
10432
|
+
if (query3.op === "branches") return { ok: true, data: branches };
|
|
10433
|
+
const branch = branches.find((candidate) => candidate.name === query3.branch);
|
|
10434
|
+
if (!branch) throw new FileError("branch_not_found", `branch ${query3.branch} does not exist`);
|
|
10435
|
+
const reader = readerFor(baseDir, branch);
|
|
10436
|
+
if (query3.op === "list") return { ok: true, data: await reader.list(query3.path) };
|
|
10437
|
+
if (query3.op === "search") return { ok: true, data: await reader.search(query3.query) };
|
|
10438
|
+
if (query3.op === "raw") return { ok: true, data: await reader.raw(query3.path) };
|
|
10439
|
+
return { ok: true, data: await reader.view(query3.path) };
|
|
10440
|
+
};
|
|
10441
|
+
var handleFileQuery = async (baseDir, query3) => {
|
|
10442
|
+
try {
|
|
10443
|
+
return await run2(baseDir, query3);
|
|
10444
|
+
} catch (error) {
|
|
10445
|
+
if (error instanceof FileError) return { ok: false, code: error.code, message: error.message };
|
|
10446
|
+
console.error(`[files] ${query3.op} query failed`, error);
|
|
10447
|
+
return { ok: false, code: "unavailable", message: "the worker could not complete the read" };
|
|
10448
|
+
}
|
|
10449
|
+
};
|
|
10450
|
+
var createFileQueryRunner = (baseDir, limit = DEFAULT_CONCURRENCY) => {
|
|
10451
|
+
let active = 0;
|
|
10452
|
+
const waiting = [];
|
|
10453
|
+
const acquire = async () => {
|
|
10454
|
+
if (active < limit) {
|
|
10455
|
+
active += 1;
|
|
10456
|
+
return;
|
|
10457
|
+
}
|
|
10458
|
+
await new Promise((resolve4) => waiting.push(resolve4));
|
|
10459
|
+
};
|
|
10460
|
+
const release = () => {
|
|
10461
|
+
const next = waiting.shift();
|
|
10462
|
+
if (next) next();
|
|
10463
|
+
else active -= 1;
|
|
10464
|
+
};
|
|
10465
|
+
return async (query3) => {
|
|
10466
|
+
await acquire();
|
|
10467
|
+
try {
|
|
10468
|
+
return await handleFileQuery(baseDir, query3);
|
|
10469
|
+
} finally {
|
|
10470
|
+
release();
|
|
10471
|
+
}
|
|
10472
|
+
};
|
|
9282
10473
|
};
|
|
9283
10474
|
|
|
9284
10475
|
// src/worker/watchdog.ts
|
|
@@ -9339,12 +10530,13 @@ var createLiveness = (deps) => {
|
|
|
9339
10530
|
|
|
9340
10531
|
// src/worker/service-supervisor.ts
|
|
9341
10532
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
9342
|
-
import {
|
|
10533
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
10534
|
+
import { join as join17 } from "node:path";
|
|
9343
10535
|
|
|
9344
10536
|
// src/worker/service-process.ts
|
|
9345
10537
|
import { spawn as spawn2 } from "node:child_process";
|
|
9346
10538
|
import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
|
|
9347
|
-
import { dirname as
|
|
10539
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
9348
10540
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9349
10541
|
var START_TIMEOUT_MS = 1e4;
|
|
9350
10542
|
var isWrapperMessage = (message) => {
|
|
@@ -9354,8 +10546,8 @@ var isWrapperMessage = (message) => {
|
|
|
9354
10546
|
return value.type === "error" && typeof value.error === "string";
|
|
9355
10547
|
};
|
|
9356
10548
|
var binPath = () => {
|
|
9357
|
-
const here =
|
|
9358
|
-
const devShim =
|
|
10549
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
10550
|
+
const devShim = join16(here, "..", "..", "bin", "baton.mjs");
|
|
9359
10551
|
return existsSync6(devShim) ? devShim : fileURLToPath2(import.meta.url);
|
|
9360
10552
|
};
|
|
9361
10553
|
var startServiceProcess = (input) => {
|
|
@@ -9370,7 +10562,7 @@ var startServiceProcess = (input) => {
|
|
|
9370
10562
|
} finally {
|
|
9371
10563
|
closeSync(logFd);
|
|
9372
10564
|
}
|
|
9373
|
-
const started = new Promise((
|
|
10565
|
+
const started = new Promise((resolve4, reject) => {
|
|
9374
10566
|
let settled = false;
|
|
9375
10567
|
let timer;
|
|
9376
10568
|
const cleanup = () => {
|
|
@@ -9384,7 +10576,7 @@ var startServiceProcess = (input) => {
|
|
|
9384
10576
|
settled = true;
|
|
9385
10577
|
cleanup();
|
|
9386
10578
|
if (result instanceof Error) reject(result);
|
|
9387
|
-
else
|
|
10579
|
+
else resolve4(result);
|
|
9388
10580
|
};
|
|
9389
10581
|
const onMessage = (message) => {
|
|
9390
10582
|
if (!isWrapperMessage(message)) return;
|
|
@@ -9409,13 +10601,13 @@ var startServiceProcess = (input) => {
|
|
|
9409
10601
|
};
|
|
9410
10602
|
|
|
9411
10603
|
// src/worker/service-supervisor.ts
|
|
9412
|
-
var waitForExit = (child) => child.exitCode !== null ? Promise.resolve() : new Promise((
|
|
10604
|
+
var waitForExit = (child) => child.exitCode !== null ? Promise.resolve() : new Promise((resolve4) => child.once("exit", () => resolve4()));
|
|
9413
10605
|
var createServiceSupervisor = (deps) => {
|
|
9414
|
-
const {
|
|
10606
|
+
const { sendReport, cfg, log } = deps;
|
|
9415
10607
|
const entries = /* @__PURE__ */ new Map();
|
|
9416
10608
|
let reportQueue = Promise.resolve();
|
|
9417
|
-
const snapshot = () => [...entries.values()].filter((entry) => entry.appPid !== void 0).map(({
|
|
9418
|
-
|
|
10609
|
+
const snapshot = () => [...entries.values()].filter((entry) => entry.appPid !== void 0).map(({ cwd, name, startedAt, publicUrl, note }) => ({
|
|
10610
|
+
cwd,
|
|
9419
10611
|
name,
|
|
9420
10612
|
startedAt,
|
|
9421
10613
|
...publicUrl ? { publicUrl } : {},
|
|
@@ -9425,15 +10617,13 @@ var createServiceSupervisor = (deps) => {
|
|
|
9425
10617
|
const body = { services: snapshot(), ...result ? { result } : {} };
|
|
9426
10618
|
const request2 = reportQueue.then(() => sendReport(body)).then(() => {
|
|
9427
10619
|
});
|
|
9428
|
-
reportQueue = request2.catch(() => {
|
|
9429
|
-
});
|
|
10620
|
+
reportQueue = request2.catch((error) => log(`service report failed: ${String(error)}`));
|
|
9430
10621
|
return request2;
|
|
9431
10622
|
};
|
|
9432
10623
|
const remove = (entry) => {
|
|
9433
10624
|
if (entries.get(entry.name) !== entry) return;
|
|
9434
10625
|
entries.delete(entry.name);
|
|
9435
|
-
void report().catch(() => {
|
|
9436
|
-
});
|
|
10626
|
+
void report().catch((error) => log(`service removal report failed: ${String(error)}`));
|
|
9437
10627
|
};
|
|
9438
10628
|
const stopEntry = (entry) => {
|
|
9439
10629
|
if (entry.wrapper.connected) {
|
|
@@ -9442,7 +10632,18 @@ var createServiceSupervisor = (deps) => {
|
|
|
9442
10632
|
}
|
|
9443
10633
|
if (entry.appPid) killPidGroup(entry.appPid, "SIGKILL");
|
|
9444
10634
|
};
|
|
9445
|
-
const runOne = async (requestId,
|
|
10635
|
+
const runOne = async (requestId, cwd, name, argv, publicUrl, note) => {
|
|
10636
|
+
try {
|
|
10637
|
+
if (!(await stat4(cwd)).isDirectory())
|
|
10638
|
+
return report({ requestId, ok: false, error: `not a directory: ${cwd}`, status: 400 });
|
|
10639
|
+
} catch (error) {
|
|
10640
|
+
const code = error.code;
|
|
10641
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
10642
|
+
return report({ requestId, ok: false, error: `directory not found: ${cwd}`, status: 400 });
|
|
10643
|
+
}
|
|
10644
|
+
const logDir = join17(workerRuntimeDir(cfg), "services");
|
|
10645
|
+
const logPath = join17(logDir, `${name}.log`);
|
|
10646
|
+
mkdirSync4(logDir, { recursive: true, mode: 448 });
|
|
9446
10647
|
if (entries.has(name))
|
|
9447
10648
|
return report({
|
|
9448
10649
|
requestId,
|
|
@@ -9450,21 +10651,10 @@ var createServiceSupervisor = (deps) => {
|
|
|
9450
10651
|
error: `service "${name}" is already running`,
|
|
9451
10652
|
status: 409
|
|
9452
10653
|
});
|
|
9453
|
-
const
|
|
9454
|
-
if (session2.workerId !== cfg.workerId || !session2.worktreePath)
|
|
9455
|
-
return report({
|
|
9456
|
-
requestId,
|
|
9457
|
-
ok: false,
|
|
9458
|
-
error: "session is not materialized on this worker",
|
|
9459
|
-
status: 400
|
|
9460
|
-
});
|
|
9461
|
-
const logDir = join10(session2.worktreePath, ".baton-services");
|
|
9462
|
-
const logPath = join10(logDir, `${name}.log`);
|
|
9463
|
-
mkdirSync4(logDir, { recursive: true });
|
|
9464
|
-
const { wrapper, started } = startServiceProcess({ argv, cwd: session2.worktreePath, logPath });
|
|
10654
|
+
const { wrapper, started } = startServiceProcess({ argv, cwd, logPath });
|
|
9465
10655
|
const entry = {
|
|
9466
10656
|
wrapper,
|
|
9467
|
-
|
|
10657
|
+
cwd,
|
|
9468
10658
|
name,
|
|
9469
10659
|
startedAt: Date.now(),
|
|
9470
10660
|
publicUrl,
|
|
@@ -9483,19 +10673,20 @@ var createServiceSupervisor = (deps) => {
|
|
|
9483
10673
|
}
|
|
9484
10674
|
const service2 = {
|
|
9485
10675
|
workerId: cfg.workerId,
|
|
9486
|
-
|
|
10676
|
+
cwd,
|
|
9487
10677
|
name,
|
|
9488
10678
|
startedAt: entry.startedAt,
|
|
9489
10679
|
...publicUrl ? { publicUrl } : {},
|
|
9490
10680
|
...note ? { note } : {}
|
|
9491
10681
|
};
|
|
9492
|
-
log(`service ${name} started in ${
|
|
10682
|
+
log(`service ${name} started in ${cwd}`);
|
|
9493
10683
|
await report({ requestId, ok: true, service: service2, logPath });
|
|
9494
10684
|
};
|
|
9495
|
-
const
|
|
10685
|
+
const run4 = async (requestId, cwd, name, argv, publicUrl, note) => {
|
|
9496
10686
|
try {
|
|
9497
|
-
await runOne(requestId,
|
|
10687
|
+
await runOne(requestId, cwd, name, argv, publicUrl, note);
|
|
9498
10688
|
} catch (error) {
|
|
10689
|
+
log(`service ${name} failed: ${String(error)}`);
|
|
9499
10690
|
await report({ requestId, ok: false, error: String(error), status: 500 });
|
|
9500
10691
|
}
|
|
9501
10692
|
};
|
|
@@ -9508,21 +10699,16 @@ var createServiceSupervisor = (deps) => {
|
|
|
9508
10699
|
remove(entry);
|
|
9509
10700
|
await report({ requestId, ok: true });
|
|
9510
10701
|
};
|
|
9511
|
-
const stopSession = async (sessionId) => {
|
|
9512
|
-
const matches = [...entries.values()].filter((entry) => entry.sessionId === sessionId);
|
|
9513
|
-
matches.forEach(stopEntry);
|
|
9514
|
-
await Promise.all(matches.map((entry) => waitForExit(entry.wrapper)));
|
|
9515
|
-
matches.forEach(remove);
|
|
9516
|
-
};
|
|
9517
10702
|
const killAll = () => entries.forEach(stopEntry);
|
|
9518
|
-
return { run:
|
|
10703
|
+
return { run: run4, stop, report, killAll };
|
|
9519
10704
|
};
|
|
9520
10705
|
|
|
9521
10706
|
// src/worker/session-supervisor.ts
|
|
9522
10707
|
import { spawn as spawn3 } from "node:child_process";
|
|
9523
10708
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9524
10709
|
import { existsSync as existsSync7 } from "node:fs";
|
|
9525
|
-
import {
|
|
10710
|
+
import { mkdir as mkdir6, rm as rm3, stat as stat5 } from "node:fs/promises";
|
|
10711
|
+
import { dirname as dirname5, join as join21 } from "node:path";
|
|
9526
10712
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9527
10713
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
9528
10714
|
|
|
@@ -9559,7 +10745,7 @@ var buildTitlePrompt = (userText, assistantText) => {
|
|
|
9559
10745
|
].join("\n");
|
|
9560
10746
|
};
|
|
9561
10747
|
var generateTitle = async (input) => {
|
|
9562
|
-
const {
|
|
10748
|
+
const { cwd, userText, assistantText, queryFn } = input;
|
|
9563
10749
|
if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
|
|
9564
10750
|
const exe = claudeExecutable();
|
|
9565
10751
|
const abort = new AbortController();
|
|
@@ -9572,7 +10758,7 @@ var generateTitle = async (input) => {
|
|
|
9572
10758
|
const messages = queryFn({
|
|
9573
10759
|
prompt: buildTitlePrompt(userText, assistantText),
|
|
9574
10760
|
options: {
|
|
9575
|
-
cwd
|
|
10761
|
+
cwd,
|
|
9576
10762
|
// A one-shot summarizer, not an agent: replace the ~18k-token Claude
|
|
9577
10763
|
// Code preset system prompt. Inside the agent harness the model
|
|
9578
10764
|
// routinely answers NONE to this ask (verified against reclaude);
|
|
@@ -9616,7 +10802,7 @@ var generateTitle = async (input) => {
|
|
|
9616
10802
|
// src/session/runner/title-codex.ts
|
|
9617
10803
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
9618
10804
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
9619
|
-
import { join as
|
|
10805
|
+
import { join as join18 } from "node:path";
|
|
9620
10806
|
import { Codex as Codex2 } from "@openai/codex-sdk";
|
|
9621
10807
|
var generateTitleWithCodex = async (input) => {
|
|
9622
10808
|
const { userText, assistantText } = input;
|
|
@@ -9625,7 +10811,7 @@ var generateTitleWithCodex = async (input) => {
|
|
|
9625
10811
|
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
9626
10812
|
let cwd = "";
|
|
9627
10813
|
try {
|
|
9628
|
-
cwd = await mkdtemp(
|
|
10814
|
+
cwd = await mkdtemp(join18(tmpdir3(), "baton-title-"));
|
|
9629
10815
|
const client = input.client ?? new Codex2();
|
|
9630
10816
|
const thread = client.startThread({
|
|
9631
10817
|
workingDirectory: cwd,
|
|
@@ -9651,19 +10837,79 @@ var generateTitleWithCodex = async (input) => {
|
|
|
9651
10837
|
}
|
|
9652
10838
|
};
|
|
9653
10839
|
|
|
10840
|
+
// src/session/runner/title-pi.ts
|
|
10841
|
+
import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
|
|
10842
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
10843
|
+
import { join as join19 } from "node:path";
|
|
10844
|
+
var generateTitleWithPi = async (input) => {
|
|
10845
|
+
const { userText, assistantText } = input;
|
|
10846
|
+
if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
|
|
10847
|
+
let cwd = "";
|
|
10848
|
+
let timedOut = false;
|
|
10849
|
+
let timer;
|
|
10850
|
+
try {
|
|
10851
|
+
cwd = await mkdtemp2(join19(tmpdir4(), "baton-title-"));
|
|
10852
|
+
const pi = await import("@earendil-works/pi-coding-agent");
|
|
10853
|
+
const agentDir = pi.getAgentDir();
|
|
10854
|
+
const resourceLoader = new pi.DefaultResourceLoader({
|
|
10855
|
+
cwd,
|
|
10856
|
+
agentDir,
|
|
10857
|
+
noExtensions: true,
|
|
10858
|
+
noSkills: true,
|
|
10859
|
+
noPromptTemplates: true,
|
|
10860
|
+
noThemes: true,
|
|
10861
|
+
noContextFiles: true,
|
|
10862
|
+
systemPrompt: "You title chat sessions."
|
|
10863
|
+
});
|
|
10864
|
+
await resourceLoader.reload();
|
|
10865
|
+
const { session: session2 } = await pi.createAgentSession({
|
|
10866
|
+
cwd,
|
|
10867
|
+
agentDir,
|
|
10868
|
+
resourceLoader,
|
|
10869
|
+
sessionManager: pi.SessionManager.inMemory(cwd),
|
|
10870
|
+
noTools: "all"
|
|
10871
|
+
});
|
|
10872
|
+
let error;
|
|
10873
|
+
session2.subscribe((event) => {
|
|
10874
|
+
if (event.type === "message_end" && event.message.role === "assistant")
|
|
10875
|
+
error = event.message.stopReason === "error" ? event.message.errorMessage : void 0;
|
|
10876
|
+
});
|
|
10877
|
+
timer = setTimeout(() => {
|
|
10878
|
+
timedOut = true;
|
|
10879
|
+
void session2.abort().catch((e) => console.error(`[title] pi abort: ${String(e)}`));
|
|
10880
|
+
}, TITLE_TIMEOUT_MS);
|
|
10881
|
+
try {
|
|
10882
|
+
await session2.prompt(buildTitlePrompt(userText, assistantText));
|
|
10883
|
+
if (timedOut) return { kind: "error", reason: `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` };
|
|
10884
|
+
if (error) return { kind: "error", reason: error.slice(0, 300) };
|
|
10885
|
+
return titleOutcome(session2.getLastAssistantText() ?? "");
|
|
10886
|
+
} finally {
|
|
10887
|
+
session2.dispose();
|
|
10888
|
+
}
|
|
10889
|
+
} catch (error) {
|
|
10890
|
+
return { kind: "error", reason: String(error).slice(0, 300) };
|
|
10891
|
+
} finally {
|
|
10892
|
+
clearTimeout(timer);
|
|
10893
|
+
if (cwd)
|
|
10894
|
+
await rm2(cwd, { recursive: true, force: true }).catch(
|
|
10895
|
+
(e) => console.error(`[title] cleanup ${cwd}: ${String(e)}`)
|
|
10896
|
+
);
|
|
10897
|
+
}
|
|
10898
|
+
};
|
|
10899
|
+
|
|
9654
10900
|
// src/worker/session-process.ts
|
|
9655
|
-
import { execFile as
|
|
9656
|
-
import { readFile, rename, writeFile as
|
|
9657
|
-
import { join as
|
|
9658
|
-
import { promisify as
|
|
9659
|
-
var
|
|
10901
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
10902
|
+
import { readFile as readFile3, rename, writeFile as writeFile4 } from "node:fs/promises";
|
|
10903
|
+
import { join as join20 } from "node:path";
|
|
10904
|
+
import { promisify as promisify5 } from "node:util";
|
|
10905
|
+
var run3 = promisify5(execFile5);
|
|
9660
10906
|
var RUNNER_RECORD = ".baton-runner.json";
|
|
9661
|
-
var recordPath = (
|
|
10907
|
+
var recordPath = (cwd) => join20(cwd, RUNNER_RECORD);
|
|
9662
10908
|
var codeOf = (error) => typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
9663
|
-
var readRunnerRecord = async (
|
|
10909
|
+
var readRunnerRecord = async (cwd) => {
|
|
9664
10910
|
let raw;
|
|
9665
10911
|
try {
|
|
9666
|
-
raw = await
|
|
10912
|
+
raw = await readFile3(recordPath(cwd), "utf8");
|
|
9667
10913
|
} catch (error) {
|
|
9668
10914
|
if (codeOf(error) === "ENOENT") return null;
|
|
9669
10915
|
throw error;
|
|
@@ -9673,10 +10919,10 @@ var readRunnerRecord = async (worktreePath) => {
|
|
|
9673
10919
|
throw new Error("invalid session process record");
|
|
9674
10920
|
return value;
|
|
9675
10921
|
};
|
|
9676
|
-
var writeRunnerRecord = async (
|
|
9677
|
-
const path = recordPath(
|
|
10922
|
+
var writeRunnerRecord = async (cwd, record) => {
|
|
10923
|
+
const path = recordPath(cwd);
|
|
9678
10924
|
const temporary = `${path}.${record.runnerToken}.tmp`;
|
|
9679
|
-
await
|
|
10925
|
+
await writeFile4(temporary, JSON.stringify(record), { mode: 384, flag: "wx" });
|
|
9680
10926
|
await rename(temporary, path);
|
|
9681
10927
|
};
|
|
9682
10928
|
var groupExists = (pgid) => {
|
|
@@ -9701,7 +10947,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
|
|
|
9701
10947
|
if (!groupExists(record.pgid)) return;
|
|
9702
10948
|
let stdout;
|
|
9703
10949
|
try {
|
|
9704
|
-
const result = await
|
|
10950
|
+
const result = await run3("ps", ["-ww", "-p", String(record.pid), "-o", "pgid=", "-o", "args="]);
|
|
9705
10951
|
stdout = result.stdout;
|
|
9706
10952
|
} catch (error) {
|
|
9707
10953
|
if (!groupExists(record.pgid)) return;
|
|
@@ -9714,37 +10960,23 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
|
|
|
9714
10960
|
signalGroup(record.pgid, "SIGTERM");
|
|
9715
10961
|
const deadline = Date.now() + graceMs;
|
|
9716
10962
|
while (groupExists(record.pgid) && Date.now() < deadline)
|
|
9717
|
-
await new Promise((
|
|
10963
|
+
await new Promise((resolve4) => setTimeout(resolve4, 50));
|
|
9718
10964
|
if (!groupExists(record.pgid)) return;
|
|
9719
10965
|
signalGroup(record.pgid, "SIGKILL");
|
|
9720
10966
|
const killDeadline = Date.now() + graceMs;
|
|
9721
10967
|
while (groupExists(record.pgid) && Date.now() < killDeadline)
|
|
9722
|
-
await new Promise((
|
|
10968
|
+
await new Promise((resolve4) => setTimeout(resolve4, 50));
|
|
9723
10969
|
if (groupExists(record.pgid)) throw new Error("session process group has not exited");
|
|
9724
10970
|
};
|
|
9725
10971
|
|
|
9726
10972
|
// src/worker/session-supervisor.ts
|
|
9727
10973
|
var binPath2 = () => {
|
|
9728
|
-
const here =
|
|
9729
|
-
const devShim =
|
|
10974
|
+
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
10975
|
+
const devShim = join21(here, "..", "..", "bin", "baton.mjs");
|
|
9730
10976
|
return existsSync7(devShim) ? devShim : fileURLToPath3(import.meta.url);
|
|
9731
10977
|
};
|
|
9732
10978
|
var createSessionSupervisor = (deps) => {
|
|
9733
|
-
const { client, cfg,
|
|
9734
|
-
const worktreeDir = defaultWorktreeDir();
|
|
9735
|
-
const baseBranch = cfg.baseBranch ?? repoHeadBranch(repo);
|
|
9736
|
-
const syncBase = deps.syncBase ?? syncBaseBranch;
|
|
9737
|
-
let syncInFlight = null;
|
|
9738
|
-
const syncedBase = () => {
|
|
9739
|
-
if (syncInFlight) return syncInFlight;
|
|
9740
|
-
const pending = syncBase(repo, baseBranch);
|
|
9741
|
-
syncInFlight = pending;
|
|
9742
|
-
const clear = () => {
|
|
9743
|
-
if (syncInFlight === pending) syncInFlight = null;
|
|
9744
|
-
};
|
|
9745
|
-
void pending.then(clear, clear);
|
|
9746
|
-
return pending;
|
|
9747
|
-
};
|
|
10979
|
+
const { client, cfg, cwd, log, hasTerminal, closeTerminal } = deps;
|
|
9748
10980
|
const children = /* @__PURE__ */ new Map();
|
|
9749
10981
|
const lifecycle = /* @__PURE__ */ new Map();
|
|
9750
10982
|
const enqueue = (id, action) => {
|
|
@@ -9763,8 +10995,8 @@ var createSessionSupervisor = (deps) => {
|
|
|
9763
10995
|
const cancelStart = (sessionId) => {
|
|
9764
10996
|
startEpochs.set(sessionId, currentStartEpoch(sessionId) + 1);
|
|
9765
10997
|
};
|
|
9766
|
-
const stopRecord = async (sessionId,
|
|
9767
|
-
const record = await readRunnerRecord(
|
|
10998
|
+
const stopRecord = async (sessionId, token) => {
|
|
10999
|
+
const record = await readRunnerRecord(sessionRuntimeDir(cfg, sessionId));
|
|
9768
11000
|
if (!record) {
|
|
9769
11001
|
if (token) throw new Error("missing record for an unfinished execution");
|
|
9770
11002
|
return;
|
|
@@ -9773,9 +11005,9 @@ var createSessionSupervisor = (deps) => {
|
|
|
9773
11005
|
throw new Error("runner identity does not match the attempt");
|
|
9774
11006
|
await stopRecordedRunner(record, { server: cfg.server, workerId: cfg.workerId, sessionId });
|
|
9775
11007
|
};
|
|
9776
|
-
const spawnChild = async (sessionId,
|
|
11008
|
+
const spawnChild = async (sessionId, cwd2, wasCanceled) => {
|
|
9777
11009
|
if (children.has(sessionId)) return;
|
|
9778
|
-
await stopRecord(sessionId
|
|
11010
|
+
await stopRecord(sessionId);
|
|
9779
11011
|
const runnerToken = randomUUID2();
|
|
9780
11012
|
const child = spawn3(
|
|
9781
11013
|
process.execPath,
|
|
@@ -9783,16 +11015,17 @@ var createSessionSupervisor = (deps) => {
|
|
|
9783
11015
|
{
|
|
9784
11016
|
detached: true,
|
|
9785
11017
|
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
9786
|
-
|
|
11018
|
+
cwd: cwd2,
|
|
11019
|
+
env: workerEnv(cfg)
|
|
9787
11020
|
}
|
|
9788
11021
|
);
|
|
9789
|
-
const entry = { child
|
|
11022
|
+
const entry = { child };
|
|
9790
11023
|
children.set(sessionId, entry);
|
|
9791
11024
|
let readyTimer;
|
|
9792
|
-
const ready = new Promise((
|
|
11025
|
+
const ready = new Promise((resolve4, reject) => {
|
|
9793
11026
|
child.on("message", (message) => {
|
|
9794
11027
|
if (typeof message === "object" && message !== null && "type" in message && message.type === "ready")
|
|
9795
|
-
|
|
11028
|
+
resolve4();
|
|
9796
11029
|
});
|
|
9797
11030
|
child.once("error", reject);
|
|
9798
11031
|
child.once("exit", () => reject(new Error("session child exited before ready")));
|
|
@@ -9812,8 +11045,8 @@ var createSessionSupervisor = (deps) => {
|
|
|
9812
11045
|
void stop(sessionId).catch((error) => log(`stuck execution cleanup failed: ${String(error)}`));
|
|
9813
11046
|
});
|
|
9814
11047
|
try {
|
|
9815
|
-
await new Promise((
|
|
9816
|
-
child.once("spawn",
|
|
11048
|
+
await new Promise((resolve4, reject) => {
|
|
11049
|
+
child.once("spawn", resolve4);
|
|
9817
11050
|
child.once("error", reject);
|
|
9818
11051
|
});
|
|
9819
11052
|
if (!child.pid) throw new Error("session child has no PID");
|
|
@@ -9825,11 +11058,11 @@ var createSessionSupervisor = (deps) => {
|
|
|
9825
11058
|
pid: child.pid,
|
|
9826
11059
|
pgid: child.pid
|
|
9827
11060
|
};
|
|
9828
|
-
await writeRunnerRecord(
|
|
11061
|
+
await writeRunnerRecord(sessionRuntimeDir(cfg, sessionId), record);
|
|
9829
11062
|
await readiness;
|
|
9830
11063
|
if (wasCanceled()) return;
|
|
9831
11064
|
await new Promise(
|
|
9832
|
-
(
|
|
11065
|
+
(resolve4, reject) => child.send({ type: "execute" }, (error) => error ? reject(error) : resolve4())
|
|
9833
11066
|
);
|
|
9834
11067
|
} catch (error) {
|
|
9835
11068
|
killProcessGroup(child);
|
|
@@ -9847,37 +11080,20 @@ var createSessionSupervisor = (deps) => {
|
|
|
9847
11080
|
if (execution.paused || execution.contextResetRequested || execution.attempt?.status === "running" || execution.attempt?.status === "stopping")
|
|
9848
11081
|
return;
|
|
9849
11082
|
if (wasCanceled()) return log(`session #${sessionId} start canceled before materializing`);
|
|
9850
|
-
|
|
9851
|
-
if (!
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
const
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
9860
|
-
sessionCode: sessionCode.slice(0, 8),
|
|
9861
|
-
base
|
|
9862
|
-
});
|
|
9863
|
-
await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
|
|
9864
|
-
if (wasCanceled()) return log(`session #${sessionId} start canceled after materializing`);
|
|
9865
|
-
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
9866
|
-
} else if (!existsSync7(worktreePath)) {
|
|
9867
|
-
await restoreWorktree(repo, worktreePath, session2.agentSessionId.slice(0, 8), baseBranch);
|
|
9868
|
-
if (wasCanceled()) return log(`session #${sessionId} start canceled during worktree restore`);
|
|
9869
|
-
log(`recreated session #${sessionId} worktree (was missing) \u2192 ${worktreePath}`);
|
|
9870
|
-
}
|
|
9871
|
-
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
9872
|
-
ensureExcluded(repo, ".baton-services/");
|
|
9873
|
-
ensureExcluded(repo, RUNNER_RECORD);
|
|
9874
|
-
ensureExcluded(repo, ".baton-runner.json.*.tmp");
|
|
9875
|
-
saveProjectConfig(join13(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg, sessionId));
|
|
9876
|
-
syncBundledArtifactSkill(repo, worktreePath);
|
|
11083
|
+
const directory2 = session2.cwd ?? cwd;
|
|
11084
|
+
if (!(await stat5(directory2)).isDirectory()) throw new Error(`not a directory: ${directory2}`);
|
|
11085
|
+
if (wasCanceled()) return;
|
|
11086
|
+
if (!session2.agentSessionId || !session2.cwd) {
|
|
11087
|
+
const id = randomUUID2();
|
|
11088
|
+
const agentSessionId = session2.agentKind === "codex" ? `pending:${id}` : id;
|
|
11089
|
+
await client.sessions.materialize(sessionId, { agentSessionId, cwd: directory2 }, cfg.apiToken);
|
|
11090
|
+
log(`materialized session #${sessionId} (${name}) \u2192 ${directory2}`);
|
|
11091
|
+
}
|
|
11092
|
+
await mkdir6(sessionRuntimeDir(cfg, sessionId), { recursive: true, mode: 448 });
|
|
9877
11093
|
if (hasTerminal(sessionId))
|
|
9878
11094
|
return log(`session #${sessionId} terminal opened mid-start \u2014 skipping headless start`);
|
|
9879
11095
|
if (wasCanceled()) return;
|
|
9880
|
-
await spawnChild(sessionId,
|
|
11096
|
+
await spawnChild(sessionId, directory2, wasCanceled);
|
|
9881
11097
|
};
|
|
9882
11098
|
const start = (sessionId, name) => {
|
|
9883
11099
|
const epoch = currentStartEpoch(sessionId);
|
|
@@ -9895,38 +11111,31 @@ var createSessionSupervisor = (deps) => {
|
|
|
9895
11111
|
cancelStart(sessionId);
|
|
9896
11112
|
return enqueue(sessionId, async () => {
|
|
9897
11113
|
const entry = children.get(sessionId);
|
|
9898
|
-
|
|
9899
|
-
if (session2.worktreePath) await stopRecord(sessionId, session2.worktreePath);
|
|
11114
|
+
await stopRecord(sessionId);
|
|
9900
11115
|
if (entry && children.get(sessionId) === entry) children.delete(sessionId);
|
|
9901
11116
|
});
|
|
9902
11117
|
};
|
|
9903
|
-
const remove = (sessionId
|
|
11118
|
+
const remove = (sessionId) => {
|
|
9904
11119
|
cancelStart(sessionId);
|
|
9905
11120
|
closeTerminal(sessionId);
|
|
9906
11121
|
return enqueue(sessionId, async () => {
|
|
9907
11122
|
const entry = children.get(sessionId);
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
await stopRecord(sessionId, path);
|
|
9911
|
-
removeWorktree(repo, path);
|
|
9912
|
-
}
|
|
11123
|
+
await stopRecord(sessionId);
|
|
11124
|
+
await rm3(sessionRuntimeDir(cfg, sessionId), { recursive: true, force: true });
|
|
9913
11125
|
if (children.get(sessionId) === entry) children.delete(sessionId);
|
|
9914
11126
|
});
|
|
9915
11127
|
};
|
|
9916
|
-
const title = async (sessionId, agentSessionId,
|
|
11128
|
+
const title = async (sessionId, agentSessionId, cwd2) => {
|
|
9917
11129
|
const session2 = await client.sessions.get(sessionId);
|
|
9918
11130
|
const events = await client.sessions.listEvents(sessionId);
|
|
9919
11131
|
const exchange = parseFirstExchangeFromEvents(events) ?? (session2.agentKind === "claude-code" ? readFirstExchange(agentSessionId) : null);
|
|
9920
11132
|
if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
|
|
9921
|
-
const
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
assistantText: exchange.assistantText,
|
|
9928
|
-
queryFn: query2
|
|
9929
|
-
});
|
|
11133
|
+
const titlers = {
|
|
11134
|
+
"claude-code": (e) => generateTitle({ cwd: cwd2, ...e, queryFn: query2 }),
|
|
11135
|
+
codex: generateTitleWithCodex,
|
|
11136
|
+
pi: generateTitleWithPi
|
|
11137
|
+
};
|
|
11138
|
+
const outcome = await titlers[session2.agentKind](exchange);
|
|
9930
11139
|
if (outcome.kind === "error") return log(`title #${sessionId} failed: ${outcome.reason}`);
|
|
9931
11140
|
if (outcome.kind === "declined")
|
|
9932
11141
|
return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
@@ -9942,10 +11151,10 @@ var createSessionSupervisor = (deps) => {
|
|
|
9942
11151
|
const session2 = await client.sessions.get(candidate.id);
|
|
9943
11152
|
let execution = await client.sessions.execution(session2.id);
|
|
9944
11153
|
if (execution.attempt?.status === "stopping") {
|
|
9945
|
-
if (!session2.
|
|
11154
|
+
if (!session2.cwd) throw new Error("unfinished execution without cwd");
|
|
9946
11155
|
if (children.has(session2.id) && Date.now() - (execution.attempt.stopRequestedAt ?? 0) < 1e4)
|
|
9947
11156
|
return;
|
|
9948
|
-
await stopRecord(session2.id,
|
|
11157
|
+
await stopRecord(session2.id, execution.attempt.runnerToken);
|
|
9949
11158
|
children.delete(session2.id);
|
|
9950
11159
|
await client.sessions.stopped(
|
|
9951
11160
|
session2.id,
|
|
@@ -9955,12 +11164,12 @@ var createSessionSupervisor = (deps) => {
|
|
|
9955
11164
|
execution = await client.sessions.execution(session2.id);
|
|
9956
11165
|
}
|
|
9957
11166
|
if (execution.paused) {
|
|
9958
|
-
if (session2.
|
|
11167
|
+
if (session2.cwd) await stopRecord(session2.id);
|
|
9959
11168
|
return;
|
|
9960
11169
|
}
|
|
9961
11170
|
if (execution.attempt?.status === "running") {
|
|
9962
|
-
if (!children.has(session2.id) && session2.
|
|
9963
|
-
await stopRecord(session2.id,
|
|
11171
|
+
if (!children.has(session2.id) && session2.cwd)
|
|
11172
|
+
await stopRecord(session2.id, execution.attempt.runnerToken);
|
|
9964
11173
|
return;
|
|
9965
11174
|
}
|
|
9966
11175
|
if (execution.pendingCount || execution.turn?.status === "recovering")
|
|
@@ -9971,7 +11180,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
9971
11180
|
for (const [id, entry] of children) {
|
|
9972
11181
|
if (!live.has(id))
|
|
9973
11182
|
await enqueue(id, async () => {
|
|
9974
|
-
await stopRecord(id
|
|
11183
|
+
await stopRecord(id);
|
|
9975
11184
|
if (children.get(id) === entry) children.delete(id);
|
|
9976
11185
|
});
|
|
9977
11186
|
}
|
|
@@ -9991,7 +11200,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
9991
11200
|
};
|
|
9992
11201
|
|
|
9993
11202
|
// src/worker/terminal-manager.ts
|
|
9994
|
-
import {
|
|
11203
|
+
import { statSync as statSync2 } from "node:fs";
|
|
9995
11204
|
import { spawn as spawn4 } from "node-pty";
|
|
9996
11205
|
|
|
9997
11206
|
// ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
|
|
@@ -10007,8 +11216,9 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
10007
11216
|
// src/worker/pty.ts
|
|
10008
11217
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
10009
11218
|
import { createRequire as createModuleRequire } from "node:module";
|
|
10010
|
-
import { homedir as
|
|
10011
|
-
import { join as
|
|
11219
|
+
import { homedir as homedir5 } from "node:os";
|
|
11220
|
+
import { dirname as dirname6, join as join22 } from "node:path";
|
|
11221
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10012
11222
|
var MAX_TERMINALS = 10;
|
|
10013
11223
|
var claudePtyCommand = (agentSessionId, hasJsonl, env = process.env) => ({
|
|
10014
11224
|
file: env.BATON_CLAUDE_BIN?.trim() || "claude",
|
|
@@ -10023,39 +11233,45 @@ var codexPtyCommand = (agentSessionId, env = process.env) => {
|
|
|
10023
11233
|
args: [moduleRequire.resolve("@openai/codex/bin/codex.js"), ...args]
|
|
10024
11234
|
};
|
|
10025
11235
|
};
|
|
11236
|
+
var piPtyCommand = (agentSessionId, env = process.env) => {
|
|
11237
|
+
const args = ["--session-id", agentSessionId];
|
|
11238
|
+
const override = env.BATON_PI_BIN?.trim();
|
|
11239
|
+
if (override) return { file: override, args };
|
|
11240
|
+
const entry = fileURLToPath4(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
11241
|
+
return { file: process.execPath, args: [join22(dirname6(entry), "bundle", "cli.js"), ...args] };
|
|
11242
|
+
};
|
|
10026
11243
|
var hasSessionJsonl = (agentSessionId) => {
|
|
10027
|
-
const root =
|
|
11244
|
+
const root = join22(homedir5(), ".claude", "projects");
|
|
10028
11245
|
if (!existsSync8(root)) return false;
|
|
10029
11246
|
for (const dir of readdirSync2(root))
|
|
10030
|
-
if (existsSync8(
|
|
11247
|
+
if (existsSync8(join22(root, dir, `${agentSessionId}.jsonl`))) return true;
|
|
10031
11248
|
return false;
|
|
10032
11249
|
};
|
|
11250
|
+
var ptyCommand = (kind, agentSessionId) => ({
|
|
11251
|
+
"claude-code": () => claudePtyCommand(agentSessionId, hasSessionJsonl(agentSessionId)),
|
|
11252
|
+
codex: () => codexPtyCommand(agentSessionId),
|
|
11253
|
+
pi: () => piPtyCommand(agentSessionId)
|
|
11254
|
+
})[kind]();
|
|
10033
11255
|
var serverTerminalWsUrl = (server, sessionId) => `${server.replace(/^http/, "ws")}/workers/me/terminal/ws?sessionId=${sessionId}`;
|
|
10034
11256
|
var isPositiveDim = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
10035
11257
|
|
|
10036
11258
|
// src/worker/terminal-manager.ts
|
|
10037
11259
|
var createTerminalManager = (deps) => {
|
|
10038
|
-
const { cfg, log, hasChild
|
|
11260
|
+
const { cfg, log, hasChild } = deps;
|
|
10039
11261
|
const terminals = /* @__PURE__ */ new Map();
|
|
10040
|
-
const
|
|
10041
|
-
const why = terminals.has(sessionId) ? "already open" : hasChild(sessionId) ? "headless child running" : !
|
|
11262
|
+
const open3 = (sessionId, agentSessionId, cwd) => {
|
|
11263
|
+
const why = terminals.has(sessionId) ? "already open" : hasChild(sessionId) ? "headless child running" : !statSync2(cwd, { throwIfNoEntry: false })?.isDirectory() ? `directory missing (${cwd})` : terminals.size >= MAX_TERMINALS ? `cap ${MAX_TERMINALS} reached` : null;
|
|
10042
11264
|
if (why) {
|
|
10043
11265
|
log(`terminal #${sessionId}: ${why} \u2014 refusing`);
|
|
10044
11266
|
return;
|
|
10045
11267
|
}
|
|
10046
|
-
|
|
10047
|
-
prepareWorktree(worktreePath);
|
|
10048
|
-
} catch (error) {
|
|
10049
|
-
log(`terminal #${sessionId}: worktree preparation failed: ${String(error)}`);
|
|
10050
|
-
return;
|
|
10051
|
-
}
|
|
10052
|
-
const command = cfg.agentKind === "codex" ? codexPtyCommand(agentSessionId) : claudePtyCommand(agentSessionId, hasSessionJsonl(agentSessionId));
|
|
11268
|
+
const command = ptyCommand(cfg.agentKind, agentSessionId);
|
|
10053
11269
|
const term = spawn4(command.file, command.args, {
|
|
10054
11270
|
name: "xterm-color",
|
|
10055
11271
|
cols: 80,
|
|
10056
11272
|
rows: 24,
|
|
10057
|
-
cwd
|
|
10058
|
-
env:
|
|
11273
|
+
cwd,
|
|
11274
|
+
env: workerEnv(cfg)
|
|
10059
11275
|
});
|
|
10060
11276
|
const ws = new import_websocket.default(serverTerminalWsUrl(cfg.server, sessionId), {
|
|
10061
11277
|
headers: { authorization: `Bearer ${cfg.apiToken}` }
|
|
@@ -10101,7 +11317,7 @@ var createTerminalManager = (deps) => {
|
|
|
10101
11317
|
}
|
|
10102
11318
|
};
|
|
10103
11319
|
return {
|
|
10104
|
-
open,
|
|
11320
|
+
open: open3,
|
|
10105
11321
|
close,
|
|
10106
11322
|
has: (sessionId) => terminals.has(sessionId),
|
|
10107
11323
|
killAll: () => {
|
|
@@ -10122,12 +11338,14 @@ var createTerminalManager = (deps) => {
|
|
|
10122
11338
|
// src/worker/daemon.ts
|
|
10123
11339
|
var ES_OPEN = 1;
|
|
10124
11340
|
var runWorkerDaemon = async (cfg, client, signal) => {
|
|
10125
|
-
const
|
|
11341
|
+
const cwd = process.cwd();
|
|
11342
|
+
ensureExcluded(cwd, PROJECT_CONFIG_NAME);
|
|
11343
|
+
syncBundledArtifactSkill(cwd);
|
|
10126
11344
|
const log = (m) => console.log(`[worker #${cfg.workerId} ${cfg.name}] ${m}`);
|
|
10127
11345
|
const supervisor = createSessionSupervisor({
|
|
10128
11346
|
client,
|
|
10129
11347
|
cfg,
|
|
10130
|
-
|
|
11348
|
+
cwd,
|
|
10131
11349
|
log,
|
|
10132
11350
|
hasTerminal: (id) => terminals.has(id),
|
|
10133
11351
|
closeTerminal: (id) => terminals.close(id)
|
|
@@ -10135,11 +11353,10 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10135
11353
|
const terminals = createTerminalManager({
|
|
10136
11354
|
cfg,
|
|
10137
11355
|
log,
|
|
10138
|
-
hasChild: (id) => supervisor.has(id)
|
|
10139
|
-
prepareWorktree: (worktreePath) => syncBundledArtifactSkill(repo, worktreePath)
|
|
11356
|
+
hasChild: (id) => supervisor.has(id)
|
|
10140
11357
|
});
|
|
11358
|
+
const files = createFileQueryRunner(cwd);
|
|
10141
11359
|
const services = createServiceSupervisor({
|
|
10142
|
-
getSession: (sessionId) => client.sessions.get(sessionId),
|
|
10143
11360
|
sendReport: (report) => client.services.report(report),
|
|
10144
11361
|
cfg,
|
|
10145
11362
|
log
|
|
@@ -10184,28 +11401,29 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10184
11401
|
else if (cmd.cmd === "session.reconcile")
|
|
10185
11402
|
void supervisor.reconcile(cmd.sessionId).catch((error) => log(`reconcile failed: ${String(error)}`));
|
|
10186
11403
|
else if (cmd.cmd === "session.delete")
|
|
10187
|
-
void
|
|
11404
|
+
void supervisor.remove(cmd.sessionId).catch((err) => log(`session cleanup failed: ${String(err)}`));
|
|
10188
11405
|
else if (cmd.cmd === "session.title")
|
|
10189
|
-
void supervisor.title(cmd.sessionId, cmd.agentSessionId, cmd.
|
|
11406
|
+
void supervisor.title(cmd.sessionId, cmd.agentSessionId, cmd.cwd).catch((err) => log(`title failed: ${String(err)}`));
|
|
10190
11407
|
else if (cmd.cmd === "session.terminal") {
|
|
10191
|
-
if (cmd.action === "open")
|
|
10192
|
-
terminals.open(cmd.sessionId, cmd.agentSessionId, cmd.worktreePath);
|
|
11408
|
+
if (cmd.action === "open") terminals.open(cmd.sessionId, cmd.agentSessionId, cmd.cwd);
|
|
10193
11409
|
else terminals.close(cmd.sessionId);
|
|
10194
11410
|
} else if (cmd.cmd === "service.run")
|
|
10195
|
-
void services.run(cmd.requestId, cmd.
|
|
11411
|
+
void services.run(cmd.requestId, cmd.cwd, cmd.name, cmd.argv, cmd.publicUrl, cmd.note).catch((err) => log(`service run failed: ${String(err)}`));
|
|
10196
11412
|
else if (cmd.cmd === "service.stop")
|
|
10197
11413
|
void services.stop(cmd.requestId, cmd.name).catch((err) => log(`service stop failed: ${String(err)}`));
|
|
11414
|
+
else if (cmd.cmd === "files.query")
|
|
11415
|
+
void files(cmd.query).then((reply) => client.files.report({ requestId: cmd.requestId, ...reply })).catch((err) => log(`files query failed: ${String(err)}`));
|
|
10198
11416
|
} catch (error) {
|
|
10199
11417
|
log(`invalid command: ${String(error)}`);
|
|
10200
11418
|
}
|
|
10201
11419
|
};
|
|
10202
11420
|
es.onerror = () => log("command stream error (eventsource will retry)");
|
|
10203
|
-
log(`listening for commands (server ${cfg.server},
|
|
11421
|
+
log(`listening for commands (server ${cfg.server}, cwd ${cwd})`);
|
|
10204
11422
|
liveness.start();
|
|
10205
|
-
await new Promise((
|
|
10206
|
-
stop =
|
|
10207
|
-
if (signal.aborted) return
|
|
10208
|
-
signal.addEventListener("abort", () =>
|
|
11423
|
+
await new Promise((resolve4) => {
|
|
11424
|
+
stop = resolve4;
|
|
11425
|
+
if (signal.aborted) return resolve4();
|
|
11426
|
+
signal.addEventListener("abort", () => resolve4(), { once: true });
|
|
10209
11427
|
});
|
|
10210
11428
|
es.close();
|
|
10211
11429
|
liveness.stop();
|
|
@@ -10217,22 +11435,93 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10217
11435
|
|
|
10218
11436
|
// src/worker/machine-id.ts
|
|
10219
11437
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10220
|
-
import { existsSync as
|
|
10221
|
-
import { homedir as
|
|
10222
|
-
import { dirname as
|
|
10223
|
-
var machineIdPath = (env = process.env) =>
|
|
11438
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
11439
|
+
import { homedir as homedir6 } from "node:os";
|
|
11440
|
+
import { dirname as dirname7, join as join23 } from "node:path";
|
|
11441
|
+
var machineIdPath = (env = process.env) => join23(env.XDG_DATA_HOME ?? join23(env.HOME ?? homedir6(), ".local/share"), "baton", "machine-id");
|
|
10224
11442
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
10225
|
-
if (
|
|
11443
|
+
if (existsSync9(path)) {
|
|
10226
11444
|
const s = readFileSync6(path, "utf8").trim();
|
|
10227
11445
|
if (s) return s;
|
|
10228
11446
|
}
|
|
10229
11447
|
const id = randomUUID3();
|
|
10230
|
-
mkdirSync5(
|
|
11448
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
10231
11449
|
writeFileSync5(path, `${id}
|
|
10232
11450
|
`, "utf8");
|
|
10233
11451
|
return id;
|
|
10234
11452
|
};
|
|
10235
11453
|
|
|
11454
|
+
// src/commands/worker/provision.ts
|
|
11455
|
+
var projectArg = {
|
|
11456
|
+
project: { type: "string", description: "project id (overrides .baton.json)" }
|
|
11457
|
+
};
|
|
11458
|
+
var positiveId = (raw) => {
|
|
11459
|
+
const id = Number(raw);
|
|
11460
|
+
if (!Number.isSafeInteger(id) || id <= 0) throw new Error("positive id required");
|
|
11461
|
+
return id;
|
|
11462
|
+
};
|
|
11463
|
+
var workerTemplatesCommand = defineCommand({
|
|
11464
|
+
meta: { name: "templates", description: "list worker creation templates for a project" },
|
|
11465
|
+
args: { ...projectArg, ...common },
|
|
11466
|
+
run: async ({ args }) => {
|
|
11467
|
+
const list = await clientFor(args).workerCreations.templates(positiveId(resolveProjectId(args)));
|
|
11468
|
+
console.log(renderList(list, fmtWorkerTemplate, Boolean(args.json)));
|
|
11469
|
+
}
|
|
11470
|
+
});
|
|
11471
|
+
var workerCreateCommand = defineCommand({
|
|
11472
|
+
meta: { name: "create", description: "request a worker from the mini template (asynchronous)" },
|
|
11473
|
+
args: {
|
|
11474
|
+
...projectArg,
|
|
11475
|
+
...common,
|
|
11476
|
+
template: {
|
|
11477
|
+
type: "string",
|
|
11478
|
+
required: true,
|
|
11479
|
+
description: "mini-glm-codeplan | mini-dashscope-codeplan | mini-codex"
|
|
11480
|
+
},
|
|
11481
|
+
name: { type: "string", required: true, description: "worker name" },
|
|
11482
|
+
agentKind: { type: "string", description: AGENT_KINDS.join(", ") }
|
|
11483
|
+
},
|
|
11484
|
+
run: async ({ args }) => {
|
|
11485
|
+
if (!isWorkerTemplateId(args.template)) throw new Error("invalid worker template");
|
|
11486
|
+
const agentKind = args.agentKind ?? workerTemplateAgentKinds(args.template)[0];
|
|
11487
|
+
if (!isAgentKind(agentKind)) throw new Error("invalid agentKind");
|
|
11488
|
+
if (!workerTemplateAgentKinds(args.template).includes(agentKind))
|
|
11489
|
+
throw new Error("agentKind is not supported by this template");
|
|
11490
|
+
const name = args.name.trim();
|
|
11491
|
+
if (!name) throw new Error("worker name required");
|
|
11492
|
+
const creation = await clientFor(args).workerCreations.create(
|
|
11493
|
+
positiveId(resolveProjectId(args)),
|
|
11494
|
+
{
|
|
11495
|
+
templateId: args.template,
|
|
11496
|
+
name,
|
|
11497
|
+
agentKind
|
|
11498
|
+
}
|
|
11499
|
+
);
|
|
11500
|
+
console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
|
|
11501
|
+
}
|
|
11502
|
+
});
|
|
11503
|
+
var workerCreationCommand = defineCommand({
|
|
11504
|
+
meta: { name: "creation", description: "inspect worker creation records" },
|
|
11505
|
+
subCommands: {
|
|
11506
|
+
ls: defineCommand({
|
|
11507
|
+
args: { ...projectArg, ...common },
|
|
11508
|
+
run: async ({ args }) => {
|
|
11509
|
+
const list = await clientFor(args).workerCreations.listByProject(
|
|
11510
|
+
positiveId(resolveProjectId(args))
|
|
11511
|
+
);
|
|
11512
|
+
console.log(renderList(list, fmtWorkerCreation, Boolean(args.json)));
|
|
11513
|
+
}
|
|
11514
|
+
}),
|
|
11515
|
+
get: defineCommand({
|
|
11516
|
+
args: { ...common, id: { type: "positional", required: true } },
|
|
11517
|
+
run: async ({ args }) => {
|
|
11518
|
+
const creation = await clientFor(args).workerCreations.get(positiveId(args.id));
|
|
11519
|
+
console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
|
|
11520
|
+
}
|
|
11521
|
+
})
|
|
11522
|
+
}
|
|
11523
|
+
});
|
|
11524
|
+
|
|
10236
11525
|
// src/commands/worker/index.ts
|
|
10237
11526
|
var resolveWorker = async (client, projectId, handle) => {
|
|
10238
11527
|
const asId = parseWorkerHandle(handle);
|
|
@@ -10246,13 +11535,8 @@ var resolveWorker = async (client, projectId, handle) => {
|
|
|
10246
11535
|
};
|
|
10247
11536
|
var parseAgentKind = (value) => {
|
|
10248
11537
|
const raw = typeof value === "string" && value.trim() ? value.trim() : "claude-code";
|
|
10249
|
-
if (raw
|
|
10250
|
-
throw new Error(`invalid agent kind "${raw}" (expected
|
|
10251
|
-
};
|
|
10252
|
-
var nonEmptyString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
10253
|
-
var resolveBaseBranch = (input) => {
|
|
10254
|
-
const branch = nonEmptyString(input.flag) ?? nonEmptyString(input.env) ?? nonEmptyString(input.saved) ?? repoHeadBranch(input.repo ?? process.cwd());
|
|
10255
|
-
return validateBaseBranch(branch);
|
|
11538
|
+
if (isAgentKind(raw)) return raw;
|
|
11539
|
+
throw new Error(`invalid agent kind "${raw}" (expected ${AGENT_KINDS.join(", ")})`);
|
|
10256
11540
|
};
|
|
10257
11541
|
var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
10258
11542
|
const out = await client.workers.register({
|
|
@@ -10267,7 +11551,6 @@ var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
|
10267
11551
|
...existing,
|
|
10268
11552
|
server: input.server,
|
|
10269
11553
|
project: input.projectId,
|
|
10270
|
-
baseBranch: input.baseBranch,
|
|
10271
11554
|
worker: {
|
|
10272
11555
|
id: out.worker.id,
|
|
10273
11556
|
name: out.worker.name,
|
|
@@ -10281,6 +11564,9 @@ var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
|
10281
11564
|
var worker = defineCommand({
|
|
10282
11565
|
meta: { name: "worker", description: "register / inspect / close workers for a project" },
|
|
10283
11566
|
subCommands: {
|
|
11567
|
+
create: workerCreateCommand,
|
|
11568
|
+
creation: workerCreationCommand,
|
|
11569
|
+
templates: workerTemplatesCommand,
|
|
10284
11570
|
register: defineCommand({
|
|
10285
11571
|
meta: {
|
|
10286
11572
|
name: "register",
|
|
@@ -10295,11 +11581,7 @@ var worker = defineCommand({
|
|
|
10295
11581
|
},
|
|
10296
11582
|
agentKind: {
|
|
10297
11583
|
type: "string",
|
|
10298
|
-
description:
|
|
10299
|
-
},
|
|
10300
|
-
baseBranch: {
|
|
10301
|
-
type: "string",
|
|
10302
|
-
description: "branch new session worktrees start from (default: current branch, then main)"
|
|
11584
|
+
description: `agent engine for new sessions: ${AGENT_KINDS.join(", ")}`
|
|
10303
11585
|
},
|
|
10304
11586
|
...common
|
|
10305
11587
|
},
|
|
@@ -10315,14 +11597,9 @@ var worker = defineCommand({
|
|
|
10315
11597
|
const fileToken = savedConfig?.worker?.apiToken;
|
|
10316
11598
|
const auth = resolveAuth(process.env, fileToken);
|
|
10317
11599
|
const c = createClient(server, auth && auth !== "cookie" ? auth : void 0);
|
|
10318
|
-
const
|
|
10319
|
-
flag: args.baseBranch,
|
|
10320
|
-
env: process.env.BATON_BASE_BRANCH,
|
|
10321
|
-
saved: savedConfig?.baseBranch
|
|
10322
|
-
});
|
|
10323
|
-
const hostname = osHostname();
|
|
11600
|
+
const hostname2 = osHostname();
|
|
10324
11601
|
const machineId = readOrCreateMachineId();
|
|
10325
|
-
const name = args.name ??
|
|
11602
|
+
const name = args.name ?? hostname2;
|
|
10326
11603
|
const agentKind = parseAgentKind(args.agentKind ?? process.env.BATON_AGENT_KIND);
|
|
10327
11604
|
const { out, configPath } = await registerWorker(
|
|
10328
11605
|
c,
|
|
@@ -10330,20 +11607,18 @@ var worker = defineCommand({
|
|
|
10330
11607
|
projectId: resolveProjectId(args),
|
|
10331
11608
|
name,
|
|
10332
11609
|
server,
|
|
10333
|
-
hostname,
|
|
11610
|
+
hostname: hostname2,
|
|
10334
11611
|
machineId,
|
|
10335
|
-
agentKind
|
|
10336
|
-
baseBranch
|
|
11612
|
+
agentKind
|
|
10337
11613
|
},
|
|
10338
11614
|
cfgPath
|
|
10339
11615
|
);
|
|
10340
11616
|
if (args.json) {
|
|
10341
|
-
console.log(toJson({ ...out,
|
|
11617
|
+
console.log(toJson({ ...out, configPath, machineIdPath: machineIdPath() }));
|
|
10342
11618
|
return;
|
|
10343
11619
|
}
|
|
10344
11620
|
console.log(`worker #${out.worker.id} (${out.worker.name}) \u2014 ${out.outcome}`);
|
|
10345
11621
|
console.log(` agentKind: ${out.worker.agentKind}`);
|
|
10346
|
-
console.log(` baseBranch: ${baseBranch}`);
|
|
10347
11622
|
console.log(` hostname: ${out.worker.hostname}`);
|
|
10348
11623
|
console.log(` machineId: ${out.worker.machineId}`);
|
|
10349
11624
|
console.log(` machineId file: ${machineIdPath()}`);
|
|
@@ -10452,14 +11727,12 @@ var worker = defineCommand({
|
|
|
10452
11727
|
console.log("(no worker registered for this project \u2014 run `baton worker register`)");
|
|
10453
11728
|
return;
|
|
10454
11729
|
}
|
|
10455
|
-
const baseBranch = resolveBaseBranch({ saved: cfg.baseBranch });
|
|
10456
11730
|
if (args.json) {
|
|
10457
11731
|
console.log(
|
|
10458
11732
|
toJson({
|
|
10459
11733
|
...worker2,
|
|
10460
11734
|
server: cfg.server,
|
|
10461
|
-
projectId: cfg.project
|
|
10462
|
-
baseBranch
|
|
11735
|
+
projectId: cfg.project
|
|
10463
11736
|
})
|
|
10464
11737
|
);
|
|
10465
11738
|
return;
|
|
@@ -10467,7 +11740,6 @@ var worker = defineCommand({
|
|
|
10467
11740
|
console.log(`worker #${worker2.id} (${worker2.name})`);
|
|
10468
11741
|
console.log(` machineId: ${worker2.machineId}`);
|
|
10469
11742
|
console.log(` agentKind: ${worker2.agentKind ?? "claude-code"}`);
|
|
10470
|
-
console.log(` baseBranch: ${baseBranch}`);
|
|
10471
11743
|
console.log(` server: ${cfg?.server}`);
|
|
10472
11744
|
}
|
|
10473
11745
|
})
|
|
@@ -10541,10 +11813,12 @@ var main = defineCommand({
|
|
|
10541
11813
|
subCommands: {
|
|
10542
11814
|
init,
|
|
10543
11815
|
worker,
|
|
11816
|
+
provisioner,
|
|
10544
11817
|
session,
|
|
10545
11818
|
service,
|
|
10546
11819
|
artifact,
|
|
10547
11820
|
loop,
|
|
11821
|
+
notify,
|
|
10548
11822
|
relay,
|
|
10549
11823
|
channel,
|
|
10550
11824
|
workspace,
|