@lesscap/baton-cli 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baton.mjs +162 -180
- package/package.json +5 -2
package/dist/baton.mjs
CHANGED
|
@@ -578,6 +578,7 @@ var sessionsClient = (baseUrl) => {
|
|
|
578
578
|
resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
|
|
579
579
|
stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
|
|
580
580
|
clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
|
|
581
|
+
abort: (id) => request(u(`/sessions/${id}/abort`), { method: "POST" }),
|
|
581
582
|
rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
|
|
582
583
|
listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
|
|
583
584
|
get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
|
|
@@ -1035,6 +1036,22 @@ var resolveSession = async (client, projectId, handle) => {
|
|
|
1035
1036
|
throw new Error(`session "${handle}" not found in project ${projectId}`);
|
|
1036
1037
|
};
|
|
1037
1038
|
|
|
1039
|
+
// src/commands/session/abort.ts
|
|
1040
|
+
var sessionAbortCommand = defineCommand({
|
|
1041
|
+
meta: { name: "abort", description: "interrupt the in-flight turn (like Esc); keeps the session" },
|
|
1042
|
+
args: {
|
|
1043
|
+
session: { type: "positional", required: true, description: "session int id or name" },
|
|
1044
|
+
project: { type: "string", description: "project id (overrides .baton.json)" },
|
|
1045
|
+
...common
|
|
1046
|
+
},
|
|
1047
|
+
run: async ({ args }) => {
|
|
1048
|
+
const c = clientFor(args);
|
|
1049
|
+
const handle = await resolveSession(c, resolveProjectId(args), args.session);
|
|
1050
|
+
const s = await c.sessions.abort(handle.id);
|
|
1051
|
+
console.log(renderOne(s, fmtSession, Boolean(args.json)));
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1038
1055
|
// src/commands/session/clear.ts
|
|
1039
1056
|
var sessionClearCommand = defineCommand({
|
|
1040
1057
|
meta: { name: "clear", description: "reset the conversation context (keeps the session + worktree)" },
|
|
@@ -1637,34 +1654,15 @@ function getBaseURL() {
|
|
|
1637
1654
|
}
|
|
1638
1655
|
|
|
1639
1656
|
// src/session/runner.ts
|
|
1640
|
-
import {
|
|
1657
|
+
import { spawnSync } from "node:child_process";
|
|
1658
|
+
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
1641
1659
|
|
|
1642
1660
|
// src/session/runner/log.ts
|
|
1643
|
-
var claudeBin = () => process.env.BATON_CLAUDE_BIN ?? "claude";
|
|
1644
|
-
var buildClaudeArgs = (claudeSessionId, text, resuming) => [
|
|
1645
|
-
"--print",
|
|
1646
|
-
resuming ? "--resume" : "--session-id",
|
|
1647
|
-
claudeSessionId,
|
|
1648
|
-
"--output-format",
|
|
1649
|
-
"stream-json",
|
|
1650
|
-
"--verbose",
|
|
1651
|
-
"--dangerously-skip-permissions",
|
|
1652
|
-
text
|
|
1653
|
-
];
|
|
1654
|
-
var STDERR_TAIL_BYTES = 2048;
|
|
1655
|
-
var tailBuffer = (cap = STDERR_TAIL_BYTES) => {
|
|
1656
|
-
let buf = "";
|
|
1657
|
-
return {
|
|
1658
|
-
append(chunk) {
|
|
1659
|
-
buf = (buf + chunk).slice(-cap);
|
|
1660
|
-
},
|
|
1661
|
-
toString() {
|
|
1662
|
-
return buf;
|
|
1663
|
-
}
|
|
1664
|
-
};
|
|
1665
|
-
};
|
|
1666
1661
|
var maskedEnvKeys = (env) => env ? Object.keys(env).sort().join(", ") : "(none)";
|
|
1667
|
-
|
|
1662
|
+
|
|
1663
|
+
// src/session/runner/sdk-env.ts
|
|
1664
|
+
var buildSdkEnv = (overlay) => overlay ? { ...process.env, ...overlay } : void 0;
|
|
1665
|
+
var claudeExecutable = () => process.env.BATON_CLAUDE_BIN;
|
|
1668
1666
|
|
|
1669
1667
|
// src/session/runner/transcript.ts
|
|
1670
1668
|
import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1788,90 +1786,74 @@ var augmentPrompt = (text, relPaths, labels) => {
|
|
|
1788
1786
|
${text}` : header;
|
|
1789
1787
|
};
|
|
1790
1788
|
|
|
1791
|
-
// src/session/runner/
|
|
1792
|
-
import
|
|
1793
|
-
var
|
|
1794
|
-
const
|
|
1795
|
-
const
|
|
1796
|
-
const
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
} catch (err) {
|
|
1808
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1809
|
-
log(`[spawn] failed: ${message}`);
|
|
1810
|
-
await worker2.emitEvent("turn_error", { message: `spawn failed: ${message}` });
|
|
1811
|
-
return null;
|
|
1812
|
-
}
|
|
1813
|
-
if (!child.stdout) {
|
|
1814
|
-
log("[spawn] no stdout from claude \u2014 aborting turn");
|
|
1815
|
-
await worker2.emitEvent("turn_error", { message: "no stdout from claude" });
|
|
1816
|
-
return null;
|
|
1817
|
-
}
|
|
1818
|
-
const tail = tailBuffer();
|
|
1819
|
-
if (child.stderr) {
|
|
1820
|
-
const errLines = createInterface2({ input: child.stderr });
|
|
1821
|
-
void (async () => {
|
|
1822
|
-
for await (const line of errLines) {
|
|
1823
|
-
tail.append(`${line}
|
|
1824
|
-
`);
|
|
1825
|
-
log(`[claude] ${line}`);
|
|
1826
|
-
}
|
|
1827
|
-
})();
|
|
1828
|
-
}
|
|
1829
|
-
return { child, tail };
|
|
1789
|
+
// src/session/runner/query.ts
|
|
1790
|
+
import "@anthropic-ai/claude-agent-sdk";
|
|
1791
|
+
var startQuery = (config, text, resuming, queryFn, abortController, log, envOverlay) => {
|
|
1792
|
+
const env = buildSdkEnv(envOverlay);
|
|
1793
|
+
const exe = claudeExecutable();
|
|
1794
|
+
const options = {
|
|
1795
|
+
cwd: config.worktreePath,
|
|
1796
|
+
permissionMode: "bypassPermissions",
|
|
1797
|
+
includePartialMessages: false,
|
|
1798
|
+
abortController,
|
|
1799
|
+
stderr: (line) => log(`[claude] ${line.trimEnd()}`),
|
|
1800
|
+
...resuming ? { resume: config.agentSessionId } : { sessionId: config.agentSessionId },
|
|
1801
|
+
...env ? { env } : {},
|
|
1802
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
1803
|
+
};
|
|
1804
|
+
return queryFn({ prompt: text, options });
|
|
1830
1805
|
};
|
|
1831
1806
|
|
|
1832
1807
|
// src/session/runner/stream.ts
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
if (!trimmed) continue;
|
|
1839
|
-
let payload;
|
|
1840
|
-
try {
|
|
1841
|
-
payload = JSON.parse(trimmed);
|
|
1842
|
-
} catch {
|
|
1843
|
-
payload = { raw: trimmed };
|
|
1844
|
-
}
|
|
1845
|
-
await worker2.emitEvent("sdk_event", payload);
|
|
1808
|
+
var streamSdkEvents = async (messages, worker2) => {
|
|
1809
|
+
let result = null;
|
|
1810
|
+
for await (const message of messages) {
|
|
1811
|
+
await worker2.emitEvent("sdk_event", message);
|
|
1812
|
+
if (message.type === "result") result = toTurnResult(message);
|
|
1846
1813
|
}
|
|
1814
|
+
return result;
|
|
1815
|
+
};
|
|
1816
|
+
var toTurnResult = (message) => {
|
|
1817
|
+
const rec = message;
|
|
1818
|
+
const subtype = typeof rec.subtype === "string" ? rec.subtype : "unknown";
|
|
1819
|
+
const isError = rec.is_error === true || subtype !== "success";
|
|
1820
|
+
const resultText = typeof rec.result === "string" ? rec.result : "";
|
|
1821
|
+
return { subtype, isError, resultText };
|
|
1847
1822
|
};
|
|
1848
1823
|
|
|
1849
1824
|
// src/session/runner/turn.ts
|
|
1850
|
-
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1851
1825
|
var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
|
|
1852
|
-
var
|
|
1853
|
-
var
|
|
1854
|
-
|
|
1855
|
-
if (
|
|
1856
|
-
|
|
1857
|
-
};
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1826
|
+
var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
|
|
1827
|
+
var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
|
|
1828
|
+
var finalizeTurn = async (result, worker2, log) => {
|
|
1829
|
+
if (!result) {
|
|
1830
|
+
log("[exit] stream ended with no result");
|
|
1831
|
+
await worker2.emitEvent("turn_error", { message: "claude produced no result" });
|
|
1832
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1833
|
+
return -1;
|
|
1834
|
+
}
|
|
1835
|
+
log(`[exit] subtype=${result.subtype} isError=${result.isError}`);
|
|
1836
|
+
if (result.isError) {
|
|
1862
1837
|
await worker2.emitEvent("turn_error", {
|
|
1863
|
-
message: `claude
|
|
1864
|
-
|
|
1865
|
-
stderrTail
|
|
1838
|
+
message: result.resultText || `claude result: ${result.subtype}`,
|
|
1839
|
+
subtype: result.subtype
|
|
1866
1840
|
});
|
|
1867
1841
|
}
|
|
1868
|
-
await worker2.emitEvent("turn_complete", {
|
|
1869
|
-
|
|
1870
|
-
|
|
1842
|
+
await worker2.emitEvent("turn_complete", { subtype: result.subtype });
|
|
1843
|
+
return result.isError ? 1 : 0;
|
|
1844
|
+
};
|
|
1845
|
+
var buildPrompt = async (config, rawText, attachments, log, fetchImpl) => {
|
|
1846
|
+
if (attachments.length === 0) return rawText;
|
|
1847
|
+
const relPaths = await materializeAttachments({
|
|
1848
|
+
worktreePath: config.worktreePath,
|
|
1849
|
+
serverBase: config.server,
|
|
1850
|
+
attachments,
|
|
1851
|
+
fetchImpl
|
|
1871
1852
|
});
|
|
1872
|
-
|
|
1853
|
+
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1854
|
+
return augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1873
1855
|
};
|
|
1874
|
-
var runTurn = async (config, worker2, msg, resuming,
|
|
1856
|
+
var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl, externalSignal) => {
|
|
1875
1857
|
await worker2.emitEvent("turn_start", { messageId: msg.id });
|
|
1876
1858
|
const payload = msg.payload;
|
|
1877
1859
|
const rawText = typeof payload?.text === "string" ? payload.text : "";
|
|
@@ -1880,61 +1862,58 @@ var runTurn = async (config, worker2, msg, resuming, spawnImpl, log = (m) => con
|
|
|
1880
1862
|
await worker2.emitEvent("turn_error", { message: "user_message missing text and attachments" });
|
|
1881
1863
|
return -1;
|
|
1882
1864
|
}
|
|
1883
|
-
let text
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
fetchImpl
|
|
1891
|
-
});
|
|
1892
|
-
log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
|
|
1893
|
-
text = augmentPrompt(rawText, relPaths, labelAttachments(attachments));
|
|
1894
|
-
} catch (err) {
|
|
1895
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1896
|
-
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
1897
|
-
return -1;
|
|
1898
|
-
}
|
|
1865
|
+
let text;
|
|
1866
|
+
try {
|
|
1867
|
+
text = await buildPrompt(config, rawText, attachments, log, fetchImpl);
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1870
|
+
await worker2.emitEvent("turn_error", { message: `attachment download failed: ${message}` });
|
|
1871
|
+
return -1;
|
|
1899
1872
|
}
|
|
1900
|
-
const
|
|
1901
|
-
if (!result) return -1;
|
|
1902
|
-
const { child, tail } = result;
|
|
1903
|
-
const stdout = child.stdout;
|
|
1904
|
-
const exited = waitExit(child);
|
|
1905
|
-
let timedOut = false;
|
|
1873
|
+
const abort = new AbortController();
|
|
1906
1874
|
const ceiling = turnTimeoutMs();
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1875
|
+
let timer;
|
|
1876
|
+
const timeout = new Promise((resolve) => {
|
|
1877
|
+
timer = setTimeout(() => resolve(TIMEOUT), ceiling);
|
|
1878
|
+
});
|
|
1879
|
+
const interrupted = new Promise((resolve) => {
|
|
1880
|
+
if (externalSignal?.aborted) resolve(ABORTED);
|
|
1881
|
+
else externalSignal?.addEventListener("abort", () => resolve(ABORTED), { once: true });
|
|
1882
|
+
});
|
|
1912
1883
|
try {
|
|
1913
|
-
const
|
|
1914
|
-
|
|
1884
|
+
const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
|
|
1885
|
+
const consume = streamSdkEvents(messages, worker2);
|
|
1886
|
+
void consume.catch(() => {
|
|
1915
1887
|
});
|
|
1916
|
-
await Promise.race([
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
await worker2.emitEvent("turn_error", {
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
});
|
|
1888
|
+
const outcome = await Promise.race([consume, timeout, interrupted]);
|
|
1889
|
+
if (outcome === ABORTED) {
|
|
1890
|
+
log("[abort] interrupted by user");
|
|
1891
|
+
abort.abort();
|
|
1892
|
+
await worker2.emitEvent("turn_error", { message: "interrupted by user" });
|
|
1893
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1894
|
+
return -1;
|
|
1924
1895
|
}
|
|
1925
|
-
|
|
1896
|
+
if (outcome === TIMEOUT) {
|
|
1897
|
+
log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
|
|
1898
|
+
abort.abort();
|
|
1899
|
+
await worker2.emitEvent("turn_error", { message: `turn exceeded ${ceiling}ms \u2014 claude aborted` });
|
|
1900
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1901
|
+
return -1;
|
|
1902
|
+
}
|
|
1903
|
+
return await finalizeTurn(outcome, worker2, log);
|
|
1926
1904
|
} catch (err) {
|
|
1927
1905
|
const message = err instanceof Error ? err.message : String(err);
|
|
1928
1906
|
log(`[run] ${message}`);
|
|
1929
|
-
await worker2.emitEvent("turn_error", { message
|
|
1907
|
+
await worker2.emitEvent("turn_error", { message });
|
|
1908
|
+
await worker2.emitEvent("turn_complete", {});
|
|
1930
1909
|
return -1;
|
|
1931
1910
|
} finally {
|
|
1932
|
-
clearTimeout(
|
|
1911
|
+
clearTimeout(timer);
|
|
1933
1912
|
}
|
|
1934
1913
|
};
|
|
1935
1914
|
|
|
1936
1915
|
// src/session/runner.ts
|
|
1937
|
-
var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
|
|
1916
|
+
var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onReady) => {
|
|
1938
1917
|
const es = new ESCtor(url);
|
|
1939
1918
|
es.onopen = onReady;
|
|
1940
1919
|
es.onmessage = (e) => {
|
|
@@ -1943,6 +1922,8 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
|
|
|
1943
1922
|
if (seen.has(ev.sequence)) return;
|
|
1944
1923
|
seen.add(ev.sequence);
|
|
1945
1924
|
if (ev.type === "user_message") onUserMessage(ev);
|
|
1925
|
+
else if (ev.type === "system" && ev.payload?.action === "interrupt")
|
|
1926
|
+
onInterrupt();
|
|
1946
1927
|
} catch {
|
|
1947
1928
|
}
|
|
1948
1929
|
};
|
|
@@ -1964,9 +1945,9 @@ var restoreTty = () => {
|
|
|
1964
1945
|
};
|
|
1965
1946
|
var runDaemon = async (config, deps, signal) => {
|
|
1966
1947
|
const log = deps.log ?? ((m) => console.log(`[#${config.sessionId} ${config.name}] ${m}`));
|
|
1967
|
-
const
|
|
1948
|
+
const qf = deps.queryFn ?? query2;
|
|
1968
1949
|
const ESCtor = deps.eventSourceImpl ?? EventSource;
|
|
1969
|
-
log(`bin: ${
|
|
1950
|
+
log(`bin: ${claudeExecutable() ?? "(sdk bundled)"} cwd: ${config.worktreePath}`);
|
|
1970
1951
|
log(`runtime env keys: ${maskedEnvKeys(deps.env)}`);
|
|
1971
1952
|
const state = {
|
|
1972
1953
|
firstSpawnDone: findTranscriptPath(config.agentSessionId) !== null,
|
|
@@ -1975,6 +1956,7 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1975
1956
|
const pendingQueue = [];
|
|
1976
1957
|
let busy = false;
|
|
1977
1958
|
let lastActivity = Date.now();
|
|
1959
|
+
let currentTurn;
|
|
1978
1960
|
const drain = async () => {
|
|
1979
1961
|
if (busy) return;
|
|
1980
1962
|
busy = true;
|
|
@@ -1984,16 +1966,20 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
1984
1966
|
if (!msg) break;
|
|
1985
1967
|
const resuming = state.firstSpawnDone;
|
|
1986
1968
|
log(`\u25B6 msg #${msg.sequence} (${resuming ? "resume" : "first"})`);
|
|
1969
|
+
const turnAbort = new AbortController();
|
|
1970
|
+
currentTurn = turnAbort;
|
|
1987
1971
|
const code = await runTurn(
|
|
1988
1972
|
config,
|
|
1989
1973
|
deps.worker,
|
|
1990
1974
|
msg,
|
|
1991
1975
|
resuming,
|
|
1992
|
-
|
|
1976
|
+
qf,
|
|
1993
1977
|
log,
|
|
1994
1978
|
deps.env,
|
|
1995
|
-
deps.fetchImpl
|
|
1979
|
+
deps.fetchImpl,
|
|
1980
|
+
turnAbort.signal
|
|
1996
1981
|
);
|
|
1982
|
+
currentTurn = void 0;
|
|
1997
1983
|
state.firstSpawnDone = true;
|
|
1998
1984
|
lastActivity = Date.now();
|
|
1999
1985
|
log(`\u2714 msg #${msg.sequence}${code === 0 ? "" : ` (exit ${code})`}`);
|
|
@@ -2014,6 +2000,13 @@ var runDaemon = async (config, deps, signal) => {
|
|
|
2014
2000
|
pendingQueue.push(ev);
|
|
2015
2001
|
void drain();
|
|
2016
2002
|
},
|
|
2003
|
+
() => {
|
|
2004
|
+
lastActivity = Date.now();
|
|
2005
|
+
if (currentTurn) {
|
|
2006
|
+
log("interrupt \u2014 aborting current turn");
|
|
2007
|
+
currentTurn.abort();
|
|
2008
|
+
}
|
|
2009
|
+
},
|
|
2017
2010
|
// Report active only now that we're subscribed — before this, a message
|
|
2018
2011
|
// sent right after spawn would be missed (no replay on the live stream).
|
|
2019
2012
|
() => {
|
|
@@ -2194,6 +2187,7 @@ var session = defineCommand({
|
|
|
2194
2187
|
resume: sessionResumeCommand,
|
|
2195
2188
|
stop: sessionStopCommand,
|
|
2196
2189
|
clear: sessionClearCommand,
|
|
2190
|
+
abort: sessionAbortCommand,
|
|
2197
2191
|
rename: sessionRenameCommand,
|
|
2198
2192
|
rm: sessionRmCommand,
|
|
2199
2193
|
send: sessionSendCommand,
|
|
@@ -2345,11 +2339,12 @@ var task = defineCommand({
|
|
|
2345
2339
|
import { hostname as osHostname } from "node:os";
|
|
2346
2340
|
|
|
2347
2341
|
// src/worker/daemon.ts
|
|
2348
|
-
import { spawn
|
|
2342
|
+
import { spawn } from "node:child_process";
|
|
2349
2343
|
import { randomUUID } from "node:crypto";
|
|
2350
2344
|
import { existsSync as existsSync4 } from "node:fs";
|
|
2351
2345
|
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2352
2346
|
import { fileURLToPath } from "node:url";
|
|
2347
|
+
import { query as query3 } from "@anthropic-ai/claude-agent-sdk";
|
|
2353
2348
|
|
|
2354
2349
|
// src/session/runner/title.ts
|
|
2355
2350
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
@@ -2357,7 +2352,7 @@ var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NON
|
|
|
2357
2352
|
var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/["'`]/g, "").replace(/^\s*(title|session)\s*[:-]\s*/i, "").replace(/\s+/g, " ").trim().slice(0, 40).trim();
|
|
2358
2353
|
var TITLE_TIMEOUT_MS = 3e4;
|
|
2359
2354
|
var generateTitle = async (input) => {
|
|
2360
|
-
const { worktreePath, userText, assistantText,
|
|
2355
|
+
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2361
2356
|
if (`${userText}${assistantText}`.trim().length < 4) return null;
|
|
2362
2357
|
const exchange = [
|
|
2363
2358
|
`User: ${clip(userText, 800)}`,
|
|
@@ -2365,44 +2360,31 @@ var generateTitle = async (input) => {
|
|
|
2365
2360
|
].filter(Boolean).join("\n");
|
|
2366
2361
|
const prompt = `Reply with ONLY a 2-5 word title (no quotes, no punctuation) summarizing this coding session. If it is too short or generic to title meaningfully, reply with exactly NONE:
|
|
2367
2362
|
${exchange}`;
|
|
2368
|
-
const
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
};
|
|
2377
|
-
let child;
|
|
2378
|
-
try {
|
|
2379
|
-
child = spawnImpl(claudeBin(), ["--print", "--dangerously-skip-permissions", prompt], {
|
|
2363
|
+
const exe = claudeExecutable();
|
|
2364
|
+
const abort = new AbortController();
|
|
2365
|
+
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
2366
|
+
let out = "";
|
|
2367
|
+
try {
|
|
2368
|
+
const messages = queryFn({
|
|
2369
|
+
prompt,
|
|
2370
|
+
options: {
|
|
2380
2371
|
cwd: worktreePath,
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
}
|
|
2387
|
-
const timer = setTimeout(() => {
|
|
2388
|
-
try {
|
|
2389
|
-
child.kill();
|
|
2390
|
-
} catch {
|
|
2372
|
+
permissionMode: "bypassPermissions",
|
|
2373
|
+
allowedTools: [],
|
|
2374
|
+
includePartialMessages: false,
|
|
2375
|
+
abortController: abort,
|
|
2376
|
+
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
2391
2377
|
}
|
|
2392
|
-
finish("");
|
|
2393
|
-
}, TITLE_TIMEOUT_MS);
|
|
2394
|
-
child.stdout?.on("data", (d) => {
|
|
2395
|
-
buf += String(d);
|
|
2396
|
-
});
|
|
2397
|
-
child.on("exit", () => {
|
|
2398
|
-
clearTimeout(timer);
|
|
2399
|
-
finish(buf);
|
|
2400
|
-
});
|
|
2401
|
-
child.on("error", () => {
|
|
2402
|
-
clearTimeout(timer);
|
|
2403
|
-
finish("");
|
|
2404
2378
|
});
|
|
2405
|
-
|
|
2379
|
+
for await (const m of messages) {
|
|
2380
|
+
if (m.type === "result" && typeof m.result === "string")
|
|
2381
|
+
out = m.result;
|
|
2382
|
+
}
|
|
2383
|
+
} catch {
|
|
2384
|
+
return null;
|
|
2385
|
+
} finally {
|
|
2386
|
+
clearTimeout(timer);
|
|
2387
|
+
}
|
|
2406
2388
|
const title = sanitizeTitle(out);
|
|
2407
2389
|
return !title || isDeclined(title) ? null : title;
|
|
2408
2390
|
};
|
|
@@ -2468,7 +2450,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2468
2450
|
};
|
|
2469
2451
|
const spawnChild = (sessionId, worktreePath) => {
|
|
2470
2452
|
if (children.has(sessionId)) return;
|
|
2471
|
-
const child =
|
|
2453
|
+
const child = spawn(process.execPath, [binPath(), "session", "run", String(sessionId)], {
|
|
2472
2454
|
detached: true,
|
|
2473
2455
|
stdio: "inherit",
|
|
2474
2456
|
env: {
|
|
@@ -2528,7 +2510,7 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2528
2510
|
worktreePath,
|
|
2529
2511
|
userText: exchange.userText,
|
|
2530
2512
|
assistantText: exchange.assistantText,
|
|
2531
|
-
|
|
2513
|
+
queryFn: query3
|
|
2532
2514
|
});
|
|
2533
2515
|
if (!title) return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2534
2516
|
await client.sessions.setName(sessionId, title, cfg.apiToken);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesscap/baton-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"baton": "tsx src/index.ts",
|
|
14
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
14
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --external:@anthropic-ai/claude-agent-sdk --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
15
15
|
"prepublishOnly": "pnpm build",
|
|
16
16
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
17
17
|
"test": "tsx --test 'src/**/*.test.ts'"
|
|
@@ -24,5 +24,8 @@
|
|
|
24
24
|
"eventsource": "^4.1.0",
|
|
25
25
|
"tsx": "^4.22.4",
|
|
26
26
|
"typescript": "^6.0.3"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.161"
|
|
27
30
|
}
|
|
28
31
|
}
|