@lesscap/baton-cli 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baton.mjs +56 -23
- package/package.json +1 -1
package/dist/baton.mjs
CHANGED
|
@@ -587,9 +587,13 @@ var sessionsClient = (baseUrl) => {
|
|
|
587
587
|
const matches = all.filter((s) => s.name === name);
|
|
588
588
|
return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
|
|
589
589
|
},
|
|
590
|
-
sendMessage: (id, text, attachments) => request(u(`/sessions/${id}/messages`), {
|
|
590
|
+
sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
|
|
591
591
|
method: "POST",
|
|
592
|
-
body:
|
|
592
|
+
body: {
|
|
593
|
+
text,
|
|
594
|
+
...attachments && attachments.length > 0 ? { attachments } : {},
|
|
595
|
+
...planMode ? { planMode: true } : {}
|
|
596
|
+
}
|
|
593
597
|
}),
|
|
594
598
|
// Raw-body upload: the file streams as the request body (no multipart), so
|
|
595
599
|
// a Blob backed by a file on disk uploads without buffering. filename rides
|
|
@@ -734,6 +738,11 @@ var viewWorker = (cfg) => {
|
|
|
734
738
|
apiToken: worker2.apiToken
|
|
735
739
|
};
|
|
736
740
|
};
|
|
741
|
+
var worktreeConfig = (cfg) => ({
|
|
742
|
+
server: cfg.server,
|
|
743
|
+
project: cfg.projectId,
|
|
744
|
+
worker: { id: cfg.workerId, name: cfg.name, machineId: cfg.machineId, apiToken: cfg.apiToken }
|
|
745
|
+
});
|
|
737
746
|
|
|
738
747
|
// src/config.ts
|
|
739
748
|
var resolveBaseUrl = (urlArg, env = process.env) => {
|
|
@@ -1795,14 +1804,15 @@ ${text}` : header;
|
|
|
1795
1804
|
};
|
|
1796
1805
|
|
|
1797
1806
|
// src/session/runner/query.ts
|
|
1798
|
-
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
|
|
1807
|
+
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay, planMode = false) => {
|
|
1799
1808
|
const env = buildSdkEnv(envOverlay);
|
|
1800
1809
|
const exe = claudeExecutable();
|
|
1801
1810
|
const addDirs = additionalDirs();
|
|
1802
1811
|
if (addDirs) log(`[add-dir] ${addDirs.join(", ")}`);
|
|
1812
|
+
if (planMode) log("[plan] read-only planning turn");
|
|
1803
1813
|
const options = {
|
|
1804
1814
|
cwd: config.worktreePath,
|
|
1805
|
-
permissionMode: "bypassPermissions",
|
|
1815
|
+
permissionMode: planMode ? "plan" : "bypassPermissions",
|
|
1806
1816
|
includePartialMessages: false,
|
|
1807
1817
|
abortController,
|
|
1808
1818
|
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
@@ -1868,6 +1878,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1868
1878
|
const payload = msg.payload;
|
|
1869
1879
|
const rawText = typeof payload?.text === "string" ? payload.text : "";
|
|
1870
1880
|
const attachments = Array.isArray(payload?.attachments) ? payload.attachments : [];
|
|
1881
|
+
const planMode = payload?.planMode === true;
|
|
1871
1882
|
if (rawText.length === 0 && attachments.length === 0) {
|
|
1872
1883
|
await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
|
|
1873
1884
|
return -1;
|
|
@@ -1883,15 +1894,15 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1883
1894
|
const abort = new AbortController();
|
|
1884
1895
|
const ceiling = turnTimeoutMs();
|
|
1885
1896
|
let timer;
|
|
1886
|
-
const timeout = new Promise((
|
|
1887
|
-
timer = setTimeout(() =>
|
|
1897
|
+
const timeout = new Promise((resolve2) => {
|
|
1898
|
+
timer = setTimeout(() => resolve2(TIMEOUT), ceiling);
|
|
1888
1899
|
});
|
|
1889
|
-
const interrupted = new Promise((
|
|
1890
|
-
if (externalSignal?.aborted)
|
|
1891
|
-
else externalSignal?.addEventListener("abort", () =>
|
|
1900
|
+
const interrupted = new Promise((resolve2) => {
|
|
1901
|
+
if (externalSignal?.aborted) resolve2(ABORTED);
|
|
1902
|
+
else externalSignal?.addEventListener("abort", () => resolve2(ABORTED), { once: true });
|
|
1892
1903
|
});
|
|
1893
1904
|
try {
|
|
1894
|
-
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
|
|
1905
|
+
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay, planMode);
|
|
1895
1906
|
const consume = streamSdkEvents(messages, worker2);
|
|
1896
1907
|
void consume.catch(() => {
|
|
1897
1908
|
});
|
|
@@ -1906,7 +1917,9 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
|
|
|
1906
1917
|
if (outcome === TIMEOUT) {
|
|
1907
1918
|
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
|
|
1908
1919
|
abort.abort();
|
|
1909
|
-
await worker2.emitEvent("turn_error", {
|
|
1920
|
+
await worker2.emitEvent("turn_error", {
|
|
1921
|
+
message: `turn exceeded ${ceiling}ms \u2014 claude aborted`
|
|
1922
|
+
});
|
|
1910
1923
|
await worker2.emitEvent("turn_complete", {});
|
|
1911
1924
|
return -1;
|
|
1912
1925
|
}
|
|
@@ -1940,9 +1953,9 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onRea
|
|
|
1940
1953
|
es.onerror = () => log("sse error (eventsource will retry)");
|
|
1941
1954
|
return es;
|
|
1942
1955
|
};
|
|
1943
|
-
var waitForAbort = (signal) => new Promise((
|
|
1944
|
-
if (signal.aborted) return
|
|
1945
|
-
signal.addEventListener("abort", () =>
|
|
1956
|
+
var waitForAbort = (signal) => new Promise((resolve2) => {
|
|
1957
|
+
if (signal.aborted) return resolve2();
|
|
1958
|
+
signal.addEventListener("abort", () => resolve2(), { once: true });
|
|
1946
1959
|
});
|
|
1947
1960
|
var shouldReap = (lastActivity, now, busy, queueLen, idleMs) => !busy && queueLen === 0 && now - lastActivity >= idleMs;
|
|
1948
1961
|
var idleMsFromEnv = () => Number(process.env.BATON_SESSION_IDLE_MS) || 30 * 6e4;
|
|
@@ -2025,12 +2038,12 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2025
2038
|
);
|
|
2026
2039
|
const idleMs = idleMsFromEnv();
|
|
2027
2040
|
let idleTimer;
|
|
2028
|
-
const reaped = new Promise((
|
|
2041
|
+
const reaped = new Promise((resolve2) => {
|
|
2029
2042
|
idleTimer = setInterval(
|
|
2030
2043
|
() => {
|
|
2031
2044
|
if (shouldReap(lastActivity, Date.now(), busy, pendingQueue.length, idleMs)) {
|
|
2032
2045
|
log("idle \u2014 shutting down (resumes on next message)");
|
|
2033
|
-
|
|
2046
|
+
resolve2();
|
|
2034
2047
|
}
|
|
2035
2048
|
},
|
|
2036
2049
|
Math.min(idleMs, 3e4)
|
|
@@ -2401,8 +2414,8 @@ ${exchange}`;
|
|
|
2401
2414
|
|
|
2402
2415
|
// src/session/worktree.ts
|
|
2403
2416
|
import { execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2404
|
-
import { existsSync as existsSync4, mkdirSync } from "node:fs";
|
|
2405
|
-
import { dirname } from "node:path";
|
|
2417
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
2418
|
+
import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
2406
2419
|
var createWorktree = (input) => {
|
|
2407
2420
|
if (existsSync4(input.worktreePath))
|
|
2408
2421
|
throw new Error(`worktree path already exists: ${input.worktreePath}`);
|
|
@@ -2425,6 +2438,24 @@ var repoHeadBranch = (repo) => {
|
|
|
2425
2438
|
const branch = r.status === 0 ? r.stdout.trim() : "";
|
|
2426
2439
|
return branch || "main";
|
|
2427
2440
|
};
|
|
2441
|
+
var ensureExcluded = (repo, pattern) => {
|
|
2442
|
+
try {
|
|
2443
|
+
const r = spawnSync2("git", ["-C", repo, "rev-parse", "--git-path", "info/exclude"], {
|
|
2444
|
+
stdio: "pipe",
|
|
2445
|
+
encoding: "utf8"
|
|
2446
|
+
});
|
|
2447
|
+
if (r.status !== 0) return;
|
|
2448
|
+
const rel = r.stdout.trim();
|
|
2449
|
+
const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
|
|
2450
|
+
const current = existsSync4(path) ? readFileSync3(path, "utf8") : "";
|
|
2451
|
+
if (current.split("\n").includes(pattern)) return;
|
|
2452
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
2453
|
+
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
2454
|
+
appendFileSync(path, `${sep}${pattern}
|
|
2455
|
+
`, "utf8");
|
|
2456
|
+
} catch {
|
|
2457
|
+
}
|
|
2458
|
+
};
|
|
2428
2459
|
var removeWorktree = (repo, worktreePath) => {
|
|
2429
2460
|
if (!existsSync4(worktreePath)) return;
|
|
2430
2461
|
try {
|
|
@@ -2494,6 +2525,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2494
2525
|
await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
|
|
2495
2526
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
2496
2527
|
}
|
|
2528
|
+
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
2529
|
+
saveProjectConfig(join5(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
2497
2530
|
spawnChild(sessionId, worktreePath);
|
|
2498
2531
|
};
|
|
2499
2532
|
const onStop = (sessionId) => {
|
|
@@ -2565,9 +2598,9 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2565
2598
|
};
|
|
2566
2599
|
es.onerror = () => log("command stream error (eventsource will retry)");
|
|
2567
2600
|
log(`listening for commands (server ${cfg.server}, repo ${repo})`);
|
|
2568
|
-
await new Promise((
|
|
2569
|
-
if (signal.aborted) return
|
|
2570
|
-
signal.addEventListener("abort", () =>
|
|
2601
|
+
await new Promise((resolve2) => {
|
|
2602
|
+
if (signal.aborted) return resolve2();
|
|
2603
|
+
signal.addEventListener("abort", () => resolve2(), { once: true });
|
|
2571
2604
|
});
|
|
2572
2605
|
es.close();
|
|
2573
2606
|
clearInterval(hb);
|
|
@@ -2576,13 +2609,13 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2576
2609
|
|
|
2577
2610
|
// src/worker/machine-id.ts
|
|
2578
2611
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2579
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as
|
|
2612
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2580
2613
|
import { homedir as homedir3 } from "node:os";
|
|
2581
2614
|
import { dirname as dirname3, join as join6 } from "node:path";
|
|
2582
2615
|
var machineIdPath = (env = process.env) => join6(env.XDG_DATA_HOME ?? join6(env.HOME ?? homedir3(), ".local/share"), "baton", "machine-id");
|
|
2583
2616
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
2584
2617
|
if (existsSync6(path)) {
|
|
2585
|
-
const s =
|
|
2618
|
+
const s = readFileSync4(path, "utf8").trim();
|
|
2586
2619
|
if (s) return s;
|
|
2587
2620
|
}
|
|
2588
2621
|
const id = randomUUID2();
|