@lesscap/baton-cli 0.7.2 → 0.7.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/README.md +18 -8
- package/dist/baton.mjs +353 -128
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,8 +24,12 @@ in (the worker spawns it to run each turn). Override the path with
|
|
|
24
24
|
cd /path/to/your-repo
|
|
25
25
|
|
|
26
26
|
# 2. register this machine as a worker for a project
|
|
27
|
-
baton worker register
|
|
28
|
-
|
|
27
|
+
baton worker register \
|
|
28
|
+
--url https://baton.fmap.dev/api \
|
|
29
|
+
--project <projectId> \
|
|
30
|
+
--name my-machine \
|
|
31
|
+
--baseBranch main
|
|
32
|
+
# → generates ./.baton.json with server, project, base branch, and worker token
|
|
29
33
|
|
|
30
34
|
# 3. run the worker daemon (foreground; Ctrl-C to stop)
|
|
31
35
|
baton worker run
|
|
@@ -35,12 +39,18 @@ The worker authenticates with its own bearer token — **no login needed**. Once
|
|
|
35
39
|
running, it shows up (alive) under the project's workers in the web back-office;
|
|
36
40
|
new sessions / inbound DingTalk messages are then executed by it.
|
|
37
41
|
|
|
38
|
-
`.baton.json` is per-directory local state (gitignored by convention)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
`.baton.json` is per-directory local state (gitignored by convention) and is
|
|
43
|
+
generated by `baton worker register`; `baton init` is optional. `--baseBranch`
|
|
44
|
+
wins over `BATON_BASE_BRANCH`, then an existing saved value. If none is set,
|
|
45
|
+
registration records the repo's current branch, falling back to `main`.
|
|
46
|
+
|
|
47
|
+
Before creating each new session worktree, the worker fetches that branch and
|
|
48
|
+
forks from the updated `origin/<branch>` ref without moving the source checkout.
|
|
49
|
+
Repositories without an `origin` use their local branch. Environments that keep
|
|
50
|
+
Git credentials in a separate broker can set `BATON_GIT_SYNC_BIN` to a trusted
|
|
51
|
+
executable; Baton invokes it as `<executable> <repo> <branch>`, then verifies the
|
|
52
|
+
updated remote-tracking ref before creating the worktree. The helper must print
|
|
53
|
+
the synced commit hash to stdout.
|
|
44
54
|
|
|
45
55
|
## Other commands
|
|
46
56
|
|
package/dist/baton.mjs
CHANGED
|
@@ -4684,11 +4684,11 @@ var channelClient = (baseUrl) => {
|
|
|
4684
4684
|
if (!res.ok) throw new Error(`GET channel \u2192 ${res.status}: ${await res.text()}`);
|
|
4685
4685
|
return await res.json();
|
|
4686
4686
|
},
|
|
4687
|
-
update: async (channelId,
|
|
4687
|
+
update: async (channelId, patch) => {
|
|
4688
4688
|
const res = await fetch(u(`/channels/${channelId}`), {
|
|
4689
4689
|
method: "PATCH",
|
|
4690
4690
|
headers: JSON_CT,
|
|
4691
|
-
body: JSON.stringify(
|
|
4691
|
+
body: JSON.stringify(patch)
|
|
4692
4692
|
});
|
|
4693
4693
|
if (!res.ok) throw new Error(`PATCH channel \u2192 ${res.status}: ${await res.text()}`);
|
|
4694
4694
|
return await res.json();
|
|
@@ -4797,10 +4797,6 @@ var saveProjectConfig = (path, config) => {
|
|
|
4797
4797
|
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
4798
4798
|
`, "utf8");
|
|
4799
4799
|
};
|
|
4800
|
-
var patch = (path, fn) => {
|
|
4801
|
-
saveProjectConfig(path, fn(loadProjectConfig(path)));
|
|
4802
|
-
};
|
|
4803
|
-
var setWorker = (path, w) => patch(path, (cfg) => ({ ...cfg, worker: w }));
|
|
4804
4800
|
var requireBase = (cfg) => {
|
|
4805
4801
|
if (!cfg.server || !cfg.project || !cfg.worker)
|
|
4806
4802
|
throw new Error("project-config missing server/project/worker \u2014 run `baton worker register`");
|
|
@@ -4811,6 +4807,7 @@ var viewWorker = (cfg) => {
|
|
|
4811
4807
|
return {
|
|
4812
4808
|
server,
|
|
4813
4809
|
projectId: project2,
|
|
4810
|
+
baseBranch: cfg.baseBranch,
|
|
4814
4811
|
workerId: worker2.id,
|
|
4815
4812
|
agentKind: worker2.agentKind ?? "claude-code",
|
|
4816
4813
|
name: worker2.name,
|
|
@@ -4821,6 +4818,7 @@ var viewWorker = (cfg) => {
|
|
|
4821
4818
|
var worktreeConfig = (cfg) => ({
|
|
4822
4819
|
server: cfg.server,
|
|
4823
4820
|
project: cfg.projectId,
|
|
4821
|
+
baseBranch: cfg.baseBranch,
|
|
4824
4822
|
worker: {
|
|
4825
4823
|
id: cfg.workerId,
|
|
4826
4824
|
name: cfg.name,
|
|
@@ -4944,7 +4942,7 @@ var projectClient = (baseUrl) => {
|
|
|
4944
4942
|
create: (input) => request(u("/projects"), { method: "POST", body: input }),
|
|
4945
4943
|
listByWorkspace: (workspaceId) => request(u(`/workspaces/${workspaceId}/projects`), { method: "GET" }),
|
|
4946
4944
|
get: (id) => request(u(`/projects/${id}`), { method: "GET" }),
|
|
4947
|
-
update: (id,
|
|
4945
|
+
update: (id, patch) => request(u(`/projects/${id}`), { method: "PATCH", body: patch }),
|
|
4948
4946
|
remove: (id) => request(u(`/projects/${id}`), { method: "DELETE" })
|
|
4949
4947
|
};
|
|
4950
4948
|
};
|
|
@@ -4967,7 +4965,7 @@ var requirementClient = (baseUrl) => {
|
|
|
4967
4965
|
listByProject: (projectId) => request(u(`/projects/${projectId}/requirements`), { method: "GET" }),
|
|
4968
4966
|
get: (id) => request(u(`/requirements/${id}`), { method: "GET" }),
|
|
4969
4967
|
getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "requirement"),
|
|
4970
|
-
update: (id,
|
|
4968
|
+
update: (id, patch) => request(u(`/requirements/${id}`), { method: "PATCH", body: patch }),
|
|
4971
4969
|
setStatus: (id, status) => request(u(`/requirements/${id}`), { method: "PATCH", body: { status } }),
|
|
4972
4970
|
remove: (id) => request(u(`/requirements/${id}`), { method: "DELETE" })
|
|
4973
4971
|
};
|
|
@@ -5026,6 +5024,7 @@ var sessionsClient = (baseUrl) => {
|
|
|
5026
5024
|
const matches = all.filter((s) => s.name === name);
|
|
5027
5025
|
return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
|
|
5028
5026
|
},
|
|
5027
|
+
listEvents: (id) => request(u(`/sessions/${id}/events`), { method: "GET" }),
|
|
5029
5028
|
sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
|
|
5030
5029
|
method: "POST",
|
|
5031
5030
|
body: {
|
|
@@ -5061,7 +5060,7 @@ var taskClient = (baseUrl) => {
|
|
|
5061
5060
|
listByRequirement: (requirementId) => request(u(`/requirements/${requirementId}/tasks`), { method: "GET" }),
|
|
5062
5061
|
get: (id) => request(u(`/tasks/${id}`), { method: "GET" }),
|
|
5063
5062
|
getByCode: (projectId, code) => fetchItemByCode(baseUrl, projectId, code, "task"),
|
|
5064
|
-
update: (id,
|
|
5063
|
+
update: (id, patch) => request(u(`/tasks/${id}`), { method: "PATCH", body: patch }),
|
|
5065
5064
|
setStatus: (id, status) => request(u(`/tasks/${id}`), { method: "PATCH", body: { status } }),
|
|
5066
5065
|
remove: (id) => request(u(`/tasks/${id}`), { method: "DELETE" }),
|
|
5067
5066
|
listComments: (id) => request(u(`/tasks/${id}/comments`), { method: "GET" }),
|
|
@@ -5097,7 +5096,7 @@ var workspaceClient = (baseUrl) => {
|
|
|
5097
5096
|
create: (input) => request(u("/workspaces"), { method: "POST", body: input }),
|
|
5098
5097
|
list: () => request(u("/workspaces"), { method: "GET" }),
|
|
5099
5098
|
get: (id) => request(u(`/workspaces/${id}`), { method: "GET" }),
|
|
5100
|
-
update: (id,
|
|
5099
|
+
update: (id, patch) => request(u(`/workspaces/${id}`), { method: "PATCH", body: patch }),
|
|
5101
5100
|
remove: (id) => request(u(`/workspaces/${id}`), { method: "DELETE" })
|
|
5102
5101
|
};
|
|
5103
5102
|
};
|
|
@@ -5855,11 +5854,11 @@ var loopSetCommand = defineCommand({
|
|
|
5855
5854
|
},
|
|
5856
5855
|
run: async ({ args }) => {
|
|
5857
5856
|
const c = clientFor(args);
|
|
5858
|
-
const
|
|
5859
|
-
if (args.message !== void 0)
|
|
5860
|
-
if (args.every !== void 0 && args.every !== "")
|
|
5861
|
-
if (args.name !== void 0)
|
|
5862
|
-
const loop2 = await c.loops.update(Number(args.loop),
|
|
5857
|
+
const patch = {};
|
|
5858
|
+
if (args.message !== void 0) patch.message = args.message;
|
|
5859
|
+
if (args.every !== void 0 && args.every !== "") patch.intervalSec = parseDuration(args.every);
|
|
5860
|
+
if (args.name !== void 0) patch.name = args.name;
|
|
5861
|
+
const loop2 = await c.loops.update(Number(args.loop), patch);
|
|
5863
5862
|
console.log(renderOne(loop2, fmtLoop, Boolean(args.json)));
|
|
5864
5863
|
}
|
|
5865
5864
|
});
|
|
@@ -5882,7 +5881,7 @@ var loop = defineCommand({
|
|
|
5882
5881
|
var createProject = (c, input, json) => c.projects.create(input).then((p) => renderOne(p, fmtProject, json));
|
|
5883
5882
|
var listProjects = (c, workspaceId, json) => c.projects.listByWorkspace(workspaceId).then((ps) => renderList(ps, fmtProject, json));
|
|
5884
5883
|
var getProject = (c, id, json) => c.projects.get(id).then((p) => renderOne(p, fmtProject, json));
|
|
5885
|
-
var updateProject = (c, id,
|
|
5884
|
+
var updateProject = (c, id, patch, json) => c.projects.update(id, patch).then((p) => renderOne(p, fmtProject, json));
|
|
5886
5885
|
var removeProject = (c, id, json) => c.projects.remove(id).then(() => removed("project", id, json));
|
|
5887
5886
|
var project = defineCommand({
|
|
5888
5887
|
meta: { name: "project", description: "manage projects" },
|
|
@@ -5932,13 +5931,13 @@ var project = defineCommand({
|
|
|
5932
5931
|
...common
|
|
5933
5932
|
},
|
|
5934
5933
|
run: async ({ args }) => {
|
|
5935
|
-
const
|
|
5934
|
+
const patch = {
|
|
5936
5935
|
...args.name !== void 0 ? { name: args.name } : {},
|
|
5937
5936
|
...args.desc !== void 0 ? { description: args.desc } : {}
|
|
5938
5937
|
};
|
|
5939
|
-
if (Object.keys(
|
|
5938
|
+
if (Object.keys(patch).length === 0) throw new Error("pass --name and/or --desc");
|
|
5940
5939
|
console.log(
|
|
5941
|
-
await updateProject(clientFor(args), Number(args.id),
|
|
5940
|
+
await updateProject(clientFor(args), Number(args.id), patch, Boolean(args.json))
|
|
5942
5941
|
);
|
|
5943
5942
|
}
|
|
5944
5943
|
}),
|
|
@@ -6131,7 +6130,7 @@ var relay = defineCommand({
|
|
|
6131
6130
|
|
|
6132
6131
|
// src/commands/requirement.ts
|
|
6133
6132
|
var createRequirement = (c, input, json) => c.requirements.create(input).then((r) => renderOne(r, fmtRequirement, json));
|
|
6134
|
-
var updateRequirement = (c, id,
|
|
6133
|
+
var updateRequirement = (c, id, patch, json) => c.requirements.update(id, patch).then((r) => renderOne(r, fmtRequirement, json));
|
|
6135
6134
|
var listRequirements = (c, projectId, json) => c.requirements.listByProject(projectId).then((rs) => renderList(rs, fmtRequirement, json));
|
|
6136
6135
|
var getRequirement = (c, id, json) => c.requirements.get(id).then((r) => renderOne(r, fmtRequirement, json));
|
|
6137
6136
|
var setRequirementStatus = (c, id, status, json) => c.requirements.setStatus(id, status).then((r) => renderOne(r, fmtRequirement, json));
|
|
@@ -6174,14 +6173,14 @@ var requirement = defineCommand({
|
|
|
6174
6173
|
...common
|
|
6175
6174
|
},
|
|
6176
6175
|
run: async ({ args }) => {
|
|
6177
|
-
const
|
|
6176
|
+
const patch = {
|
|
6178
6177
|
...args.title !== void 0 ? { title: args.title } : {},
|
|
6179
6178
|
...args.body !== void 0 ? { body: args.body } : {}
|
|
6180
6179
|
};
|
|
6181
|
-
if (Object.keys(
|
|
6180
|
+
if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
|
|
6182
6181
|
const c = clientFor(args);
|
|
6183
6182
|
const id = await resolveByCode(c, resolveProjectId(args), args.code);
|
|
6184
|
-
console.log(await updateRequirement(c, id,
|
|
6183
|
+
console.log(await updateRequirement(c, id, patch, Boolean(args.json)));
|
|
6185
6184
|
}
|
|
6186
6185
|
}),
|
|
6187
6186
|
ls: defineCommand({
|
|
@@ -6464,6 +6463,7 @@ var findTranscriptPath = (agentSessionId) => {
|
|
|
6464
6463
|
};
|
|
6465
6464
|
var isRecord = (v) => typeof v === "object" && v !== null;
|
|
6466
6465
|
var textOf = (rec) => {
|
|
6466
|
+
if (typeof rec.text === "string") return rec.text.trim();
|
|
6467
6467
|
const message = rec.message;
|
|
6468
6468
|
const content = isRecord(message) ? message.content : rec.content;
|
|
6469
6469
|
if (typeof content === "string") return content.trim();
|
|
@@ -6494,6 +6494,38 @@ var parseFirstExchange = (content) => {
|
|
|
6494
6494
|
}
|
|
6495
6495
|
return userText || assistantText ? { userText, assistantText } : null;
|
|
6496
6496
|
};
|
|
6497
|
+
var canonicalAgentMessage = (payload) => {
|
|
6498
|
+
if (!isRecord(payload) || !["item.started", "item.updated", "item.completed"].includes(String(payload.type)))
|
|
6499
|
+
return null;
|
|
6500
|
+
const item = payload.item;
|
|
6501
|
+
if (!isRecord(item) || item.type !== "agent_message" || typeof item.id !== "string") return null;
|
|
6502
|
+
const text = textOf(item);
|
|
6503
|
+
return text ? { id: item.id, text } : null;
|
|
6504
|
+
};
|
|
6505
|
+
var parseFirstExchangeFromEvents = (events) => {
|
|
6506
|
+
let userText = "";
|
|
6507
|
+
const itemOrder = [];
|
|
6508
|
+
const agentText = /* @__PURE__ */ new Map();
|
|
6509
|
+
const legacyText = [];
|
|
6510
|
+
for (const event of events) {
|
|
6511
|
+
if (!userText && event.type === "user_message" && isRecord(event.payload)) {
|
|
6512
|
+
if (typeof event.payload.text === "string") userText = event.payload.text.trim();
|
|
6513
|
+
continue;
|
|
6514
|
+
}
|
|
6515
|
+
if (!userText) continue;
|
|
6516
|
+
if (event.type === "turn_complete" || event.type === "turn_error") break;
|
|
6517
|
+
const agent = event.type === "agent_event" ? canonicalAgentMessage(event.payload) : null;
|
|
6518
|
+
if (agent) {
|
|
6519
|
+
if (!agentText.has(agent.id)) itemOrder.push(agent.id);
|
|
6520
|
+
agentText.set(agent.id, agent.text);
|
|
6521
|
+
} else if (event.type === "sdk_event" && isRecord(event.payload)) {
|
|
6522
|
+
const text = event.payload.type === "assistant" ? textOf(event.payload) : "";
|
|
6523
|
+
if (text) legacyText.push(text);
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
const assistantText = itemOrder.map((id) => agentText.get(id) ?? "").filter(Boolean).join("\n") || legacyText.join("\n");
|
|
6527
|
+
return userText || assistantText ? { userText, assistantText } : null;
|
|
6528
|
+
};
|
|
6497
6529
|
var readFirstExchange = (agentSessionId) => {
|
|
6498
6530
|
const path = findTranscriptPath(agentSessionId);
|
|
6499
6531
|
if (!path) return null;
|
|
@@ -7423,7 +7455,7 @@ var session = defineCommand({
|
|
|
7423
7455
|
|
|
7424
7456
|
// src/commands/task.ts
|
|
7425
7457
|
var createTask = (c, input, json) => c.tasks.create(input).then((t) => renderOne(t, fmtTask, json));
|
|
7426
|
-
var updateTask = (c, id,
|
|
7458
|
+
var updateTask = (c, id, patch, json) => c.tasks.update(id, patch).then((t) => renderOne(t, fmtTask, json));
|
|
7427
7459
|
var listTasks = (c, requirementId, json) => c.tasks.listByRequirement(requirementId).then((ts) => renderList(ts, fmtTask, json));
|
|
7428
7460
|
var getTask = (c, id, json) => c.tasks.get(id).then((t) => renderOne(t, fmtTask, json));
|
|
7429
7461
|
var setTaskStatus = (c, id, status, json) => c.tasks.setStatus(id, status).then((t) => renderOne(t, fmtTask, json));
|
|
@@ -7477,14 +7509,14 @@ var task = defineCommand({
|
|
|
7477
7509
|
...common
|
|
7478
7510
|
},
|
|
7479
7511
|
run: async ({ args }) => {
|
|
7480
|
-
const
|
|
7512
|
+
const patch = {
|
|
7481
7513
|
...args.title !== void 0 ? { title: args.title } : {},
|
|
7482
7514
|
...args.body !== void 0 ? { body: args.body } : {}
|
|
7483
7515
|
};
|
|
7484
|
-
if (Object.keys(
|
|
7516
|
+
if (Object.keys(patch).length === 0) throw new Error("pass --title and/or --body");
|
|
7485
7517
|
const c = clientFor(args);
|
|
7486
7518
|
const id = await resolveTaskByCode(c, resolveProjectId(args), args.code);
|
|
7487
|
-
console.log(await updateTask(c, id,
|
|
7519
|
+
console.log(await updateTask(c, id, patch, Boolean(args.json)));
|
|
7488
7520
|
}
|
|
7489
7521
|
}),
|
|
7490
7522
|
ls: defineCommand({
|
|
@@ -7583,6 +7615,155 @@ var task = defineCommand({
|
|
|
7583
7615
|
// src/commands/worker/index.ts
|
|
7584
7616
|
import { hostname as osHostname } from "node:os";
|
|
7585
7617
|
|
|
7618
|
+
// src/session/worktree.ts
|
|
7619
|
+
import { execFile, execFileSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
7620
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
7621
|
+
import { dirname, isAbsolute as isAbsolute2, resolve } from "node:path";
|
|
7622
|
+
import { promisify } from "node:util";
|
|
7623
|
+
var git = (repo, args) => spawnSync2("git", ["-C", repo, ...args], {
|
|
7624
|
+
stdio: "pipe",
|
|
7625
|
+
encoding: "utf8"
|
|
7626
|
+
});
|
|
7627
|
+
var commandError = (result) => {
|
|
7628
|
+
const raw = result.stderr || result.stdout || result.error?.message || `exit ${result.status}`;
|
|
7629
|
+
return raw.trim().replace(/(https?:\/\/)[^/@\s]+@/g, "$1***@");
|
|
7630
|
+
};
|
|
7631
|
+
var execFileAsync = promisify(execFile);
|
|
7632
|
+
var DEFAULT_SYNC_TIMEOUT_MS = 6e4;
|
|
7633
|
+
var asyncCommandError = (error, timeoutMs) => {
|
|
7634
|
+
const failure = error;
|
|
7635
|
+
if (failure.killed || failure.code === "ETIMEDOUT") return `timed out after ${timeoutMs}ms`;
|
|
7636
|
+
const raw = failure.stderr || failure.stdout || failure.message || String(error);
|
|
7637
|
+
return raw.trim().replace(/(https?:\/\/)[^/@\s]+@/g, "$1***@");
|
|
7638
|
+
};
|
|
7639
|
+
var validateBaseBranch = (branch) => {
|
|
7640
|
+
const normalized = branch.trim();
|
|
7641
|
+
const valid = spawnSync2("git", ["check-ref-format", "--branch", normalized], {
|
|
7642
|
+
stdio: "pipe",
|
|
7643
|
+
encoding: "utf8"
|
|
7644
|
+
});
|
|
7645
|
+
if (valid.status !== 0)
|
|
7646
|
+
throw new Error(`invalid base branch "${normalized}": ${commandError(valid)}`);
|
|
7647
|
+
return normalized;
|
|
7648
|
+
};
|
|
7649
|
+
var createWorktree = (input) => {
|
|
7650
|
+
if (existsSync4(input.worktreePath))
|
|
7651
|
+
throw new Error(`worktree path already exists: ${input.worktreePath}`);
|
|
7652
|
+
const head = spawnSync2("git", ["-C", input.repo, "rev-parse", "--verify", "-q", "HEAD"], {
|
|
7653
|
+
stdio: "pipe",
|
|
7654
|
+
encoding: "utf8"
|
|
7655
|
+
});
|
|
7656
|
+
if (head.status !== 0)
|
|
7657
|
+
throw new Error(
|
|
7658
|
+
`repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
|
|
7659
|
+
);
|
|
7660
|
+
mkdirSync3(dirname(input.worktreePath), { recursive: true });
|
|
7661
|
+
const branch = `baton/${input.sessionCode}`;
|
|
7662
|
+
const r = spawnSync2(
|
|
7663
|
+
"git",
|
|
7664
|
+
["-C", input.repo, "worktree", "add", "-b", branch, input.worktreePath, input.base],
|
|
7665
|
+
{ stdio: "pipe", encoding: "utf8" }
|
|
7666
|
+
);
|
|
7667
|
+
if (r.status !== 0) {
|
|
7668
|
+
throw new Error(`git worktree add failed: ${r.stderr || r.stdout || `exit ${r.status}`}`);
|
|
7669
|
+
}
|
|
7670
|
+
};
|
|
7671
|
+
var repoHeadBranch = (repo) => {
|
|
7672
|
+
const r = git(repo, ["symbolic-ref", "--short", "HEAD"]);
|
|
7673
|
+
const branch = r.status === 0 ? r.stdout.trim() : "";
|
|
7674
|
+
return branch || "main";
|
|
7675
|
+
};
|
|
7676
|
+
var syncBaseBranch = async (repo, branch, options = {}) => {
|
|
7677
|
+
const checkedBranch = validateBaseBranch(branch);
|
|
7678
|
+
const env = options.env ?? process.env;
|
|
7679
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SYNC_TIMEOUT_MS;
|
|
7680
|
+
const origin = git(repo, ["remote", "get-url", "origin"]);
|
|
7681
|
+
if (origin.status !== 0) {
|
|
7682
|
+
const localRef = `refs/heads/${checkedBranch}`;
|
|
7683
|
+
const verified2 = git(repo, ["rev-parse", "--verify", "-q", localRef]);
|
|
7684
|
+
if (verified2.status !== 0) throw new Error(`local base branch does not exist: ${checkedBranch}`);
|
|
7685
|
+
return localRef;
|
|
7686
|
+
}
|
|
7687
|
+
const helper = env.BATON_GIT_SYNC_BIN?.trim();
|
|
7688
|
+
const command = helper ?? "git";
|
|
7689
|
+
const args = helper ? [repo, checkedBranch] : [
|
|
7690
|
+
"-C",
|
|
7691
|
+
repo,
|
|
7692
|
+
"fetch",
|
|
7693
|
+
"--no-tags",
|
|
7694
|
+
"origin",
|
|
7695
|
+
`+refs/heads/${checkedBranch}:refs/remotes/origin/${checkedBranch}`
|
|
7696
|
+
];
|
|
7697
|
+
let synced;
|
|
7698
|
+
try {
|
|
7699
|
+
synced = await execFileAsync(command, args, {
|
|
7700
|
+
encoding: "utf8",
|
|
7701
|
+
env: { ...env, GIT_TERMINAL_PROMPT: "0" },
|
|
7702
|
+
timeout: timeoutMs
|
|
7703
|
+
});
|
|
7704
|
+
} catch (error) {
|
|
7705
|
+
const via = helper ? ` via ${helper}` : "";
|
|
7706
|
+
throw new Error(
|
|
7707
|
+
`git sync failed for origin/${checkedBranch}${via}: ${asyncCommandError(error, timeoutMs)}`
|
|
7708
|
+
);
|
|
7709
|
+
}
|
|
7710
|
+
const remoteRef = `refs/remotes/origin/${checkedBranch}`;
|
|
7711
|
+
const verified = git(repo, ["rev-parse", "--verify", "-q", remoteRef]);
|
|
7712
|
+
if (verified.status !== 0) throw new Error(`git sync succeeded but ${remoteRef} does not exist`);
|
|
7713
|
+
if (helper) {
|
|
7714
|
+
const reportedCommit = synced.stdout.trim();
|
|
7715
|
+
const actualCommit = verified.stdout.trim();
|
|
7716
|
+
if (!/^[0-9a-f]{40,64}$/i.test(reportedCommit))
|
|
7717
|
+
throw new Error("git sync helper did not report the synced commit on stdout");
|
|
7718
|
+
if (reportedCommit !== actualCommit)
|
|
7719
|
+
throw new Error(`git sync helper reported a commit that does not match ${remoteRef}`);
|
|
7720
|
+
}
|
|
7721
|
+
return remoteRef;
|
|
7722
|
+
};
|
|
7723
|
+
var ensureExcluded = (repo, pattern) => {
|
|
7724
|
+
try {
|
|
7725
|
+
const r = spawnSync2("git", ["-C", repo, "rev-parse", "--git-path", "info/exclude"], {
|
|
7726
|
+
stdio: "pipe",
|
|
7727
|
+
encoding: "utf8"
|
|
7728
|
+
});
|
|
7729
|
+
if (r.status !== 0) return;
|
|
7730
|
+
const rel = r.stdout.trim();
|
|
7731
|
+
const path = isAbsolute2(rel) ? rel : resolve(repo, rel);
|
|
7732
|
+
const current = existsSync4(path) ? readFileSync5(path, "utf8") : "";
|
|
7733
|
+
if (current.split("\n").includes(pattern)) return;
|
|
7734
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
7735
|
+
const sep = current === "" || current.endsWith("\n") ? "" : "\n";
|
|
7736
|
+
appendFileSync(path, `${sep}${pattern}
|
|
7737
|
+
`, "utf8");
|
|
7738
|
+
} catch {
|
|
7739
|
+
}
|
|
7740
|
+
};
|
|
7741
|
+
var removeWorktree = (repo, worktreePath) => {
|
|
7742
|
+
if (!existsSync4(worktreePath)) return;
|
|
7743
|
+
try {
|
|
7744
|
+
execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
|
|
7745
|
+
stdio: "pipe"
|
|
7746
|
+
});
|
|
7747
|
+
} catch {
|
|
7748
|
+
}
|
|
7749
|
+
};
|
|
7750
|
+
var restoreWorktree = async (repo, worktreePath, sessionCode, baseBranch = repoHeadBranch(repo)) => {
|
|
7751
|
+
spawnSync2("git", ["-C", repo, "worktree", "prune"], { stdio: "pipe" });
|
|
7752
|
+
mkdirSync3(dirname(worktreePath), { recursive: true });
|
|
7753
|
+
const branch = `baton/${sessionCode}`;
|
|
7754
|
+
const reuse = spawnSync2("git", ["-C", repo, "worktree", "add", worktreePath, branch], {
|
|
7755
|
+
stdio: "pipe",
|
|
7756
|
+
encoding: "utf8"
|
|
7757
|
+
});
|
|
7758
|
+
if (reuse.status === 0) return;
|
|
7759
|
+
createWorktree({
|
|
7760
|
+
repo,
|
|
7761
|
+
worktreePath,
|
|
7762
|
+
sessionCode,
|
|
7763
|
+
base: await syncBaseBranch(repo, baseBranch)
|
|
7764
|
+
});
|
|
7765
|
+
};
|
|
7766
|
+
|
|
7586
7767
|
// src/worker/watchdog.ts
|
|
7587
7768
|
var stale = (okAt, now, thresholdMs) => now - okAt > thresholdMs;
|
|
7588
7769
|
var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, now, thresholdMs);
|
|
@@ -7638,16 +7819,26 @@ var createLiveness = (deps) => {
|
|
|
7638
7819
|
import { spawn } from "node:child_process";
|
|
7639
7820
|
import { randomUUID } from "node:crypto";
|
|
7640
7821
|
import { existsSync as existsSync5 } from "node:fs";
|
|
7641
|
-
import { dirname as dirname2, join as
|
|
7822
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
7642
7823
|
import { fileURLToPath } from "node:url";
|
|
7643
7824
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
7644
7825
|
|
|
7826
|
+
// src/session/runner/title-codex.ts
|
|
7827
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
7828
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
7829
|
+
import { join as join7 } from "node:path";
|
|
7830
|
+
import { Codex as Codex2 } from "@openai/codex-sdk";
|
|
7831
|
+
|
|
7645
7832
|
// src/session/runner/title.ts
|
|
7646
7833
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
7647
7834
|
var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
|
|
7648
7835
|
var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/[*_`~#>]/g, "").replace(/["']/g, "").replace(/^\s*(?:title|session|标题)\s*[::-]\s*/i, "").replace(/^\s*(?:[-*•]|\d+[.)、])\s+/, "").replace(/\s+/g, " ").trim().replace(/(?:[。.!?,、;;]|[.!?;](?=\s|$)).*$/s, "").trim().slice(0, 30).replace(/[\s。.,,、;;::!!??.]+$/, "").trim();
|
|
7836
|
+
var titleOutcome = (raw) => {
|
|
7837
|
+
const title = sanitizeTitle(raw);
|
|
7838
|
+
return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
|
|
7839
|
+
};
|
|
7649
7840
|
var TITLE_TIMEOUT_MS = 9e4;
|
|
7650
|
-
var
|
|
7841
|
+
var buildTitlePrompt = (userText, assistantText) => {
|
|
7651
7842
|
const exchange = [
|
|
7652
7843
|
`User: ${clip(userText, 800)}`,
|
|
7653
7844
|
assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
|
|
@@ -7682,7 +7873,7 @@ var generateTitle = async (input) => {
|
|
|
7682
7873
|
let failure = "";
|
|
7683
7874
|
try {
|
|
7684
7875
|
const messages = queryFn({
|
|
7685
|
-
prompt:
|
|
7876
|
+
prompt: buildTitlePrompt(userText, assistantText),
|
|
7686
7877
|
options: {
|
|
7687
7878
|
cwd: worktreePath,
|
|
7688
7879
|
// A one-shot summarizer, not an agent: replace the ~18k-token Claude
|
|
@@ -7722,82 +7913,42 @@ var generateTitle = async (input) => {
|
|
|
7722
7913
|
const tail = stderrTail.length > 0 ? ` | stderr: ${stderrTail.join(" / ")}` : "";
|
|
7723
7914
|
return { kind: "error", reason: `${failure}${tail}` };
|
|
7724
7915
|
}
|
|
7725
|
-
|
|
7726
|
-
return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
|
|
7916
|
+
return titleOutcome(out);
|
|
7727
7917
|
};
|
|
7728
7918
|
|
|
7729
|
-
// src/session/
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
const head = spawnSync2("git", ["-C", input.repo, "rev-parse", "--verify", "-q", "HEAD"], {
|
|
7737
|
-
stdio: "pipe",
|
|
7738
|
-
encoding: "utf8"
|
|
7739
|
-
});
|
|
7740
|
-
if (head.status !== 0)
|
|
7741
|
-
throw new Error(
|
|
7742
|
-
`repo has no commits (unborn HEAD) \u2014 create an initial commit before starting sessions`
|
|
7743
|
-
);
|
|
7744
|
-
mkdirSync3(dirname(input.worktreePath), { recursive: true });
|
|
7745
|
-
const branch = `baton/${input.sessionCode}`;
|
|
7746
|
-
const r = spawnSync2(
|
|
7747
|
-
"git",
|
|
7748
|
-
["-C", input.repo, "worktree", "add", "-b", branch, input.worktreePath, input.base],
|
|
7749
|
-
{ stdio: "pipe", encoding: "utf8" }
|
|
7750
|
-
);
|
|
7751
|
-
if (r.status !== 0) {
|
|
7752
|
-
throw new Error(`git worktree add failed: ${r.stderr || r.stdout || `exit ${r.status}`}`);
|
|
7753
|
-
}
|
|
7754
|
-
};
|
|
7755
|
-
var repoHeadBranch = (repo) => {
|
|
7756
|
-
const r = spawnSync2("git", ["-C", repo, "symbolic-ref", "--short", "HEAD"], {
|
|
7757
|
-
stdio: "pipe",
|
|
7758
|
-
encoding: "utf8"
|
|
7759
|
-
});
|
|
7760
|
-
const branch = r.status === 0 ? r.stdout.trim() : "";
|
|
7761
|
-
return branch || "main";
|
|
7762
|
-
};
|
|
7763
|
-
var ensureExcluded = (repo, pattern) => {
|
|
7919
|
+
// src/session/runner/title-codex.ts
|
|
7920
|
+
var generateTitleWithCodex = async (input) => {
|
|
7921
|
+
const { userText, assistantText } = input;
|
|
7922
|
+
if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
|
|
7923
|
+
const abort = new AbortController();
|
|
7924
|
+
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
7925
|
+
let cwd = "";
|
|
7764
7926
|
try {
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7927
|
+
cwd = await mkdtemp(join7(tmpdir3(), "baton-title-"));
|
|
7928
|
+
const client = input.client ?? new Codex2();
|
|
7929
|
+
const thread = client.startThread({
|
|
7930
|
+
workingDirectory: cwd,
|
|
7931
|
+
skipGitRepoCheck: true,
|
|
7932
|
+
sandboxMode: "read-only",
|
|
7933
|
+
approvalPolicy: "never",
|
|
7934
|
+
networkAccessEnabled: false,
|
|
7935
|
+
webSearchMode: "disabled"
|
|
7768
7936
|
});
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
}
|
|
7779
|
-
|
|
7780
|
-
}
|
|
7781
|
-
var removeWorktree = (repo, worktreePath) => {
|
|
7782
|
-
if (!existsSync4(worktreePath)) return;
|
|
7783
|
-
try {
|
|
7784
|
-
execFileSync("git", ["-C", repo, "worktree", "remove", "--force", worktreePath], {
|
|
7785
|
-
stdio: "pipe"
|
|
7937
|
+
const result = await thread.run(buildTitlePrompt(userText, assistantText), {
|
|
7938
|
+
signal: abort.signal
|
|
7939
|
+
});
|
|
7940
|
+
return titleOutcome(result.finalResponse);
|
|
7941
|
+
} catch (error) {
|
|
7942
|
+
return {
|
|
7943
|
+
kind: "error",
|
|
7944
|
+
reason: abort.signal.aborted ? `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` : String(error).slice(0, 300)
|
|
7945
|
+
};
|
|
7946
|
+
} finally {
|
|
7947
|
+
clearTimeout(timer);
|
|
7948
|
+
if (cwd) await rm(cwd, { recursive: true, force: true }).catch(() => {
|
|
7786
7949
|
});
|
|
7787
|
-
} catch {
|
|
7788
7950
|
}
|
|
7789
7951
|
};
|
|
7790
|
-
var restoreWorktree = (repo, worktreePath, sessionCode) => {
|
|
7791
|
-
spawnSync2("git", ["-C", repo, "worktree", "prune"], { stdio: "pipe" });
|
|
7792
|
-
mkdirSync3(dirname(worktreePath), { recursive: true });
|
|
7793
|
-
const branch = `baton/${sessionCode}`;
|
|
7794
|
-
const reuse = spawnSync2("git", ["-C", repo, "worktree", "add", worktreePath, branch], {
|
|
7795
|
-
stdio: "pipe",
|
|
7796
|
-
encoding: "utf8"
|
|
7797
|
-
});
|
|
7798
|
-
if (reuse.status === 0) return;
|
|
7799
|
-
createWorktree({ repo, worktreePath, sessionCode, base: repoHeadBranch(repo) });
|
|
7800
|
-
};
|
|
7801
7952
|
|
|
7802
7953
|
// src/worker/proc.ts
|
|
7803
7954
|
var killProcessGroup = (proc) => {
|
|
@@ -7811,13 +7962,32 @@ var killProcessGroup = (proc) => {
|
|
|
7811
7962
|
// src/worker/session-supervisor.ts
|
|
7812
7963
|
var binPath = () => {
|
|
7813
7964
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
7814
|
-
const devShim =
|
|
7965
|
+
const devShim = join8(here, "..", "..", "bin", "baton.mjs");
|
|
7815
7966
|
return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
|
|
7816
7967
|
};
|
|
7817
7968
|
var createSessionSupervisor = (deps) => {
|
|
7818
7969
|
const { client, cfg, repo, log, hasTerminal, closeTerminal } = deps;
|
|
7819
7970
|
const worktreeDir = defaultWorktreeDir();
|
|
7971
|
+
const baseBranch = cfg.baseBranch ?? repoHeadBranch(repo);
|
|
7972
|
+
const syncBase = deps.syncBase ?? syncBaseBranch;
|
|
7973
|
+
let syncInFlight = null;
|
|
7974
|
+
const syncedBase = () => {
|
|
7975
|
+
if (syncInFlight) return syncInFlight;
|
|
7976
|
+
const pending = syncBase(repo, baseBranch);
|
|
7977
|
+
syncInFlight = pending;
|
|
7978
|
+
const clear = () => {
|
|
7979
|
+
if (syncInFlight === pending) syncInFlight = null;
|
|
7980
|
+
};
|
|
7981
|
+
void pending.then(clear, clear);
|
|
7982
|
+
return pending;
|
|
7983
|
+
};
|
|
7820
7984
|
const children = /* @__PURE__ */ new Map();
|
|
7985
|
+
const starts = /* @__PURE__ */ new Map();
|
|
7986
|
+
const startEpochs = /* @__PURE__ */ new Map();
|
|
7987
|
+
const currentStartEpoch = (sessionId) => startEpochs.get(sessionId) ?? 0;
|
|
7988
|
+
const cancelStart = (sessionId) => {
|
|
7989
|
+
startEpochs.set(sessionId, currentStartEpoch(sessionId) + 1);
|
|
7990
|
+
};
|
|
7821
7991
|
const spawnChild = (sessionId, worktreePath) => {
|
|
7822
7992
|
if (children.has(sessionId)) return;
|
|
7823
7993
|
const child = spawn(process.execPath, [binPath(), "session", "run", String(sessionId)], {
|
|
@@ -7834,35 +8004,58 @@ var createSessionSupervisor = (deps) => {
|
|
|
7834
8004
|
});
|
|
7835
8005
|
});
|
|
7836
8006
|
};
|
|
7837
|
-
const
|
|
8007
|
+
const startOne = async (sessionId, name, epoch) => {
|
|
8008
|
+
const wasCanceled = () => currentStartEpoch(sessionId) !== epoch;
|
|
8009
|
+
if (wasCanceled()) return;
|
|
7838
8010
|
if (hasTerminal(sessionId))
|
|
7839
8011
|
return log(`session #${sessionId} has an open terminal \u2014 skipping headless start`);
|
|
7840
8012
|
if (children.has(sessionId)) return log(`session #${sessionId} already running`);
|
|
7841
8013
|
const session2 = await client.sessions.get(sessionId);
|
|
8014
|
+
if (wasCanceled()) return log(`session #${sessionId} start canceled before materializing`);
|
|
7842
8015
|
let worktreePath = session2.worktreePath;
|
|
7843
8016
|
if (!session2.agentSessionId || !worktreePath) {
|
|
7844
8017
|
const sessionCode = randomUUID();
|
|
7845
8018
|
const agentSessionId = session2.agentKind === "codex" ? `pending:${sessionCode}` : sessionCode;
|
|
7846
|
-
worktreePath =
|
|
8019
|
+
worktreePath = join8(worktreeDir, slug(`${name}-${sessionCode.slice(0, 8)}`));
|
|
8020
|
+
const base = await syncedBase();
|
|
8021
|
+
if (wasCanceled()) return log(`session #${sessionId} start canceled during git sync`);
|
|
7847
8022
|
createWorktree({
|
|
7848
8023
|
repo,
|
|
7849
8024
|
worktreePath,
|
|
7850
8025
|
sessionCode: sessionCode.slice(0, 8),
|
|
7851
|
-
base
|
|
8026
|
+
base
|
|
7852
8027
|
});
|
|
7853
8028
|
await client.sessions.materialize(sessionId, { agentSessionId, worktreePath }, cfg.apiToken);
|
|
8029
|
+
if (wasCanceled()) return log(`session #${sessionId} start canceled after materializing`);
|
|
7854
8030
|
log(`materialized session #${sessionId} \u2192 ${worktreePath}`);
|
|
7855
8031
|
} else if (!existsSync5(worktreePath)) {
|
|
7856
|
-
restoreWorktree(repo, worktreePath, session2.agentSessionId.slice(0, 8));
|
|
8032
|
+
await restoreWorktree(repo, worktreePath, session2.agentSessionId.slice(0, 8), baseBranch);
|
|
8033
|
+
if (wasCanceled()) return log(`session #${sessionId} start canceled during worktree restore`);
|
|
7857
8034
|
log(`recreated session #${sessionId} worktree (was missing) \u2192 ${worktreePath}`);
|
|
7858
8035
|
}
|
|
7859
8036
|
ensureExcluded(repo, PROJECT_CONFIG_NAME);
|
|
7860
|
-
saveProjectConfig(
|
|
8037
|
+
saveProjectConfig(join8(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
|
|
7861
8038
|
if (hasTerminal(sessionId))
|
|
7862
8039
|
return log(`session #${sessionId} terminal opened mid-start \u2014 skipping headless start`);
|
|
7863
8040
|
spawnChild(sessionId, worktreePath);
|
|
7864
8041
|
};
|
|
8042
|
+
const start = (sessionId, name) => {
|
|
8043
|
+
const epoch = currentStartEpoch(sessionId);
|
|
8044
|
+
const existing = starts.get(sessionId);
|
|
8045
|
+
if (existing?.epoch === epoch) return existing.promise;
|
|
8046
|
+
const pending = existing ? existing.promise.then(
|
|
8047
|
+
() => startOne(sessionId, name, epoch),
|
|
8048
|
+
() => startOne(sessionId, name, epoch)
|
|
8049
|
+
) : startOne(sessionId, name, epoch);
|
|
8050
|
+
starts.set(sessionId, { epoch, promise: pending });
|
|
8051
|
+
const clear = () => {
|
|
8052
|
+
if (starts.get(sessionId)?.promise === pending) starts.delete(sessionId);
|
|
8053
|
+
};
|
|
8054
|
+
void pending.then(clear, clear);
|
|
8055
|
+
return pending;
|
|
8056
|
+
};
|
|
7865
8057
|
const stop = (sessionId) => {
|
|
8058
|
+
cancelStart(sessionId);
|
|
7866
8059
|
closeTerminal(sessionId);
|
|
7867
8060
|
const entry = children.get(sessionId);
|
|
7868
8061
|
if (!entry) return;
|
|
@@ -7871,6 +8064,7 @@ var createSessionSupervisor = (deps) => {
|
|
|
7871
8064
|
log(`stopped session #${sessionId}`);
|
|
7872
8065
|
};
|
|
7873
8066
|
const remove = (sessionId, worktreePath) => {
|
|
8067
|
+
cancelStart(sessionId);
|
|
7874
8068
|
closeTerminal(sessionId);
|
|
7875
8069
|
const entry = children.get(sessionId);
|
|
7876
8070
|
if (entry) {
|
|
@@ -7883,10 +8077,13 @@ var createSessionSupervisor = (deps) => {
|
|
|
7883
8077
|
};
|
|
7884
8078
|
const title = async (sessionId, agentSessionId, worktreePath) => {
|
|
7885
8079
|
const session2 = await client.sessions.get(sessionId);
|
|
7886
|
-
|
|
7887
|
-
const exchange = readFirstExchange(agentSessionId);
|
|
8080
|
+
const events = await client.sessions.listEvents(sessionId).catch(() => []);
|
|
8081
|
+
const exchange = parseFirstExchangeFromEvents(events) ?? (session2.agentKind === "claude-code" ? readFirstExchange(agentSessionId) : null);
|
|
7888
8082
|
if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
|
|
7889
|
-
const outcome = await
|
|
8083
|
+
const outcome = session2.agentKind === "codex" ? await generateTitleWithCodex({
|
|
8084
|
+
userText: exchange.userText,
|
|
8085
|
+
assistantText: exchange.assistantText
|
|
8086
|
+
}) : await generateTitle({
|
|
7890
8087
|
worktreePath,
|
|
7891
8088
|
userText: exchange.userText,
|
|
7892
8089
|
assistantText: exchange.assistantText,
|
|
@@ -7941,14 +8138,14 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
7941
8138
|
// src/worker/pty.ts
|
|
7942
8139
|
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
|
|
7943
8140
|
import { homedir as homedir3 } from "node:os";
|
|
7944
|
-
import { join as
|
|
8141
|
+
import { join as join9 } from "node:path";
|
|
7945
8142
|
var MAX_TERMINALS = 10;
|
|
7946
8143
|
var ptyArgs = (agentSessionId, hasJsonl) => hasJsonl ? ["--resume", agentSessionId] : ["--session-id", agentSessionId];
|
|
7947
8144
|
var hasSessionJsonl = (agentSessionId) => {
|
|
7948
|
-
const root =
|
|
8145
|
+
const root = join9(homedir3(), ".claude", "projects");
|
|
7949
8146
|
if (!existsSync6(root)) return false;
|
|
7950
8147
|
for (const dir of readdirSync2(root))
|
|
7951
|
-
if (existsSync6(
|
|
8148
|
+
if (existsSync6(join9(root, dir, `${agentSessionId}.jsonl`))) return true;
|
|
7952
8149
|
return false;
|
|
7953
8150
|
};
|
|
7954
8151
|
var serverTerminalWsUrl = (server, sessionId) => `${server.replace(/^http/, "ws")}/workers/me/terminal/ws?sessionId=${sessionId}`;
|
|
@@ -8116,8 +8313,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
8116
8313
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8117
8314
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8118
8315
|
import { homedir as homedir4 } from "node:os";
|
|
8119
|
-
import { dirname as dirname3, join as
|
|
8120
|
-
var machineIdPath = (env = process.env) =>
|
|
8316
|
+
import { dirname as dirname3, join as join10 } from "node:path";
|
|
8317
|
+
var machineIdPath = (env = process.env) => join10(env.XDG_DATA_HOME ?? join10(env.HOME ?? homedir4(), ".local/share"), "baton", "machine-id");
|
|
8121
8318
|
var readOrCreateMachineId = (path = machineIdPath()) => {
|
|
8122
8319
|
if (existsSync8(path)) {
|
|
8123
8320
|
const s = readFileSync6(path, "utf8").trim();
|
|
@@ -8146,6 +8343,11 @@ var parseAgentKind = (value) => {
|
|
|
8146
8343
|
if (raw === "claude-code" || raw === "codex") return raw;
|
|
8147
8344
|
throw new Error(`invalid agent kind "${raw}" (expected claude-code or codex)`);
|
|
8148
8345
|
};
|
|
8346
|
+
var nonEmptyString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
8347
|
+
var resolveBaseBranch = (input) => {
|
|
8348
|
+
const branch = nonEmptyString(input.flag) ?? nonEmptyString(input.env) ?? nonEmptyString(input.saved) ?? repoHeadBranch(input.repo ?? process.cwd());
|
|
8349
|
+
return validateBaseBranch(branch);
|
|
8350
|
+
};
|
|
8149
8351
|
var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
8150
8352
|
const out = await client.workers.register({
|
|
8151
8353
|
projectId: input.projectId,
|
|
@@ -8154,17 +8356,19 @@ var registerWorker = async (client, input, cfgPath = projectConfigPath()) => {
|
|
|
8154
8356
|
name: input.name,
|
|
8155
8357
|
hostname: input.hostname
|
|
8156
8358
|
});
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8359
|
+
const existing = loadProjectConfigOrNull(cfgPath) ?? {};
|
|
8360
|
+
saveProjectConfig(cfgPath, {
|
|
8361
|
+
...existing,
|
|
8362
|
+
server: input.server,
|
|
8363
|
+
project: input.projectId,
|
|
8364
|
+
baseBranch: input.baseBranch,
|
|
8365
|
+
worker: {
|
|
8366
|
+
id: out.worker.id,
|
|
8367
|
+
name: out.worker.name,
|
|
8368
|
+
agentKind: out.worker.agentKind,
|
|
8369
|
+
machineId: out.worker.machineId,
|
|
8370
|
+
apiToken: out.apiToken
|
|
8371
|
+
}
|
|
8168
8372
|
});
|
|
8169
8373
|
return { out, configPath: cfgPath };
|
|
8170
8374
|
};
|
|
@@ -8187,6 +8391,10 @@ var worker = defineCommand({
|
|
|
8187
8391
|
type: "string",
|
|
8188
8392
|
description: "agent engine for new sessions: claude-code or codex"
|
|
8189
8393
|
},
|
|
8394
|
+
baseBranch: {
|
|
8395
|
+
type: "string",
|
|
8396
|
+
description: "branch new session worktrees start from (default: current branch, then main)"
|
|
8397
|
+
},
|
|
8190
8398
|
...common
|
|
8191
8399
|
},
|
|
8192
8400
|
run: async ({ args }) => {
|
|
@@ -8197,9 +8405,15 @@ var worker = defineCommand({
|
|
|
8197
8405
|
);
|
|
8198
8406
|
if (!args.json) console.log(`registering against ${server}`);
|
|
8199
8407
|
const cfgPath = configPathFromArgs(args);
|
|
8200
|
-
const
|
|
8408
|
+
const savedConfig = loadProjectConfigOrNull(cfgPath);
|
|
8409
|
+
const fileToken = savedConfig?.worker?.apiToken;
|
|
8201
8410
|
const auth = resolveAuth(process.env, fileToken);
|
|
8202
8411
|
const c = createClient(server, auth && auth !== "cookie" ? auth : void 0);
|
|
8412
|
+
const baseBranch = resolveBaseBranch({
|
|
8413
|
+
flag: args.baseBranch,
|
|
8414
|
+
env: process.env.BATON_BASE_BRANCH,
|
|
8415
|
+
saved: savedConfig?.baseBranch
|
|
8416
|
+
});
|
|
8203
8417
|
const hostname = osHostname();
|
|
8204
8418
|
const machineId = readOrCreateMachineId();
|
|
8205
8419
|
const name = args.name ?? hostname;
|
|
@@ -8212,16 +8426,18 @@ var worker = defineCommand({
|
|
|
8212
8426
|
server,
|
|
8213
8427
|
hostname,
|
|
8214
8428
|
machineId,
|
|
8215
|
-
agentKind
|
|
8429
|
+
agentKind,
|
|
8430
|
+
baseBranch
|
|
8216
8431
|
},
|
|
8217
8432
|
cfgPath
|
|
8218
8433
|
);
|
|
8219
8434
|
if (args.json) {
|
|
8220
|
-
console.log(toJson({ ...out, configPath, machineIdPath: machineIdPath() }));
|
|
8435
|
+
console.log(toJson({ ...out, baseBranch, configPath, machineIdPath: machineIdPath() }));
|
|
8221
8436
|
return;
|
|
8222
8437
|
}
|
|
8223
8438
|
console.log(`worker #${out.worker.id} (${out.worker.name}) \u2014 ${out.outcome}`);
|
|
8224
8439
|
console.log(` agentKind: ${out.worker.agentKind}`);
|
|
8440
|
+
console.log(` baseBranch: ${baseBranch}`);
|
|
8225
8441
|
console.log(` hostname: ${out.worker.hostname}`);
|
|
8226
8442
|
console.log(` machineId: ${out.worker.machineId}`);
|
|
8227
8443
|
console.log(` machineId file: ${machineIdPath()}`);
|
|
@@ -8330,13 +8546,22 @@ var worker = defineCommand({
|
|
|
8330
8546
|
console.log("(no worker registered for this project \u2014 run `baton worker register`)");
|
|
8331
8547
|
return;
|
|
8332
8548
|
}
|
|
8549
|
+
const baseBranch = resolveBaseBranch({ saved: cfg.baseBranch });
|
|
8333
8550
|
if (args.json) {
|
|
8334
|
-
console.log(
|
|
8551
|
+
console.log(
|
|
8552
|
+
toJson({
|
|
8553
|
+
...worker2,
|
|
8554
|
+
server: cfg.server,
|
|
8555
|
+
projectId: cfg.project,
|
|
8556
|
+
baseBranch
|
|
8557
|
+
})
|
|
8558
|
+
);
|
|
8335
8559
|
return;
|
|
8336
8560
|
}
|
|
8337
8561
|
console.log(`worker #${worker2.id} (${worker2.name})`);
|
|
8338
8562
|
console.log(` machineId: ${worker2.machineId}`);
|
|
8339
8563
|
console.log(` agentKind: ${worker2.agentKind ?? "claude-code"}`);
|
|
8564
|
+
console.log(` baseBranch: ${baseBranch}`);
|
|
8340
8565
|
console.log(` server: ${cfg?.server}`);
|
|
8341
8566
|
}
|
|
8342
8567
|
})
|