@lesscap/baton-cli 0.8.4 → 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/dist/baton.mjs +1094 -202
- package/package.json +18 -18
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") {
|
|
@@ -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;
|
|
@@ -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
|
|
@@ -5488,6 +5492,11 @@ var primeLogin = (baseUrl) => {
|
|
|
5488
5492
|
);
|
|
5489
5493
|
};
|
|
5490
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
|
+
|
|
5491
5500
|
// src/client/loops.ts
|
|
5492
5501
|
var loopsClient = (baseUrl) => {
|
|
5493
5502
|
const u = (p) => `${baseUrl}${p}`;
|
|
@@ -5667,7 +5676,8 @@ var sessionsClient = (baseUrl) => {
|
|
|
5667
5676
|
},
|
|
5668
5677
|
destroy: async (id) => {
|
|
5669
5678
|
await request(u(`/sessions/${id}`), { method: "DELETE" });
|
|
5670
|
-
}
|
|
5679
|
+
},
|
|
5680
|
+
notify: (id, text) => request(u(`/sessions/${id}/notify`), { method: "POST", body: { text } })
|
|
5671
5681
|
};
|
|
5672
5682
|
};
|
|
5673
5683
|
|
|
@@ -5743,6 +5753,7 @@ var clientFromBase = (baseUrl) => ({
|
|
|
5743
5753
|
tasks: taskClient(baseUrl),
|
|
5744
5754
|
sessions: sessionsClient(baseUrl),
|
|
5745
5755
|
services: servicesClient(baseUrl),
|
|
5756
|
+
files: filesClient(baseUrl),
|
|
5746
5757
|
loops: loopsClient(baseUrl),
|
|
5747
5758
|
workers: workersClient(baseUrl)
|
|
5748
5759
|
});
|
|
@@ -6262,11 +6273,11 @@ var EventSource = class extends EventTarget {
|
|
|
6262
6273
|
return;
|
|
6263
6274
|
}
|
|
6264
6275
|
const decoder = new TextDecoder(), reader = body.getReader();
|
|
6265
|
-
let
|
|
6276
|
+
let open3 = true;
|
|
6266
6277
|
do {
|
|
6267
6278
|
const { done, value } = await reader.read();
|
|
6268
|
-
value && __privateGet(this, _parser).feed(decoder.decode(value, { stream: !done })), done && (
|
|
6269
|
-
} 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);
|
|
6270
6281
|
}), __privateAdd(this, _onFetchError, (err) => {
|
|
6271
6282
|
__privateSet(this, _controller, void 0), !(err.name === "AbortError" || err.type === "aborted") && __privateMethod(this, _EventSource_instances, scheduleReconnect_fn).call(this, flattenError(err));
|
|
6272
6283
|
}), __privateAdd(this, _onEvent, (event) => {
|
|
@@ -6721,11 +6732,10 @@ import { tmpdir } from "node:os";
|
|
|
6721
6732
|
import { join as join2 } from "node:path";
|
|
6722
6733
|
|
|
6723
6734
|
// ../shared/src/domain/attachment.ts
|
|
6724
|
-
var isImageAttachment = (att) => att.contentType.startsWith("image/");
|
|
6725
6735
|
var labelAttachments = (attachments) => {
|
|
6726
6736
|
const counters = { image: 0, file: 0 };
|
|
6727
6737
|
return attachments.map((att) => {
|
|
6728
|
-
const kind =
|
|
6738
|
+
const kind = att.contentType.startsWith("image/") ? "image" : "file";
|
|
6729
6739
|
counters[kind] += 1;
|
|
6730
6740
|
return `${kind}-${counters[kind]}`;
|
|
6731
6741
|
});
|
|
@@ -6734,6 +6744,77 @@ var labelAttachments = (attachments) => {
|
|
|
6734
6744
|
// ../shared/src/domain/channel.ts
|
|
6735
6745
|
var isMessageFor = (msg, name) => !msg.to || msg.to.length === 0 || msg.to.includes(name);
|
|
6736
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
|
+
|
|
6737
6818
|
// ../shared/src/domain/service.ts
|
|
6738
6819
|
var SERVICE_PUBLIC_URL_MAX_LENGTH = 2048;
|
|
6739
6820
|
var SERVICE_NOTE_MAX_LENGTH = 500;
|
|
@@ -6748,6 +6829,12 @@ var isServicePublicUrl = (value) => {
|
|
|
6748
6829
|
};
|
|
6749
6830
|
var isServiceNote = (value) => value.length > 0 && value.length <= SERVICE_NOTE_MAX_LENGTH;
|
|
6750
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
|
+
|
|
6751
6838
|
// ../shared/src/domain/session-event.ts
|
|
6752
6839
|
var agentMessageText = (payload) => {
|
|
6753
6840
|
if (typeof payload !== "object" || payload === null) return null;
|
|
@@ -6761,6 +6848,15 @@ var agentMessageText = (payload) => {
|
|
|
6761
6848
|
return trimmed ? { id, text: trimmed } : null;
|
|
6762
6849
|
};
|
|
6763
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
|
+
|
|
6764
6860
|
// src/commands/channel/listen.ts
|
|
6765
6861
|
var PREVIEW = 280;
|
|
6766
6862
|
var describe = (e) => {
|
|
@@ -7221,6 +7317,34 @@ var loop = defineCommand({
|
|
|
7221
7317
|
}
|
|
7222
7318
|
});
|
|
7223
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
|
+
|
|
7224
7348
|
// src/commands/project.ts
|
|
7225
7349
|
var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
|
|
7226
7350
|
var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
|
|
@@ -7316,6 +7440,37 @@ var absolutePath = (value, field) => {
|
|
|
7316
7440
|
throw new Error(`${field} must be an absolute path`);
|
|
7317
7441
|
return value;
|
|
7318
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
|
+
};
|
|
7319
7474
|
var commandEnvironment = (config) => ({
|
|
7320
7475
|
...process.env,
|
|
7321
7476
|
PATH: config.workerEnv.PATH ?? process.env.PATH ?? "/usr/bin:/bin"
|
|
@@ -7345,21 +7500,14 @@ var loadProvisionerConfig = async (path) => {
|
|
|
7345
7500
|
throw new Error("server must be an HTTP(S) URL without credentials, query, or fragment");
|
|
7346
7501
|
if (!raw.token.trim() || /[\r\n]/.test(raw.token))
|
|
7347
7502
|
throw new Error("valid provisioner token required");
|
|
7348
|
-
if (!object(raw.workerEnv)) throw new Error("workerEnv must be an object");
|
|
7349
|
-
const workerEnv2 = Object.fromEntries(
|
|
7350
|
-
Object.entries(raw.workerEnv).map(([key, value]) => {
|
|
7351
|
-
if (forbiddenEnv.has(key) || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== "string" || value.includes("\0"))
|
|
7352
|
-
throw new Error(`invalid worker environment key: ${key}`);
|
|
7353
|
-
return [key, value];
|
|
7354
|
-
})
|
|
7355
|
-
);
|
|
7356
7503
|
const config = {
|
|
7357
7504
|
server: server.toString().replace(/\/$/, ""),
|
|
7358
7505
|
token: raw.token.trim(),
|
|
7359
7506
|
projectsRoot: absolutePath(raw.projectsRoot, "projectsRoot"),
|
|
7360
7507
|
cliPath: absolutePath(raw.cliPath, "cliPath"),
|
|
7361
7508
|
nodePath: absolutePath(raw.nodePath ?? process.execPath, "nodePath"),
|
|
7362
|
-
workerEnv:
|
|
7509
|
+
workerEnv: environment(raw.workerEnv),
|
|
7510
|
+
templates: templates(raw.templates)
|
|
7363
7511
|
};
|
|
7364
7512
|
await access(config.cliPath, constants.R_OK);
|
|
7365
7513
|
await access(config.nodePath ?? process.execPath, constants.X_OK);
|
|
@@ -7399,9 +7547,9 @@ var createProvisionerClient = (config) => {
|
|
|
7399
7547
|
|
|
7400
7548
|
// src/provisioner/executor.ts
|
|
7401
7549
|
import { execFile as execFile3 } from "node:child_process";
|
|
7402
|
-
import { lstat, mkdir as
|
|
7550
|
+
import { lstat, mkdir as mkdir3, realpath, writeFile as writeFile2 } from "node:fs/promises";
|
|
7403
7551
|
import { hostname } from "node:os";
|
|
7404
|
-
import { join as
|
|
7552
|
+
import { join as join5 } from "node:path";
|
|
7405
7553
|
import { setTimeout as setTimeout2 } from "node:timers/promises";
|
|
7406
7554
|
import { promisify as promisify3 } from "node:util";
|
|
7407
7555
|
|
|
@@ -7472,15 +7620,15 @@ var startAgent = async (config, agent, signal) => {
|
|
|
7472
7620
|
throw new Error(`launchd start failed; inspect ${plist} and ${agent.log}`);
|
|
7473
7621
|
}
|
|
7474
7622
|
};
|
|
7475
|
-
var launchWorkerAgent = async (config, creation, repoPath, signal) => startAgent(
|
|
7623
|
+
var launchWorkerAgent = async (config, creation, repoPath, workerEnv2, signal) => startAgent(
|
|
7476
7624
|
config,
|
|
7477
7625
|
{
|
|
7478
7626
|
label: `dev.fmap.baton-worker-creation-${creation.id}`,
|
|
7479
7627
|
args: [config.nodePath ?? process.execPath, config.cliPath, "worker", "run"],
|
|
7480
7628
|
cwd: repoPath,
|
|
7481
7629
|
env: {
|
|
7482
|
-
PATH:
|
|
7483
|
-
...
|
|
7630
|
+
PATH: workerEnv2.PATH ?? process.env.PATH ?? "/usr/bin:/bin",
|
|
7631
|
+
...workerEnv2,
|
|
7484
7632
|
HOME: homedir(),
|
|
7485
7633
|
XDG_DATA_HOME: workerStatePath(config, creation)
|
|
7486
7634
|
},
|
|
@@ -7503,10 +7651,34 @@ var installProvisionerAgent = async (config, configPath) => startAgent(config, {
|
|
|
7503
7651
|
log: join3(homedir(), "Library/Logs/baton-provisioner.log")
|
|
7504
7652
|
});
|
|
7505
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
|
+
|
|
7506
7678
|
// src/provisioner/executor.ts
|
|
7507
7679
|
var directory = async (path) => {
|
|
7508
7680
|
try {
|
|
7509
|
-
await
|
|
7681
|
+
await mkdir3(path, { mode: 448 });
|
|
7510
7682
|
} catch (error) {
|
|
7511
7683
|
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
7512
7684
|
}
|
|
@@ -7514,7 +7686,7 @@ var directory = async (path) => {
|
|
|
7514
7686
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`unsafe directory: ${path}`);
|
|
7515
7687
|
};
|
|
7516
7688
|
var provisionWorker = async (config, creation, client, signal) => {
|
|
7517
|
-
const repoPath =
|
|
7689
|
+
const repoPath = join5(
|
|
7518
7690
|
config.projectsRoot,
|
|
7519
7691
|
`project-${creation.projectId}`,
|
|
7520
7692
|
`creation-${creation.id}`
|
|
@@ -7522,14 +7694,14 @@ var provisionWorker = async (config, creation, client, signal) => {
|
|
|
7522
7694
|
const progress = (step) => client.report(creation.id, { status: "running", step, repoPath }, signal);
|
|
7523
7695
|
await progress("directory");
|
|
7524
7696
|
signal.throwIfAborted();
|
|
7525
|
-
await
|
|
7697
|
+
await mkdir3(config.projectsRoot, { recursive: true, mode: 448 });
|
|
7526
7698
|
const root = await realpath(config.projectsRoot);
|
|
7527
|
-
await directory(
|
|
7528
|
-
await
|
|
7699
|
+
await directory(join5(root, `project-${creation.projectId}`));
|
|
7700
|
+
await mkdir3(join5(root, `project-${creation.projectId}`, `creation-${creation.id}`), {
|
|
7529
7701
|
mode: 448
|
|
7530
7702
|
});
|
|
7531
7703
|
await progress("git");
|
|
7532
|
-
const
|
|
7704
|
+
const git2 = async (args) => {
|
|
7533
7705
|
try {
|
|
7534
7706
|
await promisify3(execFile3)("git", ["-C", repoPath, ...args], {
|
|
7535
7707
|
env: commandEnvironment(config),
|
|
@@ -7541,9 +7713,9 @@ var provisionWorker = async (config, creation, client, signal) => {
|
|
|
7541
7713
|
throw new Error("Git repository initialization failed", { cause: error });
|
|
7542
7714
|
}
|
|
7543
7715
|
};
|
|
7544
|
-
await
|
|
7545
|
-
await
|
|
7546
|
-
await
|
|
7716
|
+
await git2(["init", "-b", "main"]);
|
|
7717
|
+
await writeFile2(join5(repoPath, ".git/info/exclude"), "\n.baton.json\n", { flag: "a" });
|
|
7718
|
+
await git2([
|
|
7547
7719
|
"-c",
|
|
7548
7720
|
"user.name=Baton",
|
|
7549
7721
|
"-c",
|
|
@@ -7556,11 +7728,11 @@ var provisionWorker = async (config, creation, client, signal) => {
|
|
|
7556
7728
|
await progress("register");
|
|
7557
7729
|
const result = await client.register(creation.id, hostname(), signal);
|
|
7558
7730
|
const state = workerStatePath(config, creation);
|
|
7559
|
-
await directory(
|
|
7560
|
-
await directory(
|
|
7561
|
-
await
|
|
7562
|
-
await
|
|
7563
|
-
await
|
|
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}
|
|
7564
7736
|
`, {
|
|
7565
7737
|
flag: "wx",
|
|
7566
7738
|
mode: 384
|
|
@@ -7574,14 +7746,15 @@ var provisionWorker = async (config, creation, client, signal) => {
|
|
|
7574
7746
|
machineId: result.worker.machineId,
|
|
7575
7747
|
apiToken: result.apiToken
|
|
7576
7748
|
});
|
|
7577
|
-
await
|
|
7749
|
+
await writeFile2(join5(repoPath, ".baton.json"), `${JSON.stringify(cfg, null, 2)}
|
|
7578
7750
|
`, {
|
|
7579
7751
|
flag: "wx",
|
|
7580
7752
|
mode: 384
|
|
7581
7753
|
});
|
|
7582
7754
|
await progress("start");
|
|
7583
7755
|
signal.throwIfAborted();
|
|
7584
|
-
await
|
|
7756
|
+
const workerEnv2 = await workerTemplateEnvironment(config, creation, state);
|
|
7757
|
+
await launchWorkerAgent(config, creation, repoPath, workerEnv2, signal);
|
|
7585
7758
|
await progress("connect");
|
|
7586
7759
|
const deadline = Date.now() + 6e4;
|
|
7587
7760
|
while (Date.now() < deadline) {
|
|
@@ -7598,7 +7771,11 @@ var provisionWorker = async (config, creation, client, signal) => {
|
|
|
7598
7771
|
// src/provisioner/daemon.ts
|
|
7599
7772
|
var taskError = (error, config) => {
|
|
7600
7773
|
const message = error instanceof Error ? error.message : "unknown creation error";
|
|
7601
|
-
return [
|
|
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);
|
|
7602
7779
|
};
|
|
7603
7780
|
var processCreation = async (id, config, client, signal) => {
|
|
7604
7781
|
let creation;
|
|
@@ -7754,7 +7931,7 @@ var provisioner = defineCommand({
|
|
|
7754
7931
|
// src/commands/relay.ts
|
|
7755
7932
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7756
7933
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
7757
|
-
import { join as
|
|
7934
|
+
import { join as join6 } from "node:path";
|
|
7758
7935
|
|
|
7759
7936
|
// src/client/relay.ts
|
|
7760
7937
|
var relayClient = (baseUrl) => {
|
|
@@ -7882,11 +8059,11 @@ var listenCommand2 = defineCommand({
|
|
|
7882
8059
|
process.stdout.write(`${JSON.stringify(o)}
|
|
7883
8060
|
`);
|
|
7884
8061
|
};
|
|
7885
|
-
const spillDir =
|
|
8062
|
+
const spillDir = join6(tmpdir2(), "hotline", args.channel);
|
|
7886
8063
|
mkdirSync2(spillDir, { recursive: true });
|
|
7887
8064
|
const surface = (m) => {
|
|
7888
8065
|
const text = typeof m.text === "string" ? m.text : "";
|
|
7889
|
-
const file =
|
|
8066
|
+
const file = join6(spillDir, `${m.seq}.txt`);
|
|
7890
8067
|
writeFileSync4(file, text);
|
|
7891
8068
|
const truncated = text.length > PREVIEW2;
|
|
7892
8069
|
emit({
|
|
@@ -7900,7 +8077,7 @@ var listenCommand2 = defineCommand({
|
|
|
7900
8077
|
file
|
|
7901
8078
|
});
|
|
7902
8079
|
};
|
|
7903
|
-
const
|
|
8080
|
+
const describe3 = (e) => {
|
|
7904
8081
|
const ev = e;
|
|
7905
8082
|
return ev?.code ? `${ev.code}${ev.message ? ` ${ev.message}` : ""}` : String(e);
|
|
7906
8083
|
};
|
|
@@ -7910,7 +8087,7 @@ var listenCommand2 = defineCommand({
|
|
|
7910
8087
|
onMessage: (m) => {
|
|
7911
8088
|
if (m.from !== me) surface(m);
|
|
7912
8089
|
},
|
|
7913
|
-
onError: (e) => emit({ type: "relay.error", error:
|
|
8090
|
+
onError: (e) => emit({ type: "relay.error", error: describe3(e) })
|
|
7914
8091
|
});
|
|
7915
8092
|
emit({ type: "relay.listening", channel: args.channel, from: me, url });
|
|
7916
8093
|
await new Promise((resolve4) => {
|
|
@@ -8361,12 +8538,12 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
8361
8538
|
// src/session/runner/transcript.ts
|
|
8362
8539
|
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
8363
8540
|
import { homedir as homedir2 } from "node:os";
|
|
8364
|
-
import { join as
|
|
8541
|
+
import { join as join7 } from "node:path";
|
|
8365
8542
|
var findTranscriptPath = (agentSessionId) => {
|
|
8366
|
-
const root =
|
|
8543
|
+
const root = join7(homedir2(), ".claude", "projects");
|
|
8367
8544
|
try {
|
|
8368
8545
|
for (const dir of readdirSync(root)) {
|
|
8369
|
-
const candidate =
|
|
8546
|
+
const candidate = join7(root, dir, `${agentSessionId}.jsonl`);
|
|
8370
8547
|
try {
|
|
8371
8548
|
if (statSync(candidate).isFile()) return candidate;
|
|
8372
8549
|
} catch {
|
|
@@ -8444,13 +8621,13 @@ var readFirstExchange = (agentSessionId) => {
|
|
|
8444
8621
|
};
|
|
8445
8622
|
|
|
8446
8623
|
// src/session/runner/turn.ts
|
|
8447
|
-
import { mkdir as
|
|
8448
|
-
import { join as
|
|
8624
|
+
import { mkdir as mkdir5, writeFile as writeFile3 } from "node:fs/promises";
|
|
8625
|
+
import { join as join12 } from "node:path";
|
|
8449
8626
|
|
|
8450
8627
|
// src/session/runner/attachments.ts
|
|
8451
8628
|
import { createWriteStream } from "node:fs";
|
|
8452
|
-
import { mkdir as
|
|
8453
|
-
import { join as
|
|
8629
|
+
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
8630
|
+
import { join as join8 } from "node:path";
|
|
8454
8631
|
import { Readable } from "node:stream";
|
|
8455
8632
|
import { pipeline } from "node:stream/promises";
|
|
8456
8633
|
var dedupe = (name, taken) => {
|
|
@@ -8473,15 +8650,15 @@ var dedupe = (name, taken) => {
|
|
|
8473
8650
|
var materializeAttachments = async (opts) => {
|
|
8474
8651
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
8475
8652
|
const dir = opts.directory;
|
|
8476
|
-
await
|
|
8653
|
+
await mkdir4(dir, { recursive: true });
|
|
8477
8654
|
const taken = /* @__PURE__ */ new Set();
|
|
8478
8655
|
const paths = [];
|
|
8479
8656
|
for (const att of opts.attachments) {
|
|
8480
8657
|
const name = dedupe(att.filename, taken);
|
|
8481
8658
|
const res = await fetchImpl(`${opts.serverBase}${att.url}`);
|
|
8482
8659
|
if (!res.ok || !res.body) throw new Error(`attachment ${att.id} \u2192 ${res.status}`);
|
|
8483
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(
|
|
8484
|
-
paths.push(
|
|
8660
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(join8(dir, name)));
|
|
8661
|
+
paths.push(join8(dir, name));
|
|
8485
8662
|
}
|
|
8486
8663
|
return paths;
|
|
8487
8664
|
};
|
|
@@ -8497,7 +8674,7 @@ ${text}` : header;
|
|
|
8497
8674
|
};
|
|
8498
8675
|
|
|
8499
8676
|
// src/session/runner/codex.ts
|
|
8500
|
-
import { join as
|
|
8677
|
+
import { join as join9 } from "node:path";
|
|
8501
8678
|
import { Codex } from "@openai/codex-sdk";
|
|
8502
8679
|
|
|
8503
8680
|
// src/session/runner/sdk-env.ts
|
|
@@ -8510,6 +8687,7 @@ var additionalDirs = (required = []) => {
|
|
|
8510
8687
|
const dirs = [...required, ...(raw ?? "").split(":")].map((s) => s.trim()).filter((s) => s !== "" && isAbsolute2(s) && existsSync3(s));
|
|
8511
8688
|
return dirs.length > 0 ? [...new Set(dirs)] : void 0;
|
|
8512
8689
|
};
|
|
8690
|
+
var PLAN_MODE_PREFIX = "Plan mode: inspect and propose a plan only. Do not edit files or run mutating commands.\n\n";
|
|
8513
8691
|
var truthy = (value) => {
|
|
8514
8692
|
if (value === void 0) return void 0;
|
|
8515
8693
|
const normalized = value.trim().toLowerCase();
|
|
@@ -8530,17 +8708,8 @@ var codexApprovalPolicy = () => {
|
|
|
8530
8708
|
return raw;
|
|
8531
8709
|
return "never";
|
|
8532
8710
|
};
|
|
8533
|
-
var claudeEffort = (raw) =>
|
|
8534
|
-
|
|
8535
|
-
if (raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "max")
|
|
8536
|
-
return raw;
|
|
8537
|
-
return void 0;
|
|
8538
|
-
};
|
|
8539
|
-
var codexEffort = (raw) => {
|
|
8540
|
-
if (raw === "minimal" || raw === "low" || raw === "medium" || raw === "high" || raw === "xhigh" || raw === "max")
|
|
8541
|
-
return raw;
|
|
8542
|
-
return void 0;
|
|
8543
|
-
};
|
|
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;
|
|
8544
8713
|
|
|
8545
8714
|
// src/session/runner/codex.ts
|
|
8546
8715
|
var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -8658,17 +8827,16 @@ var toAgentEvent = (raw) => {
|
|
|
8658
8827
|
return { type: "raw", raw };
|
|
8659
8828
|
};
|
|
8660
8829
|
var withPlanPrefix = (input) => {
|
|
8661
|
-
|
|
8662
|
-
if (typeof input === "string") return `${prefix}${input}`;
|
|
8830
|
+
if (typeof input === "string") return `${PLAN_MODE_PREFIX}${input}`;
|
|
8663
8831
|
return input.map(
|
|
8664
|
-
(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
|
|
8665
8833
|
);
|
|
8666
8834
|
};
|
|
8667
8835
|
async function* startCodexEvents(config, input, worker2, opts) {
|
|
8668
8836
|
const env = compactEnv(buildSdkEnv(opts.envOverlay));
|
|
8669
8837
|
const codex = new Codex(env ? { env } : void 0);
|
|
8670
8838
|
const resuming = !config.agentSessionId.startsWith("pending:");
|
|
8671
|
-
const addDirs = additionalDirs([
|
|
8839
|
+
const addDirs = additionalDirs([join9(config.runtimeDir, "attachments")]);
|
|
8672
8840
|
const sandboxMode = codexSandboxMode(opts.planMode ?? false);
|
|
8673
8841
|
const approvalPolicy = codexApprovalPolicy();
|
|
8674
8842
|
const networkAccessEnabled = codexNetworkAccess();
|
|
@@ -8706,12 +8874,234 @@ async function* startCodexEvents(config, input, worker2, opts) {
|
|
|
8706
8874
|
}
|
|
8707
8875
|
}
|
|
8708
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
|
+
|
|
8709
9099
|
// src/session/runner/query.ts
|
|
8710
|
-
import { join as
|
|
9100
|
+
import { join as join11 } from "node:path";
|
|
8711
9101
|
var startQuery = (config, text, resuming, queryFn, abortController, log, { envOverlay, planMode = false, model, effort } = {}) => {
|
|
8712
9102
|
const env = buildSdkEnv(envOverlay);
|
|
8713
9103
|
const exe = claudeExecutable();
|
|
8714
|
-
const addDirs = additionalDirs([
|
|
9104
|
+
const addDirs = additionalDirs([join11(config.runtimeDir, "attachments")]);
|
|
8715
9105
|
const level = claudeEffort(effort);
|
|
8716
9106
|
if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
|
|
8717
9107
|
if (planMode) log("[plan] read-only planning turn");
|
|
@@ -8759,34 +9149,34 @@ var streamAgentEvents = async (events, worker2) => {
|
|
|
8759
9149
|
return result;
|
|
8760
9150
|
};
|
|
8761
9151
|
var streamClaudeSdkEvents = (messages, worker2) => streamAgentEvents(claudeToAgentEvents(messages), worker2);
|
|
8762
|
-
var
|
|
8763
|
-
var
|
|
9152
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
9153
|
+
var str3 = (v) => typeof v === "string" ? v : void 0;
|
|
8764
9154
|
var messageContent = (payload) => {
|
|
8765
9155
|
const msg = payload.message;
|
|
8766
|
-
if (!
|
|
8767
|
-
return msg.content.filter(
|
|
9156
|
+
if (!isRecord4(msg) || !Array.isArray(msg.content)) return [];
|
|
9157
|
+
return msg.content.filter(isRecord4);
|
|
8768
9158
|
};
|
|
8769
9159
|
var textFromToolResult = (raw) => {
|
|
8770
9160
|
const c = raw.content;
|
|
8771
9161
|
if (typeof c === "string") return c;
|
|
8772
9162
|
if (Array.isArray(c)) {
|
|
8773
|
-
return c.map((b) =>
|
|
9163
|
+
return c.map((b) => isRecord4(b) && typeof b.text === "string" ? b.text : JSON.stringify(b)).join("\n");
|
|
8774
9164
|
}
|
|
8775
9165
|
return JSON.stringify(c ?? "");
|
|
8776
9166
|
};
|
|
8777
9167
|
var commandOf = (input) => {
|
|
8778
|
-
if (!
|
|
8779
|
-
return
|
|
9168
|
+
if (!isRecord4(input)) return "";
|
|
9169
|
+
return str3(input.command) ?? str3(input.cmd) ?? "";
|
|
8780
9170
|
};
|
|
8781
9171
|
var pathOf = (input) => {
|
|
8782
|
-
if (!
|
|
8783
|
-
return
|
|
9172
|
+
if (!isRecord4(input)) return void 0;
|
|
9173
|
+
return str3(input.file_path) ?? str3(input.path);
|
|
8784
9174
|
};
|
|
8785
9175
|
var fileKindOf = (name) => name === "Delete" ? "delete" : "update";
|
|
8786
9176
|
var todoItems2 = (input) => {
|
|
8787
|
-
if (!
|
|
8788
|
-
return input.todos.filter(
|
|
8789
|
-
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) ?? "",
|
|
8790
9180
|
completed: todo.status === "completed" || todo.completed === true
|
|
8791
9181
|
}));
|
|
8792
9182
|
};
|
|
@@ -8795,8 +9185,8 @@ var mcpParts = (name) => {
|
|
|
8795
9185
|
return match ? { server: match[1] ?? "", tool: match[2] ?? "" } : null;
|
|
8796
9186
|
};
|
|
8797
9187
|
var startedItemFromToolUse = (block) => {
|
|
8798
|
-
const id =
|
|
8799
|
-
const name =
|
|
9188
|
+
const id = str3(block.id) ?? `tool-${Math.random().toString(36).slice(2)}`;
|
|
9189
|
+
const name = str3(block.name) ?? "tool";
|
|
8800
9190
|
const input = block.input ?? null;
|
|
8801
9191
|
if (name === "Bash") {
|
|
8802
9192
|
return {
|
|
@@ -8832,7 +9222,7 @@ var startedItemFromToolUse = (block) => {
|
|
|
8832
9222
|
type: "web_search",
|
|
8833
9223
|
id,
|
|
8834
9224
|
status: "in_progress",
|
|
8835
|
-
query:
|
|
9225
|
+
query: isRecord4(input) ? str3(input.query) ?? "" : ""
|
|
8836
9226
|
};
|
|
8837
9227
|
}
|
|
8838
9228
|
if (name === "TodoWrite") {
|
|
@@ -8851,9 +9241,9 @@ var completeItem = (item, output, isError) => {
|
|
|
8851
9241
|
};
|
|
8852
9242
|
var resultEvent = (message) => {
|
|
8853
9243
|
const rec = message;
|
|
8854
|
-
const subtype =
|
|
9244
|
+
const subtype = str3(rec.subtype) ?? "unknown";
|
|
8855
9245
|
const isError = rec.is_error === true || subtype !== "success";
|
|
8856
|
-
const
|
|
9246
|
+
const resultText2 = str3(rec.result) ?? "";
|
|
8857
9247
|
const usage = {
|
|
8858
9248
|
numTurns: typeof rec.num_turns === "number" ? rec.num_turns : void 0,
|
|
8859
9249
|
totalCostUsd: typeof rec.total_cost_usd === "number" ? rec.total_cost_usd : void 0,
|
|
@@ -8861,7 +9251,7 @@ var resultEvent = (message) => {
|
|
|
8861
9251
|
};
|
|
8862
9252
|
return isError ? {
|
|
8863
9253
|
type: "turn.failed",
|
|
8864
|
-
error: { message:
|
|
9254
|
+
error: { message: resultText2 || `claude result: ${subtype}`, subtype },
|
|
8865
9255
|
usage,
|
|
8866
9256
|
raw: message
|
|
8867
9257
|
} : { type: "turn.completed", subtype, usage, raw: message };
|
|
@@ -8878,8 +9268,8 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8878
9268
|
systemEmitted = true;
|
|
8879
9269
|
yield {
|
|
8880
9270
|
type: "thread.started",
|
|
8881
|
-
sessionId:
|
|
8882
|
-
model:
|
|
9271
|
+
sessionId: str3(p.session_id) ?? "",
|
|
9272
|
+
model: str3(p.model) ?? (isRecord4(p.model_info) ? str3(p.model_info.id) : void 0),
|
|
8883
9273
|
raw: message
|
|
8884
9274
|
};
|
|
8885
9275
|
yield { type: "turn.started", raw: message };
|
|
@@ -8918,7 +9308,7 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8918
9308
|
pendingTools.set(item.id, item);
|
|
8919
9309
|
yield { type: "item.started", item, raw: block };
|
|
8920
9310
|
} else if (block.type === "tool_result") {
|
|
8921
|
-
const id =
|
|
9311
|
+
const id = str3(block.tool_use_id) ?? "";
|
|
8922
9312
|
const item = pendingTools.get(id);
|
|
8923
9313
|
if (item) {
|
|
8924
9314
|
const completed = completeItem(item, textFromToolResult(block), block.is_error === true);
|
|
@@ -8936,7 +9326,7 @@ async function* claudeToAgentEvents(messages) {
|
|
|
8936
9326
|
if (message.type === "user") {
|
|
8937
9327
|
for (const block of messageContent(p)) {
|
|
8938
9328
|
if (block.type !== "tool_result") continue;
|
|
8939
|
-
const id =
|
|
9329
|
+
const id = str3(block.tool_use_id) ?? "";
|
|
8940
9330
|
const item = pendingTools.get(id);
|
|
8941
9331
|
if (!item) {
|
|
8942
9332
|
yield { type: "raw", raw: block };
|
|
@@ -8962,19 +9352,19 @@ var UnsettledExecution = class extends Error {
|
|
|
8962
9352
|
var materializeImages = async (config, execution) => {
|
|
8963
9353
|
const images = execution.message.payload.images ?? [];
|
|
8964
9354
|
if (!images.length) return [];
|
|
8965
|
-
const directory2 =
|
|
9355
|
+
const directory2 = join12(
|
|
8966
9356
|
config.runtimeDir,
|
|
8967
9357
|
"attachments",
|
|
8968
9358
|
`message-${execution.message.id}`,
|
|
8969
9359
|
"pasted"
|
|
8970
9360
|
);
|
|
8971
|
-
await
|
|
9361
|
+
await mkdir5(directory2, { recursive: true });
|
|
8972
9362
|
return Promise.all(
|
|
8973
9363
|
images.map(async (image, index) => {
|
|
8974
9364
|
const match = /^data:image\/(png|jpeg|webp|gif);base64,([A-Za-z0-9+/=\r\n]+)$/.exec(image);
|
|
8975
9365
|
if (!match) throw new Error("unsupported pasted image");
|
|
8976
|
-
const path =
|
|
8977
|
-
await
|
|
9366
|
+
const path = join12(directory2, `pasted-${index + 1}.${match[1]}`);
|
|
9367
|
+
await writeFile3(path, Buffer.from(match[2] ?? "", "base64"));
|
|
8978
9368
|
return path;
|
|
8979
9369
|
})
|
|
8980
9370
|
);
|
|
@@ -8982,7 +9372,7 @@ var materializeImages = async (config, execution) => {
|
|
|
8982
9372
|
var buildPrompt = async (config, execution, fetchImpl) => {
|
|
8983
9373
|
const { text: rawText, attachments = [] } = execution.message.payload;
|
|
8984
9374
|
const paths = attachments.length ? await materializeAttachments({
|
|
8985
|
-
directory:
|
|
9375
|
+
directory: join12(
|
|
8986
9376
|
config.runtimeDir,
|
|
8987
9377
|
"attachments",
|
|
8988
9378
|
`message-${execution.message.id}`,
|
|
@@ -9065,25 +9455,27 @@ var runTurn = async (config, worker2, execution, resuming, queryFn, log, envOver
|
|
|
9065
9455
|
const built = await buildPrompt(config, execution, fetchImpl);
|
|
9066
9456
|
if (abort.signal.aborted) throw new Error("interrupted before SDK start");
|
|
9067
9457
|
const { planMode, model, effort } = execution.message.payload;
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
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]();
|
|
9087
9479
|
};
|
|
9088
9480
|
const consuming = consume().then(
|
|
9089
9481
|
(value) => ({ kind: "result", value }),
|
|
@@ -9107,7 +9499,7 @@ var runTurn = async (config, worker2, execution, resuming, queryFn, log, envOver
|
|
|
9107
9499
|
};
|
|
9108
9500
|
|
|
9109
9501
|
// src/session/runner.ts
|
|
9110
|
-
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:");
|
|
9111
9503
|
var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
|
|
9112
9504
|
var runDaemon = async (config, deps, signal) => {
|
|
9113
9505
|
const log = deps.log ?? console.log;
|
|
@@ -9126,7 +9518,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
9126
9518
|
});
|
|
9127
9519
|
const drain = async () => {
|
|
9128
9520
|
if (draining || closed || stranded) return;
|
|
9129
|
-
const
|
|
9521
|
+
const run4 = async () => {
|
|
9130
9522
|
while (!closed && !stranded && !signal.aborted) {
|
|
9131
9523
|
let claimed;
|
|
9132
9524
|
try {
|
|
@@ -9169,7 +9561,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
9169
9561
|
}
|
|
9170
9562
|
}
|
|
9171
9563
|
};
|
|
9172
|
-
draining =
|
|
9564
|
+
draining = run4();
|
|
9173
9565
|
try {
|
|
9174
9566
|
await draining;
|
|
9175
9567
|
} finally {
|
|
@@ -9236,16 +9628,16 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
9236
9628
|
|
|
9237
9629
|
// src/worker/runtime-paths.ts
|
|
9238
9630
|
import { createHash } from "node:crypto";
|
|
9239
|
-
import { homedir as
|
|
9240
|
-
import { join as
|
|
9241
|
-
var workerRuntimeDir = (owner, env = process.env) =>
|
|
9242
|
-
env.XDG_DATA_HOME ??
|
|
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"),
|
|
9243
9635
|
"baton",
|
|
9244
9636
|
"runtime",
|
|
9245
9637
|
createHash("sha256").update(owner.server).digest("hex"),
|
|
9246
9638
|
`worker-${owner.workerId}`
|
|
9247
9639
|
);
|
|
9248
|
-
var sessionRuntimeDir = (owner, sessionId, env = process.env) =>
|
|
9640
|
+
var sessionRuntimeDir = (owner, sessionId, env = process.env) => join13(workerRuntimeDir(owner, env), "sessions", String(sessionId));
|
|
9249
9641
|
|
|
9250
9642
|
// src/commands/session/run.ts
|
|
9251
9643
|
var bearerFetch = (token) => (input, init2) => fetch(input, {
|
|
@@ -9299,6 +9691,7 @@ var sessionRunCommand = defineCommand({
|
|
|
9299
9691
|
const sessionId = Number(args.session);
|
|
9300
9692
|
if (!Number.isInteger(sessionId) || sessionId <= 0)
|
|
9301
9693
|
throw new Error(`invalid session id: ${args.session}`);
|
|
9694
|
+
process.env.BATON_SESSION_ID = String(sessionId);
|
|
9302
9695
|
const client = createClient(server, { bearer: workerToken });
|
|
9303
9696
|
const s = await client.sessions.get(sessionId);
|
|
9304
9697
|
if (!s.agentSessionId || !s.cwd)
|
|
@@ -9606,7 +9999,7 @@ import { hostname as osHostname } from "node:os";
|
|
|
9606
9999
|
// src/worker/bundled-skills.ts
|
|
9607
10000
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9608
10001
|
import { cpSync, existsSync as existsSync5 } from "node:fs";
|
|
9609
|
-
import { dirname as dirname3, join as
|
|
10002
|
+
import { dirname as dirname3, join as join14 } from "node:path";
|
|
9610
10003
|
import { fileURLToPath } from "node:url";
|
|
9611
10004
|
|
|
9612
10005
|
// src/worker/git-exclude.ts
|
|
@@ -9638,8 +10031,8 @@ var TARGETS = [`.claude/skills/${SKILL_NAME}`, `.agents/skills/${SKILL_NAME}`];
|
|
|
9638
10031
|
var bundledSkillPath = () => {
|
|
9639
10032
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
9640
10033
|
const candidates = [
|
|
9641
|
-
|
|
9642
|
-
|
|
10034
|
+
join14(here, "..", "skills", SKILL_NAME),
|
|
10035
|
+
join14(here, "..", "..", "skills", SKILL_NAME)
|
|
9643
10036
|
];
|
|
9644
10037
|
const found = candidates.find(existsSync5);
|
|
9645
10038
|
if (!found) throw new Error(`bundled skill missing: ${SKILL_NAME}`);
|
|
@@ -9656,8 +10049,427 @@ var syncBundledArtifactSkill = (cwd) => {
|
|
|
9656
10049
|
const source = bundledSkillPath();
|
|
9657
10050
|
TARGETS.filter((target) => !isTracked(cwd, target)).forEach((target) => {
|
|
9658
10051
|
ensureExcluded(cwd, `${target}/`);
|
|
9659
|
-
cpSync(source,
|
|
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" }
|
|
9660
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
|
+
};
|
|
9661
10473
|
};
|
|
9662
10474
|
|
|
9663
10475
|
// src/worker/watchdog.ts
|
|
@@ -9718,13 +10530,13 @@ var createLiveness = (deps) => {
|
|
|
9718
10530
|
|
|
9719
10531
|
// src/worker/service-supervisor.ts
|
|
9720
10532
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
9721
|
-
import { stat as
|
|
9722
|
-
import { join as
|
|
10533
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
10534
|
+
import { join as join17 } from "node:path";
|
|
9723
10535
|
|
|
9724
10536
|
// src/worker/service-process.ts
|
|
9725
10537
|
import { spawn as spawn2 } from "node:child_process";
|
|
9726
10538
|
import { closeSync, existsSync as existsSync6, openSync } from "node:fs";
|
|
9727
|
-
import { dirname as dirname4, join as
|
|
10539
|
+
import { dirname as dirname4, join as join16 } from "node:path";
|
|
9728
10540
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9729
10541
|
var START_TIMEOUT_MS = 1e4;
|
|
9730
10542
|
var isWrapperMessage = (message) => {
|
|
@@ -9735,7 +10547,7 @@ var isWrapperMessage = (message) => {
|
|
|
9735
10547
|
};
|
|
9736
10548
|
var binPath = () => {
|
|
9737
10549
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
9738
|
-
const devShim =
|
|
10550
|
+
const devShim = join16(here, "..", "..", "bin", "baton.mjs");
|
|
9739
10551
|
return existsSync6(devShim) ? devShim : fileURLToPath2(import.meta.url);
|
|
9740
10552
|
};
|
|
9741
10553
|
var startServiceProcess = (input) => {
|
|
@@ -9822,15 +10634,15 @@ var createServiceSupervisor = (deps) => {
|
|
|
9822
10634
|
};
|
|
9823
10635
|
const runOne = async (requestId, cwd, name, argv, publicUrl, note) => {
|
|
9824
10636
|
try {
|
|
9825
|
-
if (!(await
|
|
10637
|
+
if (!(await stat4(cwd)).isDirectory())
|
|
9826
10638
|
return report({ requestId, ok: false, error: `not a directory: ${cwd}`, status: 400 });
|
|
9827
10639
|
} catch (error) {
|
|
9828
10640
|
const code = error.code;
|
|
9829
10641
|
if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
|
|
9830
10642
|
return report({ requestId, ok: false, error: `directory not found: ${cwd}`, status: 400 });
|
|
9831
10643
|
}
|
|
9832
|
-
const logDir =
|
|
9833
|
-
const logPath =
|
|
10644
|
+
const logDir = join17(workerRuntimeDir(cfg), "services");
|
|
10645
|
+
const logPath = join17(logDir, `${name}.log`);
|
|
9834
10646
|
mkdirSync4(logDir, { recursive: true, mode: 448 });
|
|
9835
10647
|
if (entries.has(name))
|
|
9836
10648
|
return report({
|
|
@@ -9870,7 +10682,7 @@ var createServiceSupervisor = (deps) => {
|
|
|
9870
10682
|
log(`service ${name} started in ${cwd}`);
|
|
9871
10683
|
await report({ requestId, ok: true, service: service2, logPath });
|
|
9872
10684
|
};
|
|
9873
|
-
const
|
|
10685
|
+
const run4 = async (requestId, cwd, name, argv, publicUrl, note) => {
|
|
9874
10686
|
try {
|
|
9875
10687
|
await runOne(requestId, cwd, name, argv, publicUrl, note);
|
|
9876
10688
|
} catch (error) {
|
|
@@ -9888,15 +10700,15 @@ var createServiceSupervisor = (deps) => {
|
|
|
9888
10700
|
await report({ requestId, ok: true });
|
|
9889
10701
|
};
|
|
9890
10702
|
const killAll = () => entries.forEach(stopEntry);
|
|
9891
|
-
return { run:
|
|
10703
|
+
return { run: run4, stop, report, killAll };
|
|
9892
10704
|
};
|
|
9893
10705
|
|
|
9894
10706
|
// src/worker/session-supervisor.ts
|
|
9895
10707
|
import { spawn as spawn3 } from "node:child_process";
|
|
9896
10708
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9897
10709
|
import { existsSync as existsSync7 } from "node:fs";
|
|
9898
|
-
import { mkdir as
|
|
9899
|
-
import { dirname as dirname5, join as
|
|
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";
|
|
9900
10712
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9901
10713
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
9902
10714
|
|
|
@@ -9990,7 +10802,7 @@ var generateTitle = async (input) => {
|
|
|
9990
10802
|
// src/session/runner/title-codex.ts
|
|
9991
10803
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
9992
10804
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
9993
|
-
import { join as
|
|
10805
|
+
import { join as join18 } from "node:path";
|
|
9994
10806
|
import { Codex as Codex2 } from "@openai/codex-sdk";
|
|
9995
10807
|
var generateTitleWithCodex = async (input) => {
|
|
9996
10808
|
const { userText, assistantText } = input;
|
|
@@ -9999,7 +10811,7 @@ var generateTitleWithCodex = async (input) => {
|
|
|
9999
10811
|
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
10000
10812
|
let cwd = "";
|
|
10001
10813
|
try {
|
|
10002
|
-
cwd = await mkdtemp(
|
|
10814
|
+
cwd = await mkdtemp(join18(tmpdir3(), "baton-title-"));
|
|
10003
10815
|
const client = input.client ?? new Codex2();
|
|
10004
10816
|
const thread = client.startThread({
|
|
10005
10817
|
workingDirectory: cwd,
|
|
@@ -10025,19 +10837,79 @@ var generateTitleWithCodex = async (input) => {
|
|
|
10025
10837
|
}
|
|
10026
10838
|
};
|
|
10027
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
|
+
|
|
10028
10900
|
// src/worker/session-process.ts
|
|
10029
|
-
import { execFile as
|
|
10030
|
-
import { readFile as
|
|
10031
|
-
import { join as
|
|
10032
|
-
import { promisify as
|
|
10033
|
-
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);
|
|
10034
10906
|
var RUNNER_RECORD = ".baton-runner.json";
|
|
10035
|
-
var recordPath = (cwd) =>
|
|
10907
|
+
var recordPath = (cwd) => join20(cwd, RUNNER_RECORD);
|
|
10036
10908
|
var codeOf = (error) => typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
10037
10909
|
var readRunnerRecord = async (cwd) => {
|
|
10038
10910
|
let raw;
|
|
10039
10911
|
try {
|
|
10040
|
-
raw = await
|
|
10912
|
+
raw = await readFile3(recordPath(cwd), "utf8");
|
|
10041
10913
|
} catch (error) {
|
|
10042
10914
|
if (codeOf(error) === "ENOENT") return null;
|
|
10043
10915
|
throw error;
|
|
@@ -10050,7 +10922,7 @@ var readRunnerRecord = async (cwd) => {
|
|
|
10050
10922
|
var writeRunnerRecord = async (cwd, record) => {
|
|
10051
10923
|
const path = recordPath(cwd);
|
|
10052
10924
|
const temporary = `${path}.${record.runnerToken}.tmp`;
|
|
10053
|
-
await
|
|
10925
|
+
await writeFile4(temporary, JSON.stringify(record), { mode: 384, flag: "wx" });
|
|
10054
10926
|
await rename(temporary, path);
|
|
10055
10927
|
};
|
|
10056
10928
|
var groupExists = (pgid) => {
|
|
@@ -10075,7 +10947,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
|
|
|
10075
10947
|
if (!groupExists(record.pgid)) return;
|
|
10076
10948
|
let stdout;
|
|
10077
10949
|
try {
|
|
10078
|
-
const result = await
|
|
10950
|
+
const result = await run3("ps", ["-ww", "-p", String(record.pid), "-o", "pgid=", "-o", "args="]);
|
|
10079
10951
|
stdout = result.stdout;
|
|
10080
10952
|
} catch (error) {
|
|
10081
10953
|
if (!groupExists(record.pgid)) return;
|
|
@@ -10100,7 +10972,7 @@ var stopRecordedRunner = async (record, expected, graceMs = 5e3) => {
|
|
|
10100
10972
|
// src/worker/session-supervisor.ts
|
|
10101
10973
|
var binPath2 = () => {
|
|
10102
10974
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
10103
|
-
const devShim =
|
|
10975
|
+
const devShim = join21(here, "..", "..", "bin", "baton.mjs");
|
|
10104
10976
|
return existsSync7(devShim) ? devShim : fileURLToPath3(import.meta.url);
|
|
10105
10977
|
};
|
|
10106
10978
|
var createSessionSupervisor = (deps) => {
|
|
@@ -10209,7 +11081,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
10209
11081
|
return;
|
|
10210
11082
|
if (wasCanceled()) return log(`session #${sessionId} start canceled before materializing`);
|
|
10211
11083
|
const directory2 = session2.cwd ?? cwd;
|
|
10212
|
-
if (!(await
|
|
11084
|
+
if (!(await stat5(directory2)).isDirectory()) throw new Error(`not a directory: ${directory2}`);
|
|
10213
11085
|
if (wasCanceled()) return;
|
|
10214
11086
|
if (!session2.agentSessionId || !session2.cwd) {
|
|
10215
11087
|
const id = randomUUID2();
|
|
@@ -10217,7 +11089,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
10217
11089
|
await client.sessions.materialize(sessionId, { agentSessionId, cwd: directory2 }, cfg.apiToken);
|
|
10218
11090
|
log(`materialized session #${sessionId} (${name}) \u2192 ${directory2}`);
|
|
10219
11091
|
}
|
|
10220
|
-
await
|
|
11092
|
+
await mkdir6(sessionRuntimeDir(cfg, sessionId), { recursive: true, mode: 448 });
|
|
10221
11093
|
if (hasTerminal(sessionId))
|
|
10222
11094
|
return log(`session #${sessionId} terminal opened mid-start \u2014 skipping headless start`);
|
|
10223
11095
|
if (wasCanceled()) return;
|
|
@@ -10249,7 +11121,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
10249
11121
|
return enqueue(sessionId, async () => {
|
|
10250
11122
|
const entry = children.get(sessionId);
|
|
10251
11123
|
await stopRecord(sessionId);
|
|
10252
|
-
await
|
|
11124
|
+
await rm3(sessionRuntimeDir(cfg, sessionId), { recursive: true, force: true });
|
|
10253
11125
|
if (children.get(sessionId) === entry) children.delete(sessionId);
|
|
10254
11126
|
});
|
|
10255
11127
|
};
|
|
@@ -10258,15 +11130,12 @@ var createSessionSupervisor = (deps) => {
|
|
|
10258
11130
|
const events = await client.sessions.listEvents(sessionId);
|
|
10259
11131
|
const exchange = parseFirstExchangeFromEvents(events) ?? (session2.agentKind === "claude-code" ? readFirstExchange(agentSessionId) : null);
|
|
10260
11132
|
if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
|
|
10261
|
-
const
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
assistantText: exchange.assistantText,
|
|
10268
|
-
queryFn: query2
|
|
10269
|
-
});
|
|
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);
|
|
10270
11139
|
if (outcome.kind === "error") return log(`title #${sessionId} failed: ${outcome.reason}`);
|
|
10271
11140
|
if (outcome.kind === "declined")
|
|
10272
11141
|
return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
@@ -10347,8 +11216,9 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
10347
11216
|
// src/worker/pty.ts
|
|
10348
11217
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
10349
11218
|
import { createRequire as createModuleRequire } from "node:module";
|
|
10350
|
-
import { homedir as
|
|
10351
|
-
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";
|
|
10352
11222
|
var MAX_TERMINALS = 10;
|
|
10353
11223
|
var claudePtyCommand = (agentSessionId, hasJsonl, env = process.env) => ({
|
|
10354
11224
|
file: env.BATON_CLAUDE_BIN?.trim() || "claude",
|
|
@@ -10363,13 +11233,25 @@ var codexPtyCommand = (agentSessionId, env = process.env) => {
|
|
|
10363
11233
|
args: [moduleRequire.resolve("@openai/codex/bin/codex.js"), ...args]
|
|
10364
11234
|
};
|
|
10365
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
|
+
};
|
|
10366
11243
|
var hasSessionJsonl = (agentSessionId) => {
|
|
10367
|
-
const root =
|
|
11244
|
+
const root = join22(homedir5(), ".claude", "projects");
|
|
10368
11245
|
if (!existsSync8(root)) return false;
|
|
10369
11246
|
for (const dir of readdirSync2(root))
|
|
10370
|
-
if (existsSync8(
|
|
11247
|
+
if (existsSync8(join22(root, dir, `${agentSessionId}.jsonl`))) return true;
|
|
10371
11248
|
return false;
|
|
10372
11249
|
};
|
|
11250
|
+
var ptyCommand = (kind, agentSessionId) => ({
|
|
11251
|
+
"claude-code": () => claudePtyCommand(agentSessionId, hasSessionJsonl(agentSessionId)),
|
|
11252
|
+
codex: () => codexPtyCommand(agentSessionId),
|
|
11253
|
+
pi: () => piPtyCommand(agentSessionId)
|
|
11254
|
+
})[kind]();
|
|
10373
11255
|
var serverTerminalWsUrl = (server, sessionId) => `${server.replace(/^http/, "ws")}/workers/me/terminal/ws?sessionId=${sessionId}`;
|
|
10374
11256
|
var isPositiveDim = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
10375
11257
|
|
|
@@ -10377,13 +11259,13 @@ var isPositiveDim = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
|
10377
11259
|
var createTerminalManager = (deps) => {
|
|
10378
11260
|
const { cfg, log, hasChild } = deps;
|
|
10379
11261
|
const terminals = /* @__PURE__ */ new Map();
|
|
10380
|
-
const
|
|
11262
|
+
const open3 = (sessionId, agentSessionId, cwd) => {
|
|
10381
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;
|
|
10382
11264
|
if (why) {
|
|
10383
11265
|
log(`terminal #${sessionId}: ${why} \u2014 refusing`);
|
|
10384
11266
|
return;
|
|
10385
11267
|
}
|
|
10386
|
-
const command = cfg.agentKind
|
|
11268
|
+
const command = ptyCommand(cfg.agentKind, agentSessionId);
|
|
10387
11269
|
const term = spawn4(command.file, command.args, {
|
|
10388
11270
|
name: "xterm-color",
|
|
10389
11271
|
cols: 80,
|
|
@@ -10435,7 +11317,7 @@ var createTerminalManager = (deps) => {
|
|
|
10435
11317
|
}
|
|
10436
11318
|
};
|
|
10437
11319
|
return {
|
|
10438
|
-
open:
|
|
11320
|
+
open: open3,
|
|
10439
11321
|
close,
|
|
10440
11322
|
has: (sessionId) => terminals.has(sessionId),
|
|
10441
11323
|
killAll: () => {
|
|
@@ -10473,6 +11355,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10473
11355
|
log,
|
|
10474
11356
|
hasChild: (id) => supervisor.has(id)
|
|
10475
11357
|
});
|
|
11358
|
+
const files = createFileQueryRunner(cwd);
|
|
10476
11359
|
const services = createServiceSupervisor({
|
|
10477
11360
|
sendReport: (report) => client.services.report(report),
|
|
10478
11361
|
cfg,
|
|
@@ -10528,6 +11411,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10528
11411
|
void services.run(cmd.requestId, cmd.cwd, cmd.name, cmd.argv, cmd.publicUrl, cmd.note).catch((err) => log(`service run failed: ${String(err)}`));
|
|
10529
11412
|
else if (cmd.cmd === "service.stop")
|
|
10530
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)}`));
|
|
10531
11416
|
} catch (error) {
|
|
10532
11417
|
log(`invalid command: ${String(error)}`);
|
|
10533
11418
|
}
|
|
@@ -10551,16 +11436,16 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
10551
11436
|
// src/worker/machine-id.ts
|
|
10552
11437
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
10553
11438
|
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
10554
|
-
import { homedir as
|
|
10555
|
-
import { dirname as
|
|
10556
|
-
var machineIdPath = (env = process.env) =>
|
|
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");
|
|
10557
11442
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
10558
11443
|
if (existsSync9(path)) {
|
|
10559
11444
|
const s = readFileSync6(path, "utf8").trim();
|
|
10560
11445
|
if (s) return s;
|
|
10561
11446
|
}
|
|
10562
11447
|
const id = randomUUID3();
|
|
10563
|
-
mkdirSync5(
|
|
11448
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
10564
11449
|
writeFileSync5(path, `${id}
|
|
10565
11450
|
`, "utf8");
|
|
10566
11451
|
return id;
|
|
@@ -10588,22 +11473,28 @@ var workerCreateCommand = defineCommand({
|
|
|
10588
11473
|
args: {
|
|
10589
11474
|
...projectArg,
|
|
10590
11475
|
...common,
|
|
10591
|
-
template: {
|
|
11476
|
+
template: {
|
|
11477
|
+
type: "string",
|
|
11478
|
+
required: true,
|
|
11479
|
+
description: "mini-glm-codeplan | mini-dashscope-codeplan | mini-codex"
|
|
11480
|
+
},
|
|
10592
11481
|
name: { type: "string", required: true, description: "worker name" },
|
|
10593
|
-
agentKind: { type: "string",
|
|
11482
|
+
agentKind: { type: "string", description: AGENT_KINDS.join(", ") }
|
|
10594
11483
|
},
|
|
10595
11484
|
run: async ({ args }) => {
|
|
10596
|
-
if (args.template
|
|
10597
|
-
|
|
10598
|
-
|
|
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");
|
|
10599
11490
|
const name = args.name.trim();
|
|
10600
11491
|
if (!name) throw new Error("worker name required");
|
|
10601
11492
|
const creation = await clientFor(args).workerCreations.create(
|
|
10602
11493
|
positiveId(resolveProjectId(args)),
|
|
10603
11494
|
{
|
|
10604
|
-
templateId:
|
|
11495
|
+
templateId: args.template,
|
|
10605
11496
|
name,
|
|
10606
|
-
agentKind
|
|
11497
|
+
agentKind
|
|
10607
11498
|
}
|
|
10608
11499
|
);
|
|
10609
11500
|
console.log(renderOne(creation, fmtWorkerCreation, Boolean(args.json)));
|
|
@@ -10644,8 +11535,8 @@ var resolveWorker = async (client, projectId, handle) => {
|
|
|
10644
11535
|
};
|
|
10645
11536
|
var parseAgentKind = (value) => {
|
|
10646
11537
|
const raw = typeof value === "string" && value.trim() ? value.trim() : "claude-code";
|
|
10647
|
-
if (raw
|
|
10648
|
-
throw new Error(`invalid agent kind "${raw}" (expected
|
|
11538
|
+
if (isAgentKind(raw)) return raw;
|
|
11539
|
+
throw new Error(`invalid agent kind "${raw}" (expected ${AGENT_KINDS.join(", ")})`);
|
|
10649
11540
|
};
|
|
10650
11541
|
var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
10651
11542
|
const out = await client.workers.register({
|
|
@@ -10690,7 +11581,7 @@ var worker = defineCommand({
|
|
|
10690
11581
|
},
|
|
10691
11582
|
agentKind: {
|
|
10692
11583
|
type: "string",
|
|
10693
|
-
description:
|
|
11584
|
+
description: `agent engine for new sessions: ${AGENT_KINDS.join(", ")}`
|
|
10694
11585
|
},
|
|
10695
11586
|
...common
|
|
10696
11587
|
},
|
|
@@ -10927,6 +11818,7 @@ var main = defineCommand({
|
|
|
10927
11818
|
service,
|
|
10928
11819
|
artifact,
|
|
10929
11820
|
loop,
|
|
11821
|
+
notify,
|
|
10930
11822
|
relay,
|
|
10931
11823
|
channel,
|
|
10932
11824
|
workspace,
|