lobstah 0.3.2 → 0.5.5
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 +30 -8
- package/dist/main.js +5640 -1027
- package/dist/runner.js +503 -143
- package/docs/assets/favicon.png +0 -0
- package/docs/assets/lob-sprite.png +0 -0
- package/docs/assets/lob.png +0 -0
- package/docs/assets/star.png +0 -0
- package/docs/configuration.md +24 -0
- package/docs/design.md +7 -1
- package/docs/lobsterman.md +218 -23
- package/docs/vocabulary.md +149 -17
- package/package.json +1 -1
package/dist/runner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// packages/runner/src/index.ts
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fs10 from "node:fs";
|
|
3
|
+
import * as path8 from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
// packages/core/dist/types.js
|
|
@@ -36,19 +36,23 @@ function evidencePath(id, lane) {
|
|
|
36
36
|
return path.join(laneDirs(lane).state, `${id}.evidence`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// packages/core/dist/attachments.js
|
|
40
|
+
function attachmentBlock(attachments) {
|
|
41
|
+
if (attachments.length === 0)
|
|
42
|
+
return "";
|
|
43
|
+
return [
|
|
44
|
+
"--- ATTACHMENTS ---",
|
|
45
|
+
...attachments.map((a) => `${a.name} (${a.type}, ${a.bytes} bytes) at ${a.path}`),
|
|
46
|
+
"Images and PDFs can be read directly with your file tools."
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
// packages/core/dist/queue.js
|
|
40
|
-
import * as
|
|
51
|
+
import * as fs2 from "node:fs";
|
|
41
52
|
import * as path2 from "node:path";
|
|
42
|
-
function complete(id, lane) {
|
|
43
|
-
const dirs = laneDirs(lane);
|
|
44
|
-
fs.renameSync(path2.join(dirs.active, id), path2.join(dirs.done, id));
|
|
45
|
-
}
|
|
46
|
-
function cancelRequested(id, lane) {
|
|
47
|
-
return fs.existsSync(path2.join(laneDirs(lane).active, id, "cancel"));
|
|
48
|
-
}
|
|
49
53
|
|
|
50
54
|
// packages/core/dist/status.js
|
|
51
|
-
import * as
|
|
55
|
+
import * as fs from "node:fs";
|
|
52
56
|
function isVerb(v) {
|
|
53
57
|
return VERBS.includes(v);
|
|
54
58
|
}
|
|
@@ -57,15 +61,15 @@ function appendStatus(id, lane, verb, note) {
|
|
|
57
61
|
throw new Error(`invalid status verb "${verb}" \u2014 must be one of: ${VERBS.join(", ")}`);
|
|
58
62
|
}
|
|
59
63
|
const entry = { at: (/* @__PURE__ */ new Date()).toISOString(), verb, ...note ? { note } : {} };
|
|
60
|
-
|
|
64
|
+
fs.appendFileSync(statusPath(id, lane), `${JSON.stringify(entry)}
|
|
61
65
|
`);
|
|
62
66
|
return entry;
|
|
63
67
|
}
|
|
64
68
|
function readStatusLog(id, lane) {
|
|
65
69
|
const file = statusPath(id, lane);
|
|
66
|
-
if (!
|
|
70
|
+
if (!fs.existsSync(file))
|
|
67
71
|
return [];
|
|
68
|
-
return
|
|
72
|
+
return fs.readFileSync(file, "utf8").split("\n").filter(Boolean).flatMap((line) => {
|
|
69
73
|
try {
|
|
70
74
|
const parsed = JSON.parse(line);
|
|
71
75
|
return isVerb(parsed.verb) ? [parsed] : [];
|
|
@@ -75,12 +79,30 @@ function readStatusLog(id, lane) {
|
|
|
75
79
|
});
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
// packages/core/dist/queue.js
|
|
83
|
+
function complete(id, lane) {
|
|
84
|
+
const dirs = laneDirs(lane);
|
|
85
|
+
fs2.renameSync(path2.join(dirs.active, id), path2.join(dirs.done, id));
|
|
86
|
+
}
|
|
87
|
+
function cancelRequested(id, lane) {
|
|
88
|
+
return fs2.existsSync(path2.join(laneDirs(lane).active, id, "cancel"));
|
|
89
|
+
}
|
|
90
|
+
|
|
78
91
|
// packages/core/dist/events.js
|
|
79
92
|
import * as fs3 from "node:fs";
|
|
80
93
|
function appendEvent(id, lane, ev) {
|
|
81
94
|
fs3.appendFileSync(eventsPath(id, lane), `${JSON.stringify(ev)}
|
|
82
95
|
`);
|
|
83
96
|
}
|
|
97
|
+
function touchEvents(id, lane, at = /* @__PURE__ */ new Date()) {
|
|
98
|
+
const file = eventsPath(id, lane);
|
|
99
|
+
try {
|
|
100
|
+
fs3.utimesSync(file, at, at);
|
|
101
|
+
} catch {
|
|
102
|
+
fs3.appendFileSync(file, "");
|
|
103
|
+
fs3.utimesSync(file, at, at);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
84
106
|
|
|
85
107
|
// packages/core/dist/evidence.js
|
|
86
108
|
import * as fs4 from "node:fs";
|
|
@@ -101,6 +123,7 @@ function mergeEvidence(id, lane, patch) {
|
|
|
101
123
|
// packages/core/dist/inbox.js
|
|
102
124
|
import * as fs5 from "node:fs";
|
|
103
125
|
import * as path3 from "node:path";
|
|
126
|
+
var metaName = (file) => file.replace(/\.msg$/, ".meta.json");
|
|
104
127
|
function inboxDir(id, lane) {
|
|
105
128
|
return path3.join(laneDirs(lane).inbox, id);
|
|
106
129
|
}
|
|
@@ -114,6 +137,10 @@ function acknowledge(id, lane, file) {
|
|
|
114
137
|
const dir = inboxDir(id, lane);
|
|
115
138
|
fs5.mkdirSync(path3.join(dir, "handled"), { recursive: true });
|
|
116
139
|
fs5.renameSync(path3.join(dir, file), path3.join(dir, "handled", file));
|
|
140
|
+
try {
|
|
141
|
+
fs5.renameSync(path3.join(dir, metaName(file)), path3.join(dir, "handled", metaName(file)));
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
// packages/core/dist/config.js
|
|
@@ -767,17 +794,37 @@ function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
|
767
794
|
}
|
|
768
795
|
|
|
769
796
|
// packages/core/dist/config.js
|
|
797
|
+
var ATTENTION_KINDS = ["question", "landed", "pr:draft", "pr:review", "pr:checks", "pr:ready"];
|
|
798
|
+
var DEFAULT_ATTENTION_KINDS = ["question", "pr:draft", "pr:review", "pr:checks", "pr:ready"];
|
|
799
|
+
function parseAttentionKinds(raw) {
|
|
800
|
+
if (raw === void 0)
|
|
801
|
+
return [...DEFAULT_ATTENTION_KINDS];
|
|
802
|
+
if (!Array.isArray(raw)) {
|
|
803
|
+
throw new Error(`attentionKinds must be an array of kinds (${ATTENTION_KINDS.join(", ")}) in ${configPath()}`);
|
|
804
|
+
}
|
|
805
|
+
for (const k of raw) {
|
|
806
|
+
if (!ATTENTION_KINDS.includes(k)) {
|
|
807
|
+
throw new Error(`attentionKinds: unknown kind "${String(k)}" in ${configPath()} \u2014 valid kinds: ${ATTENTION_KINDS.join(", ")}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return [...new Set(raw)];
|
|
811
|
+
}
|
|
770
812
|
var DEFAULT_SOAK = {
|
|
771
813
|
deferSecs: 90,
|
|
772
814
|
ttlSecs: 1800
|
|
773
815
|
};
|
|
816
|
+
var DEFAULT_HELM = {
|
|
817
|
+
ttlSecs: 1800,
|
|
818
|
+
reportSecs: 900
|
|
819
|
+
};
|
|
774
820
|
var DEFAULT_LIMITS = {
|
|
775
821
|
maxConcurrent: 2,
|
|
776
822
|
choreConcurrent: 1,
|
|
777
823
|
wedgeThresholdSecs: 600,
|
|
778
824
|
maxRestartAttempts: 2,
|
|
779
825
|
wallClockSecs: 3600,
|
|
780
|
-
choreRetentionDays: 7
|
|
826
|
+
choreRetentionDays: 7,
|
|
827
|
+
attachmentMaxBytes: 25 * 1024 * 1024
|
|
781
828
|
};
|
|
782
829
|
function configPath() {
|
|
783
830
|
return path4.join(lobstahHome(), "config.toml");
|
|
@@ -801,14 +848,22 @@ function loadConfig() {
|
|
|
801
848
|
pickup: r.pickup === void 0 ? void 0 : Boolean(r.pickup)
|
|
802
849
|
};
|
|
803
850
|
}
|
|
851
|
+
const groundsRaw = raw.grounds ?? {};
|
|
852
|
+
const grounds = {};
|
|
853
|
+
for (const [key, g] of Object.entries(groundsRaw)) {
|
|
854
|
+
grounds[key] = { repos: Array.isArray(g.repos) ? g.repos.map(String) : [] };
|
|
855
|
+
}
|
|
804
856
|
return {
|
|
805
857
|
repos,
|
|
806
858
|
harness: raw.harness ?? {},
|
|
807
859
|
limits: { ...DEFAULT_LIMITS, ...raw.limits ?? {} },
|
|
808
860
|
soak: { ...DEFAULT_SOAK, ...raw.soak ?? {} },
|
|
861
|
+
helm: { ...DEFAULT_HELM, ...raw.helm ?? {} },
|
|
862
|
+
grounds,
|
|
809
863
|
notifyCommand: raw.notifyCommand ? String(raw.notifyCommand) : void 0,
|
|
810
864
|
notifyVerbs: Array.isArray(raw.notifyVerbs) ? raw.notifyVerbs.map(String) : void 0,
|
|
811
|
-
remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0
|
|
865
|
+
remindSecs: raw.remindSecs !== void 0 ? Number(raw.remindSecs) : void 0,
|
|
866
|
+
attentionKinds: parseAttentionKinds(raw.attentionKinds)
|
|
812
867
|
};
|
|
813
868
|
}
|
|
814
869
|
function resolveDispatch(d, cfg) {
|
|
@@ -830,6 +885,58 @@ function resolveDispatch(d, cfg) {
|
|
|
830
885
|
};
|
|
831
886
|
}
|
|
832
887
|
|
|
888
|
+
// packages/core/dist/harness.js
|
|
889
|
+
import * as fs7 from "node:fs";
|
|
890
|
+
import * as path5 from "node:path";
|
|
891
|
+
import { createRequire } from "node:module";
|
|
892
|
+
function resolveOnPath(bin, pathVar = process.env.PATH) {
|
|
893
|
+
if (!pathVar)
|
|
894
|
+
return void 0;
|
|
895
|
+
const names = process.platform === "win32" ? [bin, `${bin}.exe`, `${bin}.cmd`, `${bin}.bat`] : [bin];
|
|
896
|
+
for (const dir of pathVar.split(path5.delimiter)) {
|
|
897
|
+
if (!dir)
|
|
898
|
+
continue;
|
|
899
|
+
for (const name of names) {
|
|
900
|
+
const candidate = path5.join(dir, name);
|
|
901
|
+
try {
|
|
902
|
+
fs7.accessSync(candidate, fs7.constants.X_OK);
|
|
903
|
+
if (fs7.statSync(candidate).isFile())
|
|
904
|
+
return candidate;
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return void 0;
|
|
910
|
+
}
|
|
911
|
+
function onPath(bin, pathVar = process.env.PATH) {
|
|
912
|
+
return resolveOnPath(bin, pathVar) !== void 0;
|
|
913
|
+
}
|
|
914
|
+
function codexInvocation(argv) {
|
|
915
|
+
if (onPath("codex"))
|
|
916
|
+
return { file: "codex", argv };
|
|
917
|
+
const require_ = createRequire(import.meta.url);
|
|
918
|
+
try {
|
|
919
|
+
const cli = require_.resolve("@openai/codex/bin/codex.js");
|
|
920
|
+
return { file: process.execPath, argv: [cli, ...argv] };
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
for (const dir of require_.resolve.paths("@openai/codex") ?? []) {
|
|
924
|
+
const cli = path5.join(dir, "@openai", "codex", "bin", "codex.js");
|
|
925
|
+
if (fs7.existsSync(cli))
|
|
926
|
+
return { file: process.execPath, argv: [cli, ...argv] };
|
|
927
|
+
}
|
|
928
|
+
return void 0;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// packages/core/dist/compiled.js
|
|
932
|
+
var COMPILED_BINARY = (() => {
|
|
933
|
+
const bun = globalThis.Bun;
|
|
934
|
+
return bun !== void 0 && String(bun.main ?? "").includes("$bunfs");
|
|
935
|
+
})();
|
|
936
|
+
|
|
937
|
+
// packages/adapters/dist/claude.js
|
|
938
|
+
import { spawn } from "node:child_process";
|
|
939
|
+
|
|
833
940
|
// packages/adapters/dist/types.js
|
|
834
941
|
var AsyncQueue = class {
|
|
835
942
|
buffer = [];
|
|
@@ -908,29 +1015,154 @@ function flagsToExtraArgs(flags) {
|
|
|
908
1015
|
}
|
|
909
1016
|
return extra;
|
|
910
1017
|
}
|
|
1018
|
+
function pumpClaudeMessage(msg, push, onSession) {
|
|
1019
|
+
const at = now();
|
|
1020
|
+
if (msg.type === "system" && msg.subtype === "init") {
|
|
1021
|
+
if (msg.session_id)
|
|
1022
|
+
onSession(msg.session_id);
|
|
1023
|
+
push({ at, type: "session", data: { sessionId: msg.session_id } });
|
|
1024
|
+
} else if (msg.type === "assistant") {
|
|
1025
|
+
for (const block of msg.message?.content ?? []) {
|
|
1026
|
+
if (block.type === "tool_use") {
|
|
1027
|
+
push({ at, type: "tool-start", data: { name: block.name } });
|
|
1028
|
+
} else if (block.type === "text" && block.text) {
|
|
1029
|
+
push({ at, type: "text", data: { text: String(block.text).slice(0, 2e3) } });
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
} else if (msg.type === "user") {
|
|
1033
|
+
const content = msg.message?.content;
|
|
1034
|
+
if (Array.isArray(content)) {
|
|
1035
|
+
for (const block of content) {
|
|
1036
|
+
if (block.type === "tool_result")
|
|
1037
|
+
push({ at, type: "tool-end", data: {} });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
} else if (msg.type === "result") {
|
|
1041
|
+
push({ at, type: "turn-end", data: { subtype: msg.subtype, costUsd: msg.total_cost_usd } });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
var userMessage = (content) => ({
|
|
1045
|
+
type: "user",
|
|
1046
|
+
message: { role: "user", content },
|
|
1047
|
+
parent_tool_use_id: null,
|
|
1048
|
+
session_id: ""
|
|
1049
|
+
});
|
|
1050
|
+
function claudeCliArgs(opts) {
|
|
1051
|
+
const args = [
|
|
1052
|
+
"-p",
|
|
1053
|
+
"--output-format",
|
|
1054
|
+
"stream-json",
|
|
1055
|
+
"--input-format",
|
|
1056
|
+
"stream-json",
|
|
1057
|
+
"--verbose",
|
|
1058
|
+
"--permission-mode",
|
|
1059
|
+
"bypassPermissions"
|
|
1060
|
+
];
|
|
1061
|
+
if (opts.model)
|
|
1062
|
+
args.push("--model", opts.model);
|
|
1063
|
+
if (opts.limits.maxTurns)
|
|
1064
|
+
args.push("--max-turns", String(opts.limits.maxTurns));
|
|
1065
|
+
if (opts.resumeSession)
|
|
1066
|
+
args.push("--resume", opts.resumeSession, "--fork-session");
|
|
1067
|
+
args.push(...opts.flags);
|
|
1068
|
+
return args;
|
|
1069
|
+
}
|
|
1070
|
+
function startClaudeCli(invocation, opts) {
|
|
1071
|
+
const child = spawn(invocation.file, [...invocation.argvPrefix, ...claudeCliArgs(opts)], {
|
|
1072
|
+
cwd: opts.cwd,
|
|
1073
|
+
env: { ...process.env, ...opts.env },
|
|
1074
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1075
|
+
shell: process.platform === "win32"
|
|
1076
|
+
// .cmd shims need a shell to exec
|
|
1077
|
+
});
|
|
1078
|
+
const events = new AsyncQueue();
|
|
1079
|
+
const input = new InputGate();
|
|
1080
|
+
let sessionId;
|
|
1081
|
+
let stderrTail = "";
|
|
1082
|
+
child.stdin.on("error", () => {
|
|
1083
|
+
});
|
|
1084
|
+
child.stderr.on("data", (d) => {
|
|
1085
|
+
stderrTail = (stderrTail + d.toString()).slice(-2e3);
|
|
1086
|
+
});
|
|
1087
|
+
child.stdin.write(`${JSON.stringify(userMessage(opts.prompt))}
|
|
1088
|
+
`);
|
|
1089
|
+
void (async () => {
|
|
1090
|
+
while (true) {
|
|
1091
|
+
const text = await input.next();
|
|
1092
|
+
if (text === void 0)
|
|
1093
|
+
break;
|
|
1094
|
+
child.stdin.write(`${JSON.stringify(userMessage(text))}
|
|
1095
|
+
`);
|
|
1096
|
+
}
|
|
1097
|
+
child.stdin.end();
|
|
1098
|
+
})();
|
|
1099
|
+
let buf = "";
|
|
1100
|
+
child.stdout.on("data", (d) => {
|
|
1101
|
+
buf += d.toString();
|
|
1102
|
+
let i;
|
|
1103
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
1104
|
+
const line = buf.slice(0, i).trim();
|
|
1105
|
+
buf = buf.slice(i + 1);
|
|
1106
|
+
if (!line)
|
|
1107
|
+
continue;
|
|
1108
|
+
try {
|
|
1109
|
+
pumpClaudeMessage(JSON.parse(line), (e) => events.push(e), (id) => sessionId = id);
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
const done = new Promise((resolve2) => {
|
|
1115
|
+
child.on("error", (err) => {
|
|
1116
|
+
events.push({ at: now(), type: "error", data: { message: err.message } });
|
|
1117
|
+
events.close();
|
|
1118
|
+
resolve2({ sessionId, error: err.message });
|
|
1119
|
+
});
|
|
1120
|
+
child.on("exit", (code) => {
|
|
1121
|
+
events.close();
|
|
1122
|
+
if (code === 0)
|
|
1123
|
+
resolve2({ sessionId });
|
|
1124
|
+
else {
|
|
1125
|
+
const detail = stderrTail.trim().split("\n").at(-1);
|
|
1126
|
+
const error = `claude exited ${code}${detail ? ` \u2014 ${detail}` : ""}`;
|
|
1127
|
+
resolve2({ sessionId, error });
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
return {
|
|
1132
|
+
events,
|
|
1133
|
+
send: (text) => input.send(text),
|
|
1134
|
+
end: () => input.end(),
|
|
1135
|
+
done,
|
|
1136
|
+
kill: () => {
|
|
1137
|
+
try {
|
|
1138
|
+
child.kill("SIGKILL");
|
|
1139
|
+
} catch {
|
|
1140
|
+
}
|
|
1141
|
+
input.end();
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
911
1145
|
function createClaudeAdapter() {
|
|
912
1146
|
return {
|
|
913
1147
|
name: "claude",
|
|
914
1148
|
async start(opts) {
|
|
915
|
-
|
|
1149
|
+
let sdk;
|
|
1150
|
+
try {
|
|
1151
|
+
sdk = await import("@anthropic-ai/claude-agent-sdk");
|
|
1152
|
+
} catch (err) {
|
|
1153
|
+
if (onPath("claude"))
|
|
1154
|
+
return startClaudeCli({ file: "claude", argvPrefix: [] }, opts);
|
|
1155
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1156
|
+
throw new Error(`claude adapter: agent SDK unavailable (${detail}) and no \`claude\` CLI on PATH`);
|
|
1157
|
+
}
|
|
916
1158
|
const input = new InputGate();
|
|
917
1159
|
async function* userMessages() {
|
|
918
|
-
yield
|
|
919
|
-
type: "user",
|
|
920
|
-
message: { role: "user", content: opts.prompt },
|
|
921
|
-
parent_tool_use_id: null,
|
|
922
|
-
session_id: ""
|
|
923
|
-
};
|
|
1160
|
+
yield userMessage(opts.prompt);
|
|
924
1161
|
while (true) {
|
|
925
1162
|
const text = await input.next();
|
|
926
1163
|
if (text === void 0)
|
|
927
1164
|
return;
|
|
928
|
-
yield
|
|
929
|
-
type: "user",
|
|
930
|
-
message: { role: "user", content: text },
|
|
931
|
-
parent_tool_use_id: null,
|
|
932
|
-
session_id: ""
|
|
933
|
-
};
|
|
1165
|
+
yield userMessage(text);
|
|
934
1166
|
}
|
|
935
1167
|
}
|
|
936
1168
|
const q = sdk.query({
|
|
@@ -951,29 +1183,7 @@ function createClaudeAdapter() {
|
|
|
951
1183
|
const done = (async () => {
|
|
952
1184
|
try {
|
|
953
1185
|
for await (const msg of q) {
|
|
954
|
-
|
|
955
|
-
if (msg.type === "system" && msg.subtype === "init") {
|
|
956
|
-
sessionId = msg.session_id;
|
|
957
|
-
events.push({ at, type: "session", data: { sessionId } });
|
|
958
|
-
} else if (msg.type === "assistant") {
|
|
959
|
-
for (const block of msg.message?.content ?? []) {
|
|
960
|
-
if (block.type === "tool_use") {
|
|
961
|
-
events.push({ at, type: "tool-start", data: { name: block.name } });
|
|
962
|
-
} else if (block.type === "text" && block.text) {
|
|
963
|
-
events.push({ at, type: "text", data: { text: String(block.text).slice(0, 2e3) } });
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
} else if (msg.type === "user") {
|
|
967
|
-
const content = msg.message?.content;
|
|
968
|
-
if (Array.isArray(content)) {
|
|
969
|
-
for (const block of content) {
|
|
970
|
-
if (block.type === "tool_result")
|
|
971
|
-
events.push({ at, type: "tool-end", data: {} });
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
} else if (msg.type === "result") {
|
|
975
|
-
events.push({ at, type: "turn-end", data: { subtype: msg.subtype, costUsd: msg.total_cost_usd } });
|
|
976
|
-
}
|
|
1186
|
+
pumpClaudeMessage(msg, (e) => events.push(e), (id) => sessionId = id);
|
|
977
1187
|
}
|
|
978
1188
|
return { sessionId };
|
|
979
1189
|
} catch (err) {
|
|
@@ -1002,32 +1212,159 @@ function createClaudeAdapter() {
|
|
|
1002
1212
|
}
|
|
1003
1213
|
|
|
1004
1214
|
// packages/adapters/dist/codex.js
|
|
1005
|
-
import * as
|
|
1215
|
+
import * as fs8 from "node:fs";
|
|
1006
1216
|
import * as os3 from "node:os";
|
|
1007
|
-
import * as
|
|
1217
|
+
import * as path6 from "node:path";
|
|
1218
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
1008
1219
|
function isolatedCodexHome() {
|
|
1009
|
-
const dir =
|
|
1010
|
-
|
|
1011
|
-
const auth =
|
|
1012
|
-
const realAuth =
|
|
1013
|
-
if (!
|
|
1220
|
+
const dir = path6.join(lobstahHome(), "codex-home");
|
|
1221
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1222
|
+
const auth = path6.join(dir, "auth.json");
|
|
1223
|
+
const realAuth = path6.join(os3.homedir(), ".codex", "auth.json");
|
|
1224
|
+
if (!fs8.existsSync(auth) && fs8.existsSync(realAuth)) {
|
|
1014
1225
|
try {
|
|
1015
|
-
|
|
1226
|
+
fs8.symlinkSync(realAuth, auth);
|
|
1016
1227
|
} catch {
|
|
1017
|
-
|
|
1228
|
+
fs8.copyFileSync(realAuth, auth);
|
|
1018
1229
|
}
|
|
1019
1230
|
}
|
|
1020
|
-
const cfg =
|
|
1021
|
-
if (!
|
|
1022
|
-
|
|
1231
|
+
const cfg = path6.join(dir, "config.toml");
|
|
1232
|
+
if (!fs8.existsSync(cfg)) {
|
|
1233
|
+
fs8.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
|
|
1023
1234
|
}
|
|
1024
1235
|
return dir;
|
|
1025
1236
|
}
|
|
1237
|
+
function pumpCodexEvent(ev, push, onSession) {
|
|
1238
|
+
const at = now();
|
|
1239
|
+
if (ev.type === "thread.started") {
|
|
1240
|
+
if (ev.thread_id)
|
|
1241
|
+
onSession(ev.thread_id);
|
|
1242
|
+
push({ at, type: "session", data: { sessionId: ev.thread_id } });
|
|
1243
|
+
} else if (ev.type === "turn.started") {
|
|
1244
|
+
push({ at, type: "turn-start", data: {} });
|
|
1245
|
+
} else if (ev.type === "item.started") {
|
|
1246
|
+
push({ at, type: "tool-start", data: { name: ev.item?.type } });
|
|
1247
|
+
} else if (ev.type === "item.completed") {
|
|
1248
|
+
if (ev.item?.type === "agent_message" && ev.item?.text) {
|
|
1249
|
+
push({ at, type: "text", data: { text: String(ev.item.text).slice(0, 2e3) } });
|
|
1250
|
+
} else {
|
|
1251
|
+
push({ at, type: "tool-end", data: { name: ev.item?.type } });
|
|
1252
|
+
}
|
|
1253
|
+
} else if (ev.type === "turn.failed") {
|
|
1254
|
+
const message = ev.error?.message ?? "turn failed";
|
|
1255
|
+
push({ at, type: "error", data: { message } });
|
|
1256
|
+
return message;
|
|
1257
|
+
}
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
function codexExecArgs(opts, prompt, resumeThread) {
|
|
1261
|
+
const args = ["exec"];
|
|
1262
|
+
if (resumeThread)
|
|
1263
|
+
args.push("resume", resumeThread);
|
|
1264
|
+
args.push("--json", "--skip-git-repo-check", "--sandbox", "workspace-write", "--cd", opts.cwd);
|
|
1265
|
+
if (opts.model)
|
|
1266
|
+
args.push("-m", opts.model);
|
|
1267
|
+
if (opts.effort)
|
|
1268
|
+
args.push("-c", `model_reasoning_effort=${JSON.stringify(opts.effort)}`);
|
|
1269
|
+
args.push(prompt);
|
|
1270
|
+
return args;
|
|
1271
|
+
}
|
|
1272
|
+
function startCodexCli(invoke, opts) {
|
|
1273
|
+
const input = new InputGate();
|
|
1274
|
+
const events = new AsyncQueue();
|
|
1275
|
+
let sessionId;
|
|
1276
|
+
let current;
|
|
1277
|
+
let killed = false;
|
|
1278
|
+
function runTurn(prompt, resumeThread) {
|
|
1279
|
+
return new Promise((resolve2) => {
|
|
1280
|
+
const inv = invoke(codexExecArgs(opts, prompt, resumeThread));
|
|
1281
|
+
const child = spawn2(inv.file, inv.argv, {
|
|
1282
|
+
cwd: opts.cwd,
|
|
1283
|
+
env: { ...process.env, ...opts.env, CODEX_HOME: isolatedCodexHome() },
|
|
1284
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1285
|
+
shell: process.platform === "win32" && inv.file !== process.execPath
|
|
1286
|
+
});
|
|
1287
|
+
current = child;
|
|
1288
|
+
let turnError;
|
|
1289
|
+
let stderrTail = "";
|
|
1290
|
+
child.stderr?.on("data", (d) => {
|
|
1291
|
+
stderrTail = (stderrTail + d.toString()).slice(-2e3);
|
|
1292
|
+
});
|
|
1293
|
+
let buf = "";
|
|
1294
|
+
child.stdout?.on("data", (d) => {
|
|
1295
|
+
buf += d.toString();
|
|
1296
|
+
let i;
|
|
1297
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
1298
|
+
const line = buf.slice(0, i).trim();
|
|
1299
|
+
buf = buf.slice(i + 1);
|
|
1300
|
+
if (!line)
|
|
1301
|
+
continue;
|
|
1302
|
+
try {
|
|
1303
|
+
const failed = pumpCodexEvent(JSON.parse(line), (e) => events.push(e), (id) => sessionId = id);
|
|
1304
|
+
if (failed)
|
|
1305
|
+
turnError = failed;
|
|
1306
|
+
} catch {
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
child.on("error", (err) => resolve2(err.message));
|
|
1311
|
+
child.on("exit", (code) => {
|
|
1312
|
+
if (turnError)
|
|
1313
|
+
return resolve2(turnError);
|
|
1314
|
+
if (code === 0 || killed)
|
|
1315
|
+
return resolve2(void 0);
|
|
1316
|
+
const detail = stderrTail.trim().split("\n").at(-1);
|
|
1317
|
+
resolve2(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
const done = (async () => {
|
|
1322
|
+
try {
|
|
1323
|
+
let next = opts.prompt;
|
|
1324
|
+
let thread = opts.resumeSession;
|
|
1325
|
+
while (next !== void 0 && !killed) {
|
|
1326
|
+
const error = await runTurn(next, thread);
|
|
1327
|
+
thread = sessionId ?? thread;
|
|
1328
|
+
if (error) {
|
|
1329
|
+
events.push({ at: now(), type: "error", data: { message: error } });
|
|
1330
|
+
return { sessionId, error };
|
|
1331
|
+
}
|
|
1332
|
+
events.push({ at: now(), type: "turn-end", data: {} });
|
|
1333
|
+
next = await input.next();
|
|
1334
|
+
}
|
|
1335
|
+
return { sessionId };
|
|
1336
|
+
} finally {
|
|
1337
|
+
events.close();
|
|
1338
|
+
}
|
|
1339
|
+
})();
|
|
1340
|
+
return {
|
|
1341
|
+
events,
|
|
1342
|
+
send: (text) => input.send(text),
|
|
1343
|
+
end: () => input.end(),
|
|
1344
|
+
done,
|
|
1345
|
+
kill: () => {
|
|
1346
|
+
killed = true;
|
|
1347
|
+
try {
|
|
1348
|
+
current?.kill("SIGKILL");
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
input.end();
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1026
1355
|
function createCodexAdapter() {
|
|
1027
1356
|
return {
|
|
1028
1357
|
name: "codex",
|
|
1029
1358
|
async start(opts) {
|
|
1030
|
-
|
|
1359
|
+
let mod;
|
|
1360
|
+
try {
|
|
1361
|
+
mod = await import("@openai/codex-sdk");
|
|
1362
|
+
} catch (err) {
|
|
1363
|
+
if (codexInvocation([]))
|
|
1364
|
+
return startCodexCli((argv) => codexInvocation(argv), opts);
|
|
1365
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1366
|
+
throw new Error(`codex adapter: SDK unavailable (${detail}) and no codex CLI found (PATH or vendored)`);
|
|
1367
|
+
}
|
|
1031
1368
|
const codex = new mod.Codex({
|
|
1032
1369
|
env: { ...process.env, ...opts.env, CODEX_HOME: isolatedCodexHome() }
|
|
1033
1370
|
});
|
|
@@ -1050,24 +1387,9 @@ function createCodexAdapter() {
|
|
|
1050
1387
|
while (next !== void 0) {
|
|
1051
1388
|
const streamed = await thread.runStreamed(next);
|
|
1052
1389
|
for await (const ev of streamed.events) {
|
|
1053
|
-
const
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1056
|
-
events.push({ at, type: "session", data: { sessionId } });
|
|
1057
|
-
} else if (ev.type === "turn.started") {
|
|
1058
|
-
events.push({ at, type: "turn-start", data: {} });
|
|
1059
|
-
} else if (ev.type === "item.started") {
|
|
1060
|
-
events.push({ at, type: "tool-start", data: { name: ev.item?.type } });
|
|
1061
|
-
} else if (ev.type === "item.completed") {
|
|
1062
|
-
if (ev.item?.type === "agent_message" && ev.item?.text) {
|
|
1063
|
-
events.push({ at, type: "text", data: { text: String(ev.item.text).slice(0, 2e3) } });
|
|
1064
|
-
} else {
|
|
1065
|
-
events.push({ at, type: "tool-end", data: { name: ev.item?.type } });
|
|
1066
|
-
}
|
|
1067
|
-
} else if (ev.type === "turn.failed") {
|
|
1068
|
-
lastTurnError = ev.error?.message ?? "turn failed";
|
|
1069
|
-
events.push({ at, type: "error", data: { message: lastTurnError } });
|
|
1070
|
-
}
|
|
1390
|
+
const failed = pumpCodexEvent(ev, (e) => events.push(e), (id) => sessionId = id);
|
|
1391
|
+
if (failed)
|
|
1392
|
+
lastTurnError = failed;
|
|
1071
1393
|
}
|
|
1072
1394
|
events.push({ at: now(), type: "turn-end", data: {} });
|
|
1073
1395
|
next = await input.next();
|
|
@@ -1105,14 +1427,14 @@ function loadAdapter(name) {
|
|
|
1105
1427
|
}
|
|
1106
1428
|
|
|
1107
1429
|
// packages/worktree/dist/index.js
|
|
1108
|
-
import * as
|
|
1109
|
-
import * as
|
|
1430
|
+
import * as fs9 from "node:fs";
|
|
1431
|
+
import * as path7 from "node:path";
|
|
1110
1432
|
import { exec, execFile } from "node:child_process";
|
|
1111
1433
|
import { promisify } from "node:util";
|
|
1112
1434
|
var run = promisify(execFile);
|
|
1113
1435
|
var shell = promisify(exec);
|
|
1114
1436
|
function worktreePath(id) {
|
|
1115
|
-
return
|
|
1437
|
+
return path7.join(lobstahHome(), "worktrees", id);
|
|
1116
1438
|
}
|
|
1117
1439
|
async function git(cwd, ...args) {
|
|
1118
1440
|
const { stdout } = await run("git", args, { cwd, env: process.env });
|
|
@@ -1120,10 +1442,10 @@ async function git(cwd, ...args) {
|
|
|
1120
1442
|
}
|
|
1121
1443
|
async function allocate(repo, id) {
|
|
1122
1444
|
const dir = worktreePath(id);
|
|
1123
|
-
if (
|
|
1445
|
+
if (fs9.existsSync(dir)) {
|
|
1124
1446
|
throw new Error(`worktree for ${id} already exists at ${dir} \u2014 never allocate a second`);
|
|
1125
1447
|
}
|
|
1126
|
-
if (!
|
|
1448
|
+
if (!fs9.existsSync(repo.path)) {
|
|
1127
1449
|
if (!repo.origin)
|
|
1128
1450
|
throw new Error(`repo path ${repo.path} missing and no origin configured`);
|
|
1129
1451
|
await run("git", ["clone", repo.origin, repo.path], { env: process.env });
|
|
@@ -1154,10 +1476,77 @@ function buildPrompt(brief, opts) {
|
|
|
1154
1476
|
`--- BRIEF ---`,
|
|
1155
1477
|
brief
|
|
1156
1478
|
];
|
|
1479
|
+
if (opts.attachments?.length) parts.push(attachmentBlock(opts.attachments));
|
|
1157
1480
|
if (opts.nudge) parts.push(`--- SUPERVISOR NOTE ---`, opts.nudge);
|
|
1158
1481
|
return parts.join("\n\n");
|
|
1159
1482
|
}
|
|
1160
1483
|
|
|
1484
|
+
// packages/runner/src/drive.ts
|
|
1485
|
+
var WAITING_VERBS = ["needs-decision", "blocked", "paused"];
|
|
1486
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1487
|
+
function deliver(run2, id, lane) {
|
|
1488
|
+
const msgs = unhandled(id, lane);
|
|
1489
|
+
for (const m of msgs) {
|
|
1490
|
+
run2.send(m.text);
|
|
1491
|
+
acknowledge(id, lane, m.file);
|
|
1492
|
+
}
|
|
1493
|
+
return msgs.length;
|
|
1494
|
+
}
|
|
1495
|
+
async function drive(run2, opts) {
|
|
1496
|
+
const { id, lane, pollMs = 3e3, stopped = () => false } = opts;
|
|
1497
|
+
let cancelled = false;
|
|
1498
|
+
let finished = false;
|
|
1499
|
+
void run2.done.then(() => finished = true);
|
|
1500
|
+
const cancel = () => {
|
|
1501
|
+
cancelled = true;
|
|
1502
|
+
run2.kill();
|
|
1503
|
+
};
|
|
1504
|
+
const answered = (verb) => {
|
|
1505
|
+
appendStatus(id, lane, "working", "operator message delivered");
|
|
1506
|
+
appendEvent(id, lane, { at: (/* @__PURE__ */ new Date()).toISOString(), type: "runner", data: { resumed: verb } });
|
|
1507
|
+
};
|
|
1508
|
+
const awaitAnswer = async (verb) => {
|
|
1509
|
+
appendEvent(id, lane, { at: (/* @__PURE__ */ new Date()).toISOString(), type: "runner", data: { waiting: verb } });
|
|
1510
|
+
while (true) {
|
|
1511
|
+
if (stopped() || finished) return;
|
|
1512
|
+
if (cancelRequested(id, lane)) return cancel();
|
|
1513
|
+
if (deliver(run2, id, lane) > 0) return answered(verb);
|
|
1514
|
+
touchEvents(id, lane);
|
|
1515
|
+
await sleep(pollMs);
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
for await (const ev of run2.events) {
|
|
1519
|
+
appendEvent(id, lane, ev);
|
|
1520
|
+
if (ev.type === "session" && ev.data?.sessionId) {
|
|
1521
|
+
mergeEvidence(id, lane, { sessionId: String(ev.data.sessionId) });
|
|
1522
|
+
}
|
|
1523
|
+
if (ev.type !== "turn-end" || cancelled || stopped()) continue;
|
|
1524
|
+
if (cancelRequested(id, lane)) {
|
|
1525
|
+
cancel();
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
const lastVerb = readStatusLog(id, lane).at(-1)?.verb;
|
|
1529
|
+
const waiting = lastVerb !== void 0 && WAITING_VERBS.includes(lastVerb);
|
|
1530
|
+
if (deliver(run2, id, lane) > 0) {
|
|
1531
|
+
if (waiting) answered(lastVerb);
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (waiting) {
|
|
1535
|
+
await awaitAnswer(lastVerb);
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
run2.end();
|
|
1539
|
+
}
|
|
1540
|
+
return { cancelled };
|
|
1541
|
+
}
|
|
1542
|
+
function settle(id, lane, r) {
|
|
1543
|
+
const lastVerb = readStatusLog(id, lane).at(-1)?.verb;
|
|
1544
|
+
if (r.cancelled) appendStatus(id, lane, "failed", "cancelled by operator");
|
|
1545
|
+
else if (r.wallClockHit) appendStatus(id, lane, "failed", "wall-clock limit exceeded");
|
|
1546
|
+
else if (r.error) appendStatus(id, lane, "failed", r.error.slice(0, 500));
|
|
1547
|
+
else if (!lastVerb || !TERMINAL_VERBS.includes(lastVerb)) appendStatus(id, lane, "done");
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1161
1550
|
// packages/runner/src/index.ts
|
|
1162
1551
|
function followUpSession(followUp) {
|
|
1163
1552
|
for (const lane of ["work", "chore"]) {
|
|
@@ -1167,40 +1556,40 @@ function followUpSession(followUp) {
|
|
|
1167
1556
|
return void 0;
|
|
1168
1557
|
}
|
|
1169
1558
|
async function main(activeDir, lane) {
|
|
1170
|
-
const id =
|
|
1559
|
+
const id = path8.basename(activeDir);
|
|
1171
1560
|
const status = (verb, note) => appendStatus(id, lane, verb, note);
|
|
1172
1561
|
const cfg = loadConfig();
|
|
1173
|
-
const descriptor = JSON.parse(
|
|
1562
|
+
const descriptor = JSON.parse(fs10.readFileSync(path8.join(activeDir, "descriptor.json"), "utf8"));
|
|
1174
1563
|
const resolved = resolveDispatch(descriptor, cfg);
|
|
1175
1564
|
const repo = cfg.repos[descriptor.repo];
|
|
1176
|
-
const briefFile =
|
|
1177
|
-
if (!
|
|
1178
|
-
const brief =
|
|
1565
|
+
const briefFile = path8.join(activeDir, "brief.md");
|
|
1566
|
+
if (!fs10.existsSync(briefFile)) fs10.writeFileSync(briefFile, descriptor.brief);
|
|
1567
|
+
const brief = fs10.readFileSync(briefFile, "utf8");
|
|
1179
1568
|
const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
|
|
1180
1569
|
const runnerInfo = {
|
|
1181
1570
|
pid: process.pid,
|
|
1182
1571
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1183
1572
|
attempts
|
|
1184
1573
|
};
|
|
1185
|
-
|
|
1574
|
+
fs10.writeFileSync(path8.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
|
|
1186
1575
|
status("working", attempts > 1 ? `attempt ${attempts}` : void 0);
|
|
1187
1576
|
appendEvent(id, lane, { at: (/* @__PURE__ */ new Date()).toISOString(), type: "runner", data: { pid: process.pid, attempts } });
|
|
1188
|
-
const wtFile =
|
|
1577
|
+
const wtFile = path8.join(activeDir, "worktree.json");
|
|
1189
1578
|
let cwd;
|
|
1190
|
-
if (
|
|
1191
|
-
cwd = JSON.parse(
|
|
1192
|
-
if (!
|
|
1193
|
-
} else if (
|
|
1579
|
+
if (fs10.existsSync(wtFile)) {
|
|
1580
|
+
cwd = JSON.parse(fs10.readFileSync(wtFile, "utf8")).path;
|
|
1581
|
+
if (!fs10.existsSync(cwd)) throw new Error(`recorded worktree ${cwd} is gone`);
|
|
1582
|
+
} else if (fs10.existsSync(worktreePath(id))) {
|
|
1194
1583
|
throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
|
|
1195
1584
|
} else {
|
|
1196
1585
|
cwd = await allocate(repo, id);
|
|
1197
|
-
|
|
1586
|
+
fs10.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
|
|
1198
1587
|
}
|
|
1199
1588
|
const resumeSession = process.env.LOBSTAH_RESUME ?? (descriptor.followUp ? followUpSession(descriptor.followUp) : void 0);
|
|
1200
1589
|
const adapter = loadAdapter(resolved.harness);
|
|
1201
|
-
const prompt = buildPrompt(brief, { id, nudge: process.env.LOBSTAH_NUDGE });
|
|
1202
|
-
const binDir =
|
|
1203
|
-
const workerPath =
|
|
1590
|
+
const prompt = buildPrompt(brief, { id, nudge: process.env.LOBSTAH_NUDGE, attachments: descriptor.attachments });
|
|
1591
|
+
const binDir = path8.resolve(path8.dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "bin");
|
|
1592
|
+
const workerPath = fs10.existsSync(path8.join(binDir, "lobstah")) ? `${binDir}${path8.delimiter}${process.env.PATH ?? ""}` : void 0;
|
|
1204
1593
|
const run2 = await adapter.start({
|
|
1205
1594
|
id,
|
|
1206
1595
|
cwd,
|
|
@@ -1221,29 +1610,7 @@ async function main(activeDir, lane) {
|
|
|
1221
1610
|
wallClockHit = true;
|
|
1222
1611
|
run2.kill();
|
|
1223
1612
|
}, resolved.limits.wallClockSecs * 1e3) : void 0;
|
|
1224
|
-
|
|
1225
|
-
for await (const ev of run2.events) {
|
|
1226
|
-
appendEvent(id, lane, ev);
|
|
1227
|
-
if (ev.type === "session" && ev.data?.sessionId) {
|
|
1228
|
-
mergeEvidence(id, lane, { sessionId: String(ev.data.sessionId) });
|
|
1229
|
-
}
|
|
1230
|
-
if (ev.type === "turn-end") {
|
|
1231
|
-
if (cancelRequested(id, lane)) {
|
|
1232
|
-
cancelled = true;
|
|
1233
|
-
run2.kill();
|
|
1234
|
-
continue;
|
|
1235
|
-
}
|
|
1236
|
-
const msgs = unhandled(id, lane);
|
|
1237
|
-
if (msgs.length > 0) {
|
|
1238
|
-
for (const m of msgs) {
|
|
1239
|
-
run2.send(m.text);
|
|
1240
|
-
acknowledge(id, lane, m.file);
|
|
1241
|
-
}
|
|
1242
|
-
} else {
|
|
1243
|
-
run2.end();
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1613
|
+
const { cancelled } = await drive(run2, { id, lane, stopped: () => wallClockHit });
|
|
1247
1614
|
const result = await run2.done;
|
|
1248
1615
|
if (wallTimer) clearTimeout(wallTimer);
|
|
1249
1616
|
try {
|
|
@@ -1256,33 +1623,26 @@ async function main(activeDir, lane) {
|
|
|
1256
1623
|
note: `evidence collection failed: ${err instanceof Error ? err.message : String(err)}`.slice(0, 300)
|
|
1257
1624
|
});
|
|
1258
1625
|
}
|
|
1259
|
-
|
|
1260
|
-
if (cancelled) {
|
|
1261
|
-
status("failed", "cancelled by operator");
|
|
1262
|
-
} else if (wallClockHit) {
|
|
1263
|
-
status("failed", "wall-clock limit exceeded");
|
|
1264
|
-
} else if (result.error) {
|
|
1265
|
-
status("failed", result.error.slice(0, 500));
|
|
1266
|
-
} else if (!lastVerb || !TERMINAL_VERBS.includes(lastVerb)) {
|
|
1267
|
-
status("done");
|
|
1268
|
-
}
|
|
1626
|
+
settle(id, lane, { cancelled, wallClockHit, error: result.error });
|
|
1269
1627
|
complete(id, lane);
|
|
1270
1628
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const id = path7.basename(activeDirArg);
|
|
1629
|
+
function runRunner(activeDirArg2, laneArg2) {
|
|
1630
|
+
const lane = laneArg2 === "chore" ? "chore" : "work";
|
|
1631
|
+
main(activeDirArg2, lane).catch((err) => {
|
|
1632
|
+
const id = path8.basename(activeDirArg2);
|
|
1276
1633
|
try {
|
|
1277
1634
|
appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
|
|
1278
|
-
|
|
1635
|
+
fs10.renameSync(activeDirArg2, path8.join(laneDirs(lane).done, id));
|
|
1279
1636
|
} catch {
|
|
1280
1637
|
}
|
|
1281
1638
|
process.exitCode = 1;
|
|
1282
1639
|
});
|
|
1283
1640
|
}
|
|
1641
|
+
var [activeDirArg, laneArg] = process.argv.slice(2);
|
|
1642
|
+
if (activeDirArg && activeDirArg !== "__runner") runRunner(activeDirArg, laneArg);
|
|
1284
1643
|
export {
|
|
1285
|
-
main
|
|
1644
|
+
main,
|
|
1645
|
+
runRunner
|
|
1286
1646
|
};
|
|
1287
1647
|
/*! Bundled license information:
|
|
1288
1648
|
|