@pitboss/cli 0.4.8 → 0.4.10
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/cli.js +754 -288
- package/dist/event-worker.js +198 -59
- package/dist/mcp.js +100 -15
- package/package.json +1 -1
package/dist/event-worker.js
CHANGED
|
@@ -59,7 +59,8 @@ var BASE_TASK_LOOP = (agentId, repoPath) => `You are an AI worker agent (ID: ${a
|
|
|
59
59
|
a one-line status on the task thread naming what you are checking) when you begin, then
|
|
60
60
|
run the tests, compare the diff against the spec the task names, then
|
|
61
61
|
\`mcp__relai__submit_review\` \u2014 \`approve\`, or \`reject\` with a specific note the author
|
|
62
|
-
can act on.
|
|
62
|
+
can act on. When the task is a goal's build with a PR, pass \`headSha\`: the full sha of the
|
|
63
|
+
commit you reviewed. Do this even when get_my_tasks is empty.
|
|
63
64
|
If the change touches a long-running service or a third-party runtime integration (SSE/event streams, launchd/systemd, auth headers, a client library), run the changed path once before approving and quote what it printed. A type cast past a library's own types is a reason to stop and run it.
|
|
64
65
|
|
|
65
66
|
4. Call \`mcp__relai__get_my_tasks\` \u2014 if it returns no tasks, stop immediately.
|
|
@@ -301,7 +302,11 @@ Find the default branch with \`git remote show origin\`.
|
|
|
301
302
|
\`specialization\` or \`domains\` only to values agents in \`mcp__relai__list_agents\` declare:
|
|
302
303
|
the router never hands out a task whose tag no agent matches. When the repo has \`writer\` agents, set
|
|
303
304
|
\`specialization: "writer"\` on build tasks, so they wait for a writer instead of going to a reviewer.
|
|
304
|
-
|
|
305
|
+
Before writing any build task, choose the goal's reviewer (for a \`reviewer_agent\` gate): a
|
|
306
|
+
\`reviewer\` agent, on a different \`workerType\` from the writers when one exists. Never assign that
|
|
307
|
+
agent a build task, and never leave the only reviewers busy building: a goal whose every reviewer
|
|
308
|
+
built part of it has nobody left to review it. The API defaults a build task with no
|
|
309
|
+
\`specialization\` to \`writer\` when the repo has writers. Make them small
|
|
305
310
|
and independently testable so several agents can work at once. Use \`blockedBy\` only where
|
|
306
311
|
one task truly needs another's result. Each description is self-contained: what to change,
|
|
307
312
|
which files, how to test it, and the acceptance criteria.
|
|
@@ -444,6 +449,9 @@ var USAGE_NUMBER_KEYS = [
|
|
|
444
449
|
"cache_read_input_tokens",
|
|
445
450
|
"cache_creation_input_tokens"
|
|
446
451
|
];
|
|
452
|
+
function notReportedUsage(reason) {
|
|
453
|
+
return { notReported: true, reason };
|
|
454
|
+
}
|
|
447
455
|
function finiteNumber(value) {
|
|
448
456
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
449
457
|
}
|
|
@@ -511,14 +519,32 @@ async function getTaskStatus(conn, id, logPrefix) {
|
|
|
511
519
|
return void 0;
|
|
512
520
|
}
|
|
513
521
|
}
|
|
514
|
-
async function
|
|
522
|
+
async function listUpdatedSince(conn, since, logPrefix) {
|
|
523
|
+
try {
|
|
524
|
+
const res = await fetch(
|
|
525
|
+
`${conn.apiUrl}/tasks?repoId=${conn.repoId}&assignedTo=${conn.agentId}&updatedAfter=${encodeURIComponent(since)}`,
|
|
526
|
+
{ headers: { Authorization: `Bearer ${conn.apiSecret}` } }
|
|
527
|
+
);
|
|
528
|
+
if (!res.ok) throw new Error(`task list failed (${res.status} ${res.statusText})`);
|
|
529
|
+
const { data } = await res.json();
|
|
530
|
+
return (data ?? []).filter((row) => typeof row.id === "string" && row.id.length > 0).filter((row) => row.status !== "assigned").map((row) => row.id);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
console.error(
|
|
533
|
+
`${logPrefix} Could not list tasks updated since the session started:`,
|
|
534
|
+
err instanceof Error ? err.message : String(err)
|
|
535
|
+
);
|
|
536
|
+
return [];
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function sessionTouchedTaskIds(conn, candidates, logPrefix, since) {
|
|
515
540
|
const touched = [];
|
|
516
541
|
for (const id of candidates) {
|
|
517
542
|
const status = await getTaskStatus(conn, id, logPrefix);
|
|
518
543
|
if (status !== void 0 && status !== "assigned") touched.push(id);
|
|
519
544
|
}
|
|
520
545
|
const inProgressAtClose = await listSessionTaskIds(conn, logPrefix, "in_progress");
|
|
521
|
-
|
|
546
|
+
const updated = since ? await listUpdatedSince(conn, since, logPrefix) : [];
|
|
547
|
+
return unionTaskIds(touched, inProgressAtClose, updated);
|
|
522
548
|
}
|
|
523
549
|
function unionTaskIds(...lists) {
|
|
524
550
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -532,34 +558,99 @@ function unionTaskIds(...lists) {
|
|
|
532
558
|
}
|
|
533
559
|
return out;
|
|
534
560
|
}
|
|
535
|
-
async function writeSessionUsage(conn, taskIds, usage, logPrefix) {
|
|
536
|
-
const sessionUsage = {
|
|
537
|
-
...usage,
|
|
538
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
539
|
-
sessionTaskIds: [...taskIds]
|
|
540
|
-
};
|
|
561
|
+
async function writeSessionUsage(conn, taskIds, usage, logPrefix, opts = {}) {
|
|
541
562
|
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${conn.apiSecret}` };
|
|
542
563
|
for (const id of taskIds) {
|
|
564
|
+
const slice = taskIds.length > 1 && !usage.notReported ? opts.slices?.[id] : void 0;
|
|
565
|
+
const share = usage.notReported ? void 0 : slice || taskIds.length === 1 ? "task" : "session";
|
|
566
|
+
const body = {
|
|
567
|
+
usage: slice ?? usage,
|
|
568
|
+
sessionTaskIds: taskIds,
|
|
569
|
+
...opts.model ? { model: opts.model } : {},
|
|
570
|
+
...share ? { share } : {}
|
|
571
|
+
};
|
|
543
572
|
try {
|
|
544
|
-
const
|
|
545
|
-
method: "
|
|
573
|
+
const res = await fetch(`${conn.apiUrl}/tasks/${id}/usage-report`, {
|
|
574
|
+
method: "POST",
|
|
546
575
|
headers,
|
|
547
|
-
body: JSON.stringify(
|
|
576
|
+
body: JSON.stringify(body)
|
|
548
577
|
});
|
|
549
|
-
if (!
|
|
550
|
-
console.error(`${logPrefix} Could not
|
|
578
|
+
if (!res.ok) {
|
|
579
|
+
console.error(`${logPrefix} Could not report usage on ${id} (${res.status} ${res.statusText})`);
|
|
551
580
|
}
|
|
552
581
|
} catch (err) {
|
|
553
582
|
console.error(
|
|
554
|
-
`${logPrefix} Could not
|
|
583
|
+
`${logPrefix} Could not report usage on ${id}:`,
|
|
555
584
|
err instanceof Error ? err.message : String(err)
|
|
556
585
|
);
|
|
557
586
|
}
|
|
558
587
|
}
|
|
559
588
|
}
|
|
560
|
-
async function recordSessionUsage(conn, startIds, usage, logPrefix) {
|
|
589
|
+
async function recordSessionUsage(conn, startIds, usage, logPrefix, opts = {}) {
|
|
561
590
|
const start = await startIds;
|
|
562
|
-
|
|
591
|
+
const { since, ...rest } = opts;
|
|
592
|
+
await writeSessionUsage(conn, await sessionTouchedTaskIds(conn, start, logPrefix, since), usage, logPrefix, rest);
|
|
593
|
+
}
|
|
594
|
+
var BOUNDARY_STATUSES = /* @__PURE__ */ new Set(["completed", "blocked", "pending_verification"]);
|
|
595
|
+
function addUsage(into, from) {
|
|
596
|
+
into.num_turns = (into.num_turns ?? 0) + (from.num_turns ?? 0);
|
|
597
|
+
const total = into.usage ?? {};
|
|
598
|
+
for (const key of USAGE_NUMBER_KEYS) {
|
|
599
|
+
const n = from.usage?.[key];
|
|
600
|
+
if (n !== void 0) total[key] = (total[key] ?? 0) + n;
|
|
601
|
+
}
|
|
602
|
+
into.usage = total;
|
|
603
|
+
}
|
|
604
|
+
function createUsageTracker() {
|
|
605
|
+
let model;
|
|
606
|
+
let pending = /* @__PURE__ */ new Map();
|
|
607
|
+
const slices = {};
|
|
608
|
+
let anon = 0;
|
|
609
|
+
function cut(taskId) {
|
|
610
|
+
const slice = slices[taskId] ?? {};
|
|
611
|
+
for (const messageUsage of pending.values()) addUsage(slice, messageUsage);
|
|
612
|
+
pending = /* @__PURE__ */ new Map();
|
|
613
|
+
if (slice.usage) slices[taskId] = slice;
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
observe(event) {
|
|
617
|
+
if (event.type === "system" && event.subtype === "init") {
|
|
618
|
+
if (typeof event.model === "string" && event.model) model = event.model;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (event.type !== "assistant") return;
|
|
622
|
+
const message = event.message ?? {};
|
|
623
|
+
const raw = message.usage;
|
|
624
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
625
|
+
const usage = pickNumbers(raw, USAGE_NUMBER_KEYS);
|
|
626
|
+
if (Object.keys(usage).length > 0) {
|
|
627
|
+
pending.set(typeof message.id === "string" ? message.id : `anon_${anon++}`, { num_turns: 1, usage });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
631
|
+
for (const block of content) {
|
|
632
|
+
if (block.type !== "tool_use" || typeof block.name !== "string" || !block.name.endsWith("update_task_status")) continue;
|
|
633
|
+
const input = block.input ?? {};
|
|
634
|
+
if (typeof input.taskId === "string" && typeof input.status === "string" && BOUNDARY_STATUSES.has(input.status)) {
|
|
635
|
+
cut(input.taskId);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
model: () => model,
|
|
640
|
+
opts: () => ({ ...model ? { model } : {}, slices })
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ../claude-worker/src/model.ts
|
|
645
|
+
import { readFileSync } from "node:fs";
|
|
646
|
+
function pinnedModel(env = process.env) {
|
|
647
|
+
const file = env.PITBOSS_MODEL_FILE;
|
|
648
|
+
if (!file) return void 0;
|
|
649
|
+
try {
|
|
650
|
+
return readFileSync(file, "utf-8").trim() || void 0;
|
|
651
|
+
} catch {
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
563
654
|
}
|
|
564
655
|
|
|
565
656
|
// ../claude-worker/src/session.ts
|
|
@@ -594,26 +685,36 @@ function writeMcpConfig(config2, prefix = "claude-worker") {
|
|
|
594
685
|
writeFileSync(path, JSON.stringify(mcpConfig), { mode: 384 });
|
|
595
686
|
return path;
|
|
596
687
|
}
|
|
688
|
+
function claudeArgs(opts) {
|
|
689
|
+
return [
|
|
690
|
+
"--print",
|
|
691
|
+
"--mcp-config",
|
|
692
|
+
opts.mcpConfigPath,
|
|
693
|
+
"--no-session-persistence",
|
|
694
|
+
"--dangerously-skip-permissions",
|
|
695
|
+
"--output-format",
|
|
696
|
+
"stream-json",
|
|
697
|
+
"--verbose",
|
|
698
|
+
"--model",
|
|
699
|
+
opts.model,
|
|
700
|
+
"--add-dir",
|
|
701
|
+
opts.repoPath,
|
|
702
|
+
...opts.isolateUserConfig ? ["--setting-sources", "project,local"] : []
|
|
703
|
+
];
|
|
704
|
+
}
|
|
597
705
|
async function runClaudeSession(config2) {
|
|
598
706
|
const prompt = buildPrompt(config2);
|
|
599
707
|
const mcpConfigPath = writeMcpConfig(config2);
|
|
708
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
600
709
|
const taskIdsPromise = listSessionTaskIds(config2, "[claude-worker]");
|
|
601
710
|
try {
|
|
602
711
|
return await new Promise((resolve, reject) => {
|
|
603
|
-
const args =
|
|
604
|
-
"--print",
|
|
605
|
-
"--mcp-config",
|
|
712
|
+
const args = claudeArgs({
|
|
606
713
|
mcpConfigPath,
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
"
|
|
610
|
-
|
|
611
|
-
"--verbose",
|
|
612
|
-
"--model",
|
|
613
|
-
config2.model,
|
|
614
|
-
"--add-dir",
|
|
615
|
-
config2.repoPath
|
|
616
|
-
];
|
|
714
|
+
model: pinnedModel() ?? config2.model,
|
|
715
|
+
repoPath: config2.repoPath,
|
|
716
|
+
isolateUserConfig: process.env.PITBOSS_ISOLATE_USER_CONFIG === "1"
|
|
717
|
+
});
|
|
617
718
|
console.log(`[claude-worker] Spawning: ${config2.claudeBin} ${args.slice(0, 4).join(" ")} ...`);
|
|
618
719
|
console.log(`[claude-worker] cwd: ${config2.repoPath}`);
|
|
619
720
|
const { ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ...workerEnv } = process.env;
|
|
@@ -629,6 +730,8 @@ async function runClaudeSession(config2) {
|
|
|
629
730
|
let buffer = "";
|
|
630
731
|
let resultError = "";
|
|
631
732
|
let sessionUsage;
|
|
733
|
+
let sawResult = false;
|
|
734
|
+
const tracker = createUsageTracker();
|
|
632
735
|
proc.stdout.on("data", (chunk) => {
|
|
633
736
|
buffer += chunk.toString();
|
|
634
737
|
const lines = buffer.split("\n");
|
|
@@ -637,6 +740,7 @@ async function runClaudeSession(config2) {
|
|
|
637
740
|
if (!line.trim()) continue;
|
|
638
741
|
try {
|
|
639
742
|
const event = JSON.parse(line);
|
|
743
|
+
tracker.observe(event);
|
|
640
744
|
if (event.type === "assistant") {
|
|
641
745
|
const content = event.message?.content ?? [];
|
|
642
746
|
for (const block of content) {
|
|
@@ -650,6 +754,7 @@ async function runClaudeSession(config2) {
|
|
|
650
754
|
}
|
|
651
755
|
}
|
|
652
756
|
} else if (event.type === "result") {
|
|
757
|
+
sawResult = true;
|
|
653
758
|
const parsed = parseResultUsage(event);
|
|
654
759
|
if (parsed) sessionUsage = parsed;
|
|
655
760
|
if (event.is_error) {
|
|
@@ -671,9 +776,9 @@ async function runClaudeSession(config2) {
|
|
|
671
776
|
proc.on("close", (code, signal) => {
|
|
672
777
|
const unique = [...new Set(toolsUsed)];
|
|
673
778
|
console.log(`[claude-worker] Done \u2014 tools used: ${unique.length ? unique.join(", ") : "none"}`);
|
|
674
|
-
const usage = sessionUsage;
|
|
675
|
-
if (
|
|
676
|
-
const finish =
|
|
779
|
+
const usage = sessionUsage ?? notReportedUsage(sawResult ? "adapter" : "killed");
|
|
780
|
+
if (sessionUsage) console.log(formatSessionUsageLine("[claude-worker]", sessionUsage));
|
|
781
|
+
const finish = recordSessionUsage(config2, taskIdsPromise, usage, "[claude-worker]", { since: startedAt, ...tracker.opts() });
|
|
677
782
|
const settle = () => {
|
|
678
783
|
if (signal || code !== 0 && code !== null) {
|
|
679
784
|
const detail = (resultError || stderrOutput || "").trim();
|
|
@@ -731,8 +836,8 @@ function classifySessionError(text) {
|
|
|
731
836
|
|
|
732
837
|
// ../claude-worker/src/adapters/cursor.ts
|
|
733
838
|
var bin = () => process.env.CURSOR_BIN ?? "cursor-agent";
|
|
734
|
-
function cursorArgs(repoPath, prompt) {
|
|
735
|
-
return ["-p", "--trust", "--force", "--output-format", "stream-json", "--workspace", repoPath, prompt];
|
|
839
|
+
function cursorArgs(repoPath, prompt, model) {
|
|
840
|
+
return ["-p", "--trust", "--force", "--output-format", "stream-json", "--workspace", repoPath, ...model ? ["--model", model] : [], prompt];
|
|
736
841
|
}
|
|
737
842
|
function readCursorEvent(line) {
|
|
738
843
|
let event;
|
|
@@ -741,6 +846,9 @@ function readCursorEvent(line) {
|
|
|
741
846
|
} catch {
|
|
742
847
|
return {};
|
|
743
848
|
}
|
|
849
|
+
if (event.type === "system" && event.subtype === "init") {
|
|
850
|
+
return typeof event.model === "string" && event.model ? { model: event.model } : {};
|
|
851
|
+
}
|
|
744
852
|
if (event.type === "tool_call" && event.subtype === "started") {
|
|
745
853
|
const call = event.tool_call ?? {};
|
|
746
854
|
const [kind] = Object.keys(call);
|
|
@@ -756,8 +864,9 @@ function readCursorEvent(line) {
|
|
|
756
864
|
return {};
|
|
757
865
|
}
|
|
758
866
|
async function runCursorSession(config2) {
|
|
759
|
-
const args = cursorArgs(config2.repoPath, buildPrompt(config2));
|
|
867
|
+
const args = cursorArgs(config2.repoPath, buildPrompt(config2), pinnedModel());
|
|
760
868
|
console.log(`[cursor-worker] Spawning: ${bin()} ${args.slice(0, 5).join(" ")} ...`);
|
|
869
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
761
870
|
const taskIdsPromise = listSessionTaskIds(config2, "[cursor-worker]");
|
|
762
871
|
return new Promise((resolve, reject) => {
|
|
763
872
|
const proc = spawn2(bin(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -767,12 +876,14 @@ async function runCursorSession(config2) {
|
|
|
767
876
|
let resultError = "";
|
|
768
877
|
let stderrOutput = "";
|
|
769
878
|
let sessionUsage;
|
|
879
|
+
let model;
|
|
770
880
|
proc.stdout.on("data", (chunk) => {
|
|
771
881
|
buffer += chunk.toString();
|
|
772
882
|
const lines = buffer.split("\n");
|
|
773
883
|
buffer = lines.pop() ?? "";
|
|
774
884
|
for (const line of lines) {
|
|
775
|
-
const { tool, error, usage } = readCursorEvent(line);
|
|
885
|
+
const { tool, error, usage, model: initModel } = readCursorEvent(line);
|
|
886
|
+
if (initModel) model = initModel;
|
|
776
887
|
if (tool) {
|
|
777
888
|
toolsUsed.push(tool);
|
|
778
889
|
process.stdout.write(` \u2699 ${tool}
|
|
@@ -790,9 +901,9 @@ async function runCursorSession(config2) {
|
|
|
790
901
|
});
|
|
791
902
|
proc.on("close", (code, signal) => {
|
|
792
903
|
console.log(`[cursor-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
|
|
793
|
-
const usage = sessionUsage;
|
|
794
|
-
if (
|
|
795
|
-
const finish =
|
|
904
|
+
const usage = sessionUsage ?? notReportedUsage("killed");
|
|
905
|
+
if (sessionUsage) console.log(formatSessionUsageLine("[cursor-worker]", sessionUsage));
|
|
906
|
+
const finish = recordSessionUsage(config2, taskIdsPromise, usage, "[cursor-worker]", { since: startedAt, ...model ? { model } : {} });
|
|
796
907
|
const settle = () => {
|
|
797
908
|
if (signal || code !== 0 && code !== null) {
|
|
798
909
|
const detail = (resultError || stderrOutput).trim();
|
|
@@ -837,10 +948,11 @@ import { unlinkSync as unlinkSync2 } from "fs";
|
|
|
837
948
|
var bin2 = () => process.env.COPILOT_BIN ?? "copilot";
|
|
838
949
|
var POLICY_BLOCKS = [/access denied by policy/i, /third-party mcp servers are disabled/i];
|
|
839
950
|
var QUOTA = [/premium request/i, /monthly (?:limit|allowance|quota)/i];
|
|
840
|
-
function copilotArgs(repoPath, prompt, mcpConfigPath) {
|
|
951
|
+
function copilotArgs(repoPath, prompt, mcpConfigPath, model) {
|
|
841
952
|
return [
|
|
842
953
|
"-p",
|
|
843
954
|
prompt,
|
|
955
|
+
...model ? ["--model", model] : [],
|
|
844
956
|
"--allow-all-tools",
|
|
845
957
|
"--output-format",
|
|
846
958
|
"json",
|
|
@@ -862,6 +974,7 @@ function readCopilotEvent(line) {
|
|
|
862
974
|
const type = typeof event.type === "string" ? event.type : "";
|
|
863
975
|
const data = event.data ?? {};
|
|
864
976
|
const message = typeof data.message === "string" ? data.message : "";
|
|
977
|
+
if (type === "session.tools_updated" && typeof data.model === "string" && data.model) return { model: data.model };
|
|
865
978
|
if (/^tool\..*start/.test(type) && typeof data.toolName === "string") return { tool: data.toolName };
|
|
866
979
|
if (type === "session.error") return { error: message || "copilot session error" };
|
|
867
980
|
if (type === "result" && typeof event.exitCode === "number" && event.exitCode !== 0) return { error: `copilot finished with exit code ${event.exitCode}` };
|
|
@@ -870,7 +983,9 @@ function readCopilotEvent(line) {
|
|
|
870
983
|
}
|
|
871
984
|
async function runCopilotSession(config2) {
|
|
872
985
|
const mcpConfigPath = writeMcpConfig(config2, "copilot-worker");
|
|
873
|
-
const args = copilotArgs(config2.repoPath, buildPrompt(config2), mcpConfigPath);
|
|
986
|
+
const args = copilotArgs(config2.repoPath, buildPrompt(config2), mcpConfigPath, pinnedModel());
|
|
987
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
988
|
+
const taskIdsPromise = listSessionTaskIds(config2, "[copilot-worker]");
|
|
874
989
|
console.log(`[copilot-worker] Spawning: ${bin2()} -p <prompt> ${args.slice(2, 6).join(" ")} ...`);
|
|
875
990
|
return new Promise((resolve, reject) => {
|
|
876
991
|
const proc = spawn3(bin2(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -879,12 +994,14 @@ async function runCopilotSession(config2) {
|
|
|
879
994
|
let buffer = "";
|
|
880
995
|
let sessionError = "";
|
|
881
996
|
let stderrOutput = "";
|
|
997
|
+
let model;
|
|
882
998
|
proc.stdout.on("data", (chunk) => {
|
|
883
999
|
buffer += chunk.toString();
|
|
884
1000
|
const lines = buffer.split("\n");
|
|
885
1001
|
buffer = lines.pop() ?? "";
|
|
886
1002
|
for (const line of lines) {
|
|
887
|
-
const { tool, error } = readCopilotEvent(line);
|
|
1003
|
+
const { tool, error, model: initModel } = readCopilotEvent(line);
|
|
1004
|
+
if (initModel) model = initModel;
|
|
888
1005
|
if (tool) {
|
|
889
1006
|
toolsUsed.push(tool);
|
|
890
1007
|
process.stdout.write(` \u2699 ${tool}
|
|
@@ -905,12 +1022,15 @@ async function runCopilotSession(config2) {
|
|
|
905
1022
|
} catch {
|
|
906
1023
|
}
|
|
907
1024
|
console.log(`[copilot-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1025
|
+
const settle = () => {
|
|
1026
|
+
if (sessionError || signal || code !== 0 && code !== null) {
|
|
1027
|
+
const detail = (sessionError || stderrOutput).trim();
|
|
1028
|
+
reject(new Error(`copilot exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
|
|
1029
|
+
} else {
|
|
1030
|
+
resolve([...new Set(toolsUsed)]);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
recordSessionUsage(config2, taskIdsPromise, notReportedUsage("adapter"), "[copilot-worker]", { since: startedAt, ...model ? { model } : {} }).then(settle, settle);
|
|
914
1034
|
});
|
|
915
1035
|
proc.on("error", (err) => {
|
|
916
1036
|
try {
|
|
@@ -943,7 +1063,7 @@ var bin3 = () => process.env.CODEX_BIN ?? "codex";
|
|
|
943
1063
|
var codexHome = () => process.env.CODEX_HOME ?? join3(homedir(), ".codex");
|
|
944
1064
|
var QUOTA2 = [/usage limit/i, /rate limit reached for/i, /upgrade to (plus|pro)/i];
|
|
945
1065
|
var CREDENTIALS = [/not logged in/i, /codex login/i, /401 unauthorized/i];
|
|
946
|
-
function codexArgs(repoPath, prompt, profile) {
|
|
1066
|
+
function codexArgs(repoPath, prompt, profile, model) {
|
|
947
1067
|
return [
|
|
948
1068
|
"exec",
|
|
949
1069
|
"--json",
|
|
@@ -954,6 +1074,7 @@ function codexArgs(repoPath, prompt, profile) {
|
|
|
954
1074
|
repoPath,
|
|
955
1075
|
"-p",
|
|
956
1076
|
profile,
|
|
1077
|
+
...model ? ["-m", model] : [],
|
|
957
1078
|
prompt
|
|
958
1079
|
];
|
|
959
1080
|
}
|
|
@@ -1002,7 +1123,9 @@ async function runCodexSession(config2) {
|
|
|
1002
1123
|
} catch {
|
|
1003
1124
|
}
|
|
1004
1125
|
};
|
|
1005
|
-
const args = codexArgs(config2.repoPath, buildPrompt(config2), profile);
|
|
1126
|
+
const args = codexArgs(config2.repoPath, buildPrompt(config2), profile, pinnedModel());
|
|
1127
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1128
|
+
const taskIdsPromise = listSessionTaskIds(config2, "[codex-worker]");
|
|
1006
1129
|
console.log(`[codex-worker] Spawning: ${bin3()} ${args.slice(0, 6).join(" ")} ...`);
|
|
1007
1130
|
return new Promise((resolve, reject) => {
|
|
1008
1131
|
const proc = spawn4(bin3(), args, { cwd: config2.repoPath, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -1034,12 +1157,15 @@ async function runCodexSession(config2) {
|
|
|
1034
1157
|
proc.on("close", (code, signal) => {
|
|
1035
1158
|
cleanup();
|
|
1036
1159
|
console.log(`[codex-worker] Done \u2014 tools used: ${toolsUsed.length ? [...new Set(toolsUsed)].join(", ") : "none"}`);
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1160
|
+
const settle = () => {
|
|
1161
|
+
if (sessionError || signal || code !== 0 && code !== null) {
|
|
1162
|
+
const detail = (sessionError || stderrOutput).trim();
|
|
1163
|
+
reject(new Error(`codex exited with ${signal ?? `code ${code}`}${detail ? `: ${detail}` : ""}`));
|
|
1164
|
+
} else {
|
|
1165
|
+
resolve([...new Set(toolsUsed)]);
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
recordSessionUsage(config2, taskIdsPromise, notReportedUsage("adapter"), "[codex-worker]", { since: startedAt }).then(settle, settle);
|
|
1043
1169
|
});
|
|
1044
1170
|
proc.on("error", (err) => {
|
|
1045
1171
|
cleanup();
|
|
@@ -1287,6 +1413,10 @@ function loadConfig2() {
|
|
|
1287
1413
|
};
|
|
1288
1414
|
}
|
|
1289
1415
|
|
|
1416
|
+
// ../event-worker/src/worker.ts
|
|
1417
|
+
import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1418
|
+
import { dirname as dirname2 } from "node:path";
|
|
1419
|
+
|
|
1290
1420
|
// ../../node_modules/.pnpm/eventsource-parser@3.0.7/node_modules/eventsource-parser/dist/index.js
|
|
1291
1421
|
var ParseError = class extends Error {
|
|
1292
1422
|
constructor(message, options) {
|
|
@@ -1847,7 +1977,16 @@ async function runEventWorker(config2) {
|
|
|
1847
1977
|
}
|
|
1848
1978
|
try {
|
|
1849
1979
|
console.log("[event-worker] Event received \u2014 running session...");
|
|
1850
|
-
|
|
1980
|
+
const activityFile = process.env.PITBOSS_ACTIVITY_FILE;
|
|
1981
|
+
if (activityFile) {
|
|
1982
|
+
mkdirSync2(dirname2(activityFile), { recursive: true });
|
|
1983
|
+
writeFileSync3(activityFile, String(Date.now()));
|
|
1984
|
+
}
|
|
1985
|
+
try {
|
|
1986
|
+
await getAdapter(config2.runtime).runSession(config2);
|
|
1987
|
+
} finally {
|
|
1988
|
+
if (activityFile) rmSync(activityFile, { force: true });
|
|
1989
|
+
}
|
|
1851
1990
|
await clearUnavailable(config2, "[event-worker]");
|
|
1852
1991
|
} catch (err) {
|
|
1853
1992
|
const text = err instanceof Error ? err.message : String(err);
|