omnexx 0.5.0 → 0.7.0
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 +53 -4
- package/dist/chat-server-X2QSACYD.js +245 -0
- package/dist/chat-server-X2QSACYD.js.map +1 -0
- package/dist/chat-settings-DZCYNQF5.js +144 -0
- package/dist/chat-settings-DZCYNQF5.js.map +1 -0
- package/dist/{chunk-OXDXHUTX.js → chunk-2TKRLH7N.js} +279 -345
- package/dist/chunk-2TKRLH7N.js.map +1 -0
- package/dist/chunk-7PC4SSYS.js +78 -0
- package/dist/chunk-7PC4SSYS.js.map +1 -0
- package/dist/chunk-7PNGHZYH.js +87 -0
- package/dist/chunk-7PNGHZYH.js.map +1 -0
- package/dist/{chunk-IJPR52MO.js → chunk-FC5LAF6U.js} +9 -5
- package/dist/{chunk-IJPR52MO.js.map → chunk-FC5LAF6U.js.map} +1 -1
- package/dist/chunk-JHJW7UQV.js +148 -0
- package/dist/chunk-JHJW7UQV.js.map +1 -0
- package/dist/{chunk-CLAS22GZ.js → chunk-M23TQTS6.js} +10 -76
- package/dist/chunk-M23TQTS6.js.map +1 -0
- package/dist/{chunk-TZ3KO3PA.js → chunk-NPXKRK6O.js} +611 -465
- package/dist/chunk-NPXKRK6O.js.map +1 -0
- package/dist/chunk-UX4MF6V4.js +70 -0
- package/dist/chunk-UX4MF6V4.js.map +1 -0
- package/dist/{start-Y5B2FUMR.js → chunk-ZA7YDO2B.js} +321 -1038
- package/dist/chunk-ZA7YDO2B.js.map +1 -0
- package/dist/{chunk-YMIRAOIB.js → chunk-ZCBVW3DZ.js} +41 -6
- package/dist/chunk-ZCBVW3DZ.js.map +1 -0
- package/dist/{chunk-Y74QSMFT.js → chunk-ZGB5IZJR.js} +28 -164
- package/dist/chunk-ZGB5IZJR.js.map +1 -0
- package/dist/cli.js +36 -25
- package/dist/cli.js.map +1 -1
- package/dist/discovery-2LUVI2SX.js +11 -0
- package/dist/discovery-2LUVI2SX.js.map +1 -0
- package/dist/model-picker-RVX6A2E3.js +21 -0
- package/dist/model-picker-RVX6A2E3.js.map +1 -0
- package/dist/{ollama-VFXDRFQ4.js → ollama-ELPMF6WY.js} +7 -4
- package/dist/{ollama-VFXDRFQ4.js.map → ollama-ELPMF6WY.js.map} +1 -1
- package/dist/{print-KUDY6NOG.js → print-KGSMCQ2Y.js} +6 -4
- package/dist/{print-KUDY6NOG.js.map → print-KGSMCQ2Y.js.map} +1 -1
- package/dist/session-OKHTHELI.js +27 -0
- package/dist/session-OKHTHELI.js.map +1 -0
- package/dist/start-LAVPSRH2.js +710 -0
- package/dist/start-LAVPSRH2.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-CLAS22GZ.js.map +0 -1
- package/dist/chunk-OXDXHUTX.js.map +0 -1
- package/dist/chunk-TZ3KO3PA.js.map +0 -1
- package/dist/chunk-Y74QSMFT.js.map +0 -1
- package/dist/chunk-YMIRAOIB.js.map +0 -1
- package/dist/start-Y5B2FUMR.js.map +0 -1
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
println
|
|
4
|
-
} from "./chunk-CLAS22GZ.js";
|
|
5
2
|
import {
|
|
6
3
|
EventLog,
|
|
7
4
|
PathJail,
|
|
@@ -14,6 +11,7 @@ import {
|
|
|
14
11
|
applyRemember,
|
|
15
12
|
buildCodemap,
|
|
16
13
|
buildCycleContext,
|
|
14
|
+
cacheHitRate,
|
|
17
15
|
childrenOf,
|
|
18
16
|
closeSession,
|
|
19
17
|
coerceSummary,
|
|
@@ -21,10 +19,12 @@ import {
|
|
|
21
19
|
costUsd,
|
|
22
20
|
cycleSummarySchema,
|
|
23
21
|
dailyPauseThreshold,
|
|
22
|
+
emptyTelemetry,
|
|
24
23
|
ensureFacts,
|
|
25
24
|
estimateTokens,
|
|
26
25
|
factSummary,
|
|
27
26
|
fail,
|
|
27
|
+
fold,
|
|
28
28
|
getNode,
|
|
29
29
|
git,
|
|
30
30
|
inWrapup,
|
|
@@ -73,183 +73,25 @@ import {
|
|
|
73
73
|
writeFileAtomic,
|
|
74
74
|
writeIntentTool,
|
|
75
75
|
writeJsonAtomic
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-NPXKRK6O.js";
|
|
77
|
+
import {
|
|
78
|
+
println
|
|
79
|
+
} from "./chunk-7PC4SSYS.js";
|
|
77
80
|
import {
|
|
78
81
|
ACTION_HINT,
|
|
79
82
|
ACTION_ROLE,
|
|
80
83
|
EXIT,
|
|
84
|
+
formatDuration,
|
|
85
|
+
parseDuration,
|
|
86
|
+
resolvePaths
|
|
87
|
+
} from "./chunk-ZGB5IZJR.js";
|
|
88
|
+
import {
|
|
81
89
|
LockError,
|
|
82
90
|
OmnexxError,
|
|
83
91
|
ProviderError,
|
|
84
92
|
StateError,
|
|
85
|
-
UsageError
|
|
86
|
-
|
|
87
|
-
parseDuration,
|
|
88
|
-
resolvePaths
|
|
89
|
-
} from "./chunk-Y74QSMFT.js";
|
|
90
|
-
|
|
91
|
-
// src/security/sandbox-docker.ts
|
|
92
|
-
import { randomBytes } from "crypto";
|
|
93
|
-
import { execa } from "execa";
|
|
94
|
-
import { quote } from "shell-quote";
|
|
95
|
-
function clientEnv(env) {
|
|
96
|
-
const out = {};
|
|
97
|
-
for (const k of [
|
|
98
|
-
"PATH",
|
|
99
|
-
"HOME",
|
|
100
|
-
"DOCKER_HOST",
|
|
101
|
-
"DOCKER_CONFIG",
|
|
102
|
-
"DOCKER_CONTEXT",
|
|
103
|
-
"DOCKER_CERT_PATH",
|
|
104
|
-
"DOCKER_TLS_VERIFY"
|
|
105
|
-
]) {
|
|
106
|
-
const v = env[k];
|
|
107
|
-
if (v !== void 0) out[k] = v;
|
|
108
|
-
}
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
function runArgs(s) {
|
|
112
|
-
const d = s.docker;
|
|
113
|
-
const mounts = [
|
|
114
|
-
"-v",
|
|
115
|
-
`${s.worktree}:${s.worktree}:rw`,
|
|
116
|
-
"-v",
|
|
117
|
-
`${s.tmpDir}:${s.tmpDir}:rw`,
|
|
118
|
-
...s.gitDir ? ["-v", `${s.gitDir}:${s.gitDir}:ro`] : []
|
|
119
|
-
];
|
|
120
|
-
return [
|
|
121
|
-
"run",
|
|
122
|
-
"-d",
|
|
123
|
-
"--rm",
|
|
124
|
-
"--init",
|
|
125
|
-
"--name",
|
|
126
|
-
s.name,
|
|
127
|
-
"--label",
|
|
128
|
-
"org.omnexx.sandbox=1",
|
|
129
|
-
"--user",
|
|
130
|
-
`${s.uid}:${s.gid}`,
|
|
131
|
-
"--network",
|
|
132
|
-
d.network,
|
|
133
|
-
"--cpus",
|
|
134
|
-
d.cpus,
|
|
135
|
-
"--memory",
|
|
136
|
-
d.memory,
|
|
137
|
-
"--pids-limit",
|
|
138
|
-
String(d.pids_limit),
|
|
139
|
-
"--cap-drop",
|
|
140
|
-
"ALL",
|
|
141
|
-
"--security-opt",
|
|
142
|
-
"no-new-privileges",
|
|
143
|
-
"--read-only",
|
|
144
|
-
"--tmpfs",
|
|
145
|
-
`/tmp:rw,exec,nosuid,size=${d.tmp_size}`,
|
|
146
|
-
// HOME and pid files live on their own owner-only tmpfs, not in the shared /tmp.
|
|
147
|
-
"--tmpfs",
|
|
148
|
-
`/omnexx:rw,exec,nosuid,size=256m,mode=0700,uid=${s.uid},gid=${s.gid}`,
|
|
149
|
-
"-e",
|
|
150
|
-
"HOME=/omnexx/home",
|
|
151
|
-
"-w",
|
|
152
|
-
s.worktree,
|
|
153
|
-
...mounts,
|
|
154
|
-
d.image,
|
|
155
|
-
"sleep",
|
|
156
|
-
"infinity"
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
var DockerSandbox = class {
|
|
160
|
-
constructor(spec, env) {
|
|
161
|
-
this.spec = spec;
|
|
162
|
-
this.env = env;
|
|
163
|
-
}
|
|
164
|
-
spec;
|
|
165
|
-
env;
|
|
166
|
-
started = false;
|
|
167
|
-
docker(args, timeoutMs = 12e4) {
|
|
168
|
-
return execa("docker", args, {
|
|
169
|
-
env: clientEnv(this.env),
|
|
170
|
-
extendEnv: false,
|
|
171
|
-
reject: false,
|
|
172
|
-
stdin: "ignore",
|
|
173
|
-
timeout: timeoutMs
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
async start() {
|
|
177
|
-
await this.docker(["rm", "-f", this.spec.name]);
|
|
178
|
-
const pull = await this.docker(["image", "inspect", this.spec.docker.image]);
|
|
179
|
-
if (pull.exitCode !== 0) {
|
|
180
|
-
const p = await this.docker(["pull", this.spec.docker.image], 15 * 6e4);
|
|
181
|
-
if (p.exitCode !== 0)
|
|
182
|
-
throw new OmnexxError(
|
|
183
|
-
"sandbox",
|
|
184
|
-
`cannot pull ${this.spec.docker.image}: ${p.stderr.split("\n")[0] ?? ""}`,
|
|
185
|
-
"check `docker info` and the [docker] image"
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
const r = await this.docker(runArgs(this.spec));
|
|
189
|
-
if (r.exitCode !== 0) {
|
|
190
|
-
throw new OmnexxError(
|
|
191
|
-
"sandbox",
|
|
192
|
-
`cannot start the sandbox container: ${r.stderr.split("\n").at(-1) ?? ""}`,
|
|
193
|
-
"run `omnexx doctor`; is the docker daemon running?"
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
await this.docker([
|
|
197
|
-
"exec",
|
|
198
|
-
this.spec.name,
|
|
199
|
-
"mkdir",
|
|
200
|
-
"-p",
|
|
201
|
-
"-m",
|
|
202
|
-
"700",
|
|
203
|
-
"/omnexx/home",
|
|
204
|
-
"/omnexx/exec"
|
|
205
|
-
]);
|
|
206
|
-
this.started = true;
|
|
207
|
-
}
|
|
208
|
-
async stop() {
|
|
209
|
-
if (!this.started) return;
|
|
210
|
-
await this.docker(["rm", "-f", this.spec.name]);
|
|
211
|
-
this.started = false;
|
|
212
|
-
}
|
|
213
|
-
/** Run one command inside the container; on timeout/abort the in-container process group dies too. */
|
|
214
|
-
exec = (command, opts) => {
|
|
215
|
-
const id = randomBytes(6).toString("hex");
|
|
216
|
-
const pidFile = `/omnexx/exec/${id}.pid`;
|
|
217
|
-
const inner = `setsid sh -c ${quote([command])} & p=$!; echo $p > ${pidFile}; wait $p; s=$?; rm -f ${pidFile}; exit $s`;
|
|
218
|
-
const envArgs = Object.entries(opts.env).filter(([k]) => k !== "PATH" && k !== "HOME").flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
219
|
-
const argv = [
|
|
220
|
-
"docker",
|
|
221
|
-
"exec",
|
|
222
|
-
...opts.stdin !== void 0 ? ["-i"] : [],
|
|
223
|
-
"-w",
|
|
224
|
-
opts.cwd,
|
|
225
|
-
...envArgs,
|
|
226
|
-
this.spec.name,
|
|
227
|
-
"sh",
|
|
228
|
-
"-c",
|
|
229
|
-
inner
|
|
230
|
-
];
|
|
231
|
-
return runShell(quote(argv), {
|
|
232
|
-
...opts,
|
|
233
|
-
env: clientEnv(this.env),
|
|
234
|
-
onKill: async () => {
|
|
235
|
-
await this.docker(
|
|
236
|
-
["exec", this.spec.name, "sh", "-c", `[ -f ${pidFile} ] && kill -9 -$(cat ${pidFile})`],
|
|
237
|
-
1e4
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
|
-
async function dockerAvailable(env) {
|
|
244
|
-
const r = await execa("docker", ["info", "--format", "{{.ServerVersion}}"], {
|
|
245
|
-
env: clientEnv(env),
|
|
246
|
-
extendEnv: false,
|
|
247
|
-
reject: false,
|
|
248
|
-
stdin: "ignore",
|
|
249
|
-
timeout: 1e4
|
|
250
|
-
});
|
|
251
|
-
return r.exitCode === 0 && r.stdout.trim() !== "";
|
|
252
|
-
}
|
|
93
|
+
UsageError
|
|
94
|
+
} from "./chunk-7PNGHZYH.js";
|
|
253
95
|
|
|
254
96
|
// src/cli/commands/control.ts
|
|
255
97
|
import pc from "picocolors";
|
|
@@ -448,7 +290,7 @@ function instruction(mode, plan) {
|
|
|
448
290
|
return 'There is no plan yet. Explore the repository. Then call write_intent with what the user actually wants (the goal may be one short sentence: infer the intended build from it and from the repo, and record your assumptions instead of asking). Then call write_plan with milestones, expanding only the first one or two into tasks. M1 is the shortest path to a working, checkable v1; widen and polish in later milestones. Every item in the "done when" list of the intent must map to a task that delivers it, and the checks of that task must run a test proving it (not just look for a file or string); commits are only accepted when every test passes, so a task that adds a test also makes it pass. Before the run may finish, an independent audit compares the result with the "done when" list.';
|
|
449
291
|
const view = plan ? compactPlanView(plan, void 0) : "";
|
|
450
292
|
const full = plan ? JSON.stringify({
|
|
451
|
-
milestones: plan.nodes.filter((
|
|
293
|
+
milestones: plan.nodes.filter((n) => n.type === "milestone").map((m) => ({
|
|
452
294
|
id: m.id,
|
|
453
295
|
title: m.title,
|
|
454
296
|
status: m.status,
|
|
@@ -465,7 +307,7 @@ ${full}
|
|
|
465
307
|
Expand milestone ${mode.milestoneId} into leaf tasks (ids ${mode.milestoneId}.T01, ...). Call write_plan with the milestone you changed (milestones and tasks you leave out are kept unchanged).`;
|
|
466
308
|
}
|
|
467
309
|
if (mode.kind === "split") {
|
|
468
|
-
const t = plan?.nodes.find((
|
|
310
|
+
const t = plan?.nodes.find((n) => n.id === mode.taskId);
|
|
469
311
|
const evidence = t ? [...t.approachesTried.map((a) => `- tried: ${a}`), ...t.evidence.slice(-2)].join("\n") : "";
|
|
470
312
|
return `Current plan:
|
|
471
313
|
${view}
|
|
@@ -642,7 +484,7 @@ ${dry.alreadyPass.join("\n")}`
|
|
|
642
484
|
const codemapText = await readTextOr(run.store.file("codemap.json"), "");
|
|
643
485
|
const codemap = codemapText ? renderCodemap(JSON.parse(codemapText), run.config.context.repo_map_max_tokens) : "# Codebase map\n(not built)";
|
|
644
486
|
const notes = renderNotes(await run.store.readNotes());
|
|
645
|
-
let
|
|
487
|
+
let n = 0;
|
|
646
488
|
const toolCtx = {
|
|
647
489
|
jail: new PathJail(run.worktree),
|
|
648
490
|
exec: run.exec,
|
|
@@ -656,7 +498,7 @@ ${dry.alreadyPass.join("\n")}`
|
|
|
656
498
|
notesMaxTokens: run.config.context.notes_max_tokens,
|
|
657
499
|
today: new Date(run.clock.now()).toISOString().slice(0, 10),
|
|
658
500
|
signal: run.abort.signal,
|
|
659
|
-
nextCommandId: () => `cmd-plan${run.state.cycle}-${++
|
|
501
|
+
nextCommandId: () => `cmd-plan${run.state.cycle}-${++n}`,
|
|
660
502
|
edited: /* @__PURE__ */ new Set()
|
|
661
503
|
};
|
|
662
504
|
toolCtx.subagent = subagentRunner(run, toolCtx);
|
|
@@ -714,7 +556,7 @@ ${notes}`, cacheBreakpoint: true }
|
|
|
714
556
|
if (written) return [];
|
|
715
557
|
const cap = run.config.budget.max_tokens_per_cycle;
|
|
716
558
|
const nudge = PLAN_NUDGES.find(
|
|
717
|
-
(
|
|
559
|
+
(n2, i) => i >= nudged && (planTurns >= n2.at || planTokens >= cap * n2.tokenShare)
|
|
718
560
|
);
|
|
719
561
|
if (!nudge) return [];
|
|
720
562
|
nudged = PLAN_NUDGES.indexOf(nudge) + 1;
|
|
@@ -857,16 +699,16 @@ ${f.body}`);
|
|
|
857
699
|
function allOf(notifiers) {
|
|
858
700
|
return {
|
|
859
701
|
async notify(p) {
|
|
860
|
-
await Promise.all(notifiers.map((
|
|
702
|
+
await Promise.all(notifiers.map((n) => n.notify(p)));
|
|
861
703
|
}
|
|
862
704
|
};
|
|
863
705
|
}
|
|
864
706
|
|
|
865
707
|
// src/git/pr.ts
|
|
866
|
-
import { execa
|
|
708
|
+
import { execa } from "execa";
|
|
867
709
|
var MAX_BODY = 6e4;
|
|
868
710
|
async function gh(cwd, args, input) {
|
|
869
|
-
const r = await
|
|
711
|
+
const r = await execa("gh", args, {
|
|
870
712
|
cwd,
|
|
871
713
|
reject: false,
|
|
872
714
|
...input !== void 0 ? { input } : { stdin: "ignore" },
|
|
@@ -960,7 +802,7 @@ var PROBLEM_KEYS = [
|
|
|
960
802
|
"message"
|
|
961
803
|
];
|
|
962
804
|
var FINE = /^(ok|pass(ed)?|confirmed|met|done|resolved|none|fine)$/i;
|
|
963
|
-
var str = (v,
|
|
805
|
+
var str = (v, n) => typeof v === "string" ? v.trim().slice(0, n) : "";
|
|
964
806
|
function coerceReview(input) {
|
|
965
807
|
if (typeof input !== "object" || input === null) return void 0;
|
|
966
808
|
const o = input;
|
|
@@ -1105,6 +947,169 @@ ${codebase}`;
|
|
|
1105
947
|
// src/core/supervisor.ts
|
|
1106
948
|
import { join as join7 } from "path";
|
|
1107
949
|
|
|
950
|
+
// src/security/sandbox-docker.ts
|
|
951
|
+
import { randomBytes } from "crypto";
|
|
952
|
+
import { execa as execa2 } from "execa";
|
|
953
|
+
import { quote } from "shell-quote";
|
|
954
|
+
function clientEnv(env) {
|
|
955
|
+
const out = {};
|
|
956
|
+
for (const k of [
|
|
957
|
+
"PATH",
|
|
958
|
+
"HOME",
|
|
959
|
+
"DOCKER_HOST",
|
|
960
|
+
"DOCKER_CONFIG",
|
|
961
|
+
"DOCKER_CONTEXT",
|
|
962
|
+
"DOCKER_CERT_PATH",
|
|
963
|
+
"DOCKER_TLS_VERIFY"
|
|
964
|
+
]) {
|
|
965
|
+
const v = env[k];
|
|
966
|
+
if (v !== void 0) out[k] = v;
|
|
967
|
+
}
|
|
968
|
+
return out;
|
|
969
|
+
}
|
|
970
|
+
function runArgs(s) {
|
|
971
|
+
const d = s.docker;
|
|
972
|
+
const mounts = [
|
|
973
|
+
"-v",
|
|
974
|
+
`${s.worktree}:${s.worktree}:rw`,
|
|
975
|
+
"-v",
|
|
976
|
+
`${s.tmpDir}:${s.tmpDir}:rw`,
|
|
977
|
+
...s.gitDir ? ["-v", `${s.gitDir}:${s.gitDir}:ro`] : []
|
|
978
|
+
];
|
|
979
|
+
return [
|
|
980
|
+
"run",
|
|
981
|
+
"-d",
|
|
982
|
+
"--rm",
|
|
983
|
+
"--init",
|
|
984
|
+
"--name",
|
|
985
|
+
s.name,
|
|
986
|
+
"--label",
|
|
987
|
+
"org.omnexx.sandbox=1",
|
|
988
|
+
"--user",
|
|
989
|
+
`${s.uid}:${s.gid}`,
|
|
990
|
+
"--network",
|
|
991
|
+
d.network,
|
|
992
|
+
"--cpus",
|
|
993
|
+
d.cpus,
|
|
994
|
+
"--memory",
|
|
995
|
+
d.memory,
|
|
996
|
+
"--pids-limit",
|
|
997
|
+
String(d.pids_limit),
|
|
998
|
+
"--cap-drop",
|
|
999
|
+
"ALL",
|
|
1000
|
+
"--security-opt",
|
|
1001
|
+
"no-new-privileges",
|
|
1002
|
+
"--read-only",
|
|
1003
|
+
"--tmpfs",
|
|
1004
|
+
`/tmp:rw,exec,nosuid,size=${d.tmp_size}`,
|
|
1005
|
+
// HOME and pid files live on their own owner-only tmpfs, not in the shared /tmp.
|
|
1006
|
+
"--tmpfs",
|
|
1007
|
+
`/omnexx:rw,exec,nosuid,size=256m,mode=0700,uid=${s.uid},gid=${s.gid}`,
|
|
1008
|
+
"-e",
|
|
1009
|
+
"HOME=/omnexx/home",
|
|
1010
|
+
"-w",
|
|
1011
|
+
s.worktree,
|
|
1012
|
+
...mounts,
|
|
1013
|
+
d.image,
|
|
1014
|
+
"sleep",
|
|
1015
|
+
"infinity"
|
|
1016
|
+
];
|
|
1017
|
+
}
|
|
1018
|
+
var DockerSandbox = class {
|
|
1019
|
+
constructor(spec, env) {
|
|
1020
|
+
this.spec = spec;
|
|
1021
|
+
this.env = env;
|
|
1022
|
+
}
|
|
1023
|
+
spec;
|
|
1024
|
+
env;
|
|
1025
|
+
started = false;
|
|
1026
|
+
docker(args, timeoutMs = 12e4) {
|
|
1027
|
+
return execa2("docker", args, {
|
|
1028
|
+
env: clientEnv(this.env),
|
|
1029
|
+
extendEnv: false,
|
|
1030
|
+
reject: false,
|
|
1031
|
+
stdin: "ignore",
|
|
1032
|
+
timeout: timeoutMs
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async start() {
|
|
1036
|
+
await this.docker(["rm", "-f", this.spec.name]);
|
|
1037
|
+
const pull = await this.docker(["image", "inspect", this.spec.docker.image]);
|
|
1038
|
+
if (pull.exitCode !== 0) {
|
|
1039
|
+
const p = await this.docker(["pull", this.spec.docker.image], 15 * 6e4);
|
|
1040
|
+
if (p.exitCode !== 0)
|
|
1041
|
+
throw new OmnexxError(
|
|
1042
|
+
"sandbox",
|
|
1043
|
+
`cannot pull ${this.spec.docker.image}: ${p.stderr.split("\n")[0] ?? ""}`,
|
|
1044
|
+
"check `docker info` and the [docker] image"
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
const r = await this.docker(runArgs(this.spec));
|
|
1048
|
+
if (r.exitCode !== 0) {
|
|
1049
|
+
throw new OmnexxError(
|
|
1050
|
+
"sandbox",
|
|
1051
|
+
`cannot start the sandbox container: ${r.stderr.split("\n").at(-1) ?? ""}`,
|
|
1052
|
+
"run `omnexx doctor`; is the docker daemon running?"
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
await this.docker([
|
|
1056
|
+
"exec",
|
|
1057
|
+
this.spec.name,
|
|
1058
|
+
"mkdir",
|
|
1059
|
+
"-p",
|
|
1060
|
+
"-m",
|
|
1061
|
+
"700",
|
|
1062
|
+
"/omnexx/home",
|
|
1063
|
+
"/omnexx/exec"
|
|
1064
|
+
]);
|
|
1065
|
+
this.started = true;
|
|
1066
|
+
}
|
|
1067
|
+
async stop() {
|
|
1068
|
+
if (!this.started) return;
|
|
1069
|
+
await this.docker(["rm", "-f", this.spec.name]);
|
|
1070
|
+
this.started = false;
|
|
1071
|
+
}
|
|
1072
|
+
/** Run one command inside the container; on timeout/abort the in-container process group dies too. */
|
|
1073
|
+
exec = (command, opts) => {
|
|
1074
|
+
const id = randomBytes(6).toString("hex");
|
|
1075
|
+
const pidFile = `/omnexx/exec/${id}.pid`;
|
|
1076
|
+
const inner = `setsid sh -c ${quote([command])} & p=$!; echo $p > ${pidFile}; wait $p; s=$?; rm -f ${pidFile}; exit $s`;
|
|
1077
|
+
const envArgs = Object.entries(opts.env).filter(([k]) => k !== "PATH" && k !== "HOME").flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
1078
|
+
const argv = [
|
|
1079
|
+
"docker",
|
|
1080
|
+
"exec",
|
|
1081
|
+
...opts.stdin !== void 0 ? ["-i"] : [],
|
|
1082
|
+
"-w",
|
|
1083
|
+
opts.cwd,
|
|
1084
|
+
...envArgs,
|
|
1085
|
+
this.spec.name,
|
|
1086
|
+
"sh",
|
|
1087
|
+
"-c",
|
|
1088
|
+
inner
|
|
1089
|
+
];
|
|
1090
|
+
return runShell(quote(argv), {
|
|
1091
|
+
...opts,
|
|
1092
|
+
env: clientEnv(this.env),
|
|
1093
|
+
onKill: async () => {
|
|
1094
|
+
await this.docker(
|
|
1095
|
+
["exec", this.spec.name, "sh", "-c", `[ -f ${pidFile} ] && kill -9 -$(cat ${pidFile})`],
|
|
1096
|
+
1e4
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
1102
|
+
async function dockerAvailable(env) {
|
|
1103
|
+
const r = await execa2("docker", ["info", "--format", "{{.ServerVersion}}"], {
|
|
1104
|
+
env: clientEnv(env),
|
|
1105
|
+
extendEnv: false,
|
|
1106
|
+
reject: false,
|
|
1107
|
+
stdin: "ignore",
|
|
1108
|
+
timeout: 1e4
|
|
1109
|
+
});
|
|
1110
|
+
return r.exitCode === 0 && r.stdout.trim() !== "";
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1108
1113
|
// src/daemon/boot-id.ts
|
|
1109
1114
|
import { readFile as readFile2 } from "fs/promises";
|
|
1110
1115
|
import { execa as execa3 } from "execa";
|
|
@@ -1250,12 +1255,12 @@ var InCycleWatch = class {
|
|
|
1250
1255
|
}
|
|
1251
1256
|
for (const c of calls) {
|
|
1252
1257
|
const key = `${c.name} ${JSON.stringify(c.input)}`;
|
|
1253
|
-
const
|
|
1254
|
-
this.calls.set(key,
|
|
1255
|
-
if (
|
|
1258
|
+
const n = (this.calls.get(key) ?? 0) + 1;
|
|
1259
|
+
this.calls.set(key, n);
|
|
1260
|
+
if (n >= this.limits.repeatedToolCall)
|
|
1256
1261
|
return {
|
|
1257
1262
|
signal: "repeated_tool_call",
|
|
1258
|
-
detail: `called ${c.name} with the same arguments ${
|
|
1263
|
+
detail: `called ${c.name} with the same arguments ${n} times: ${key.slice(0, 200)}`
|
|
1259
1264
|
};
|
|
1260
1265
|
}
|
|
1261
1266
|
if (turn - this.lastEditTurn >= this.limits.noEditTurns)
|
|
@@ -1338,8 +1343,8 @@ function validateQuestions(questions) {
|
|
|
1338
1343
|
if (questions.length < 1 || questions.length > MAX_QUESTIONS)
|
|
1339
1344
|
return `need 1-${MAX_QUESTIONS} questions`;
|
|
1340
1345
|
for (const q of questions) {
|
|
1341
|
-
const
|
|
1342
|
-
if (
|
|
1346
|
+
const n = q.type === "choice" ? q.options.length : q.type === "score" ? q.levels.length : 2;
|
|
1347
|
+
if (n < 2 || n > MAX_OPTIONS) return `${q.id}: needs 2-${MAX_OPTIONS} options`;
|
|
1343
1348
|
}
|
|
1344
1349
|
return void 0;
|
|
1345
1350
|
}
|
|
@@ -1365,7 +1370,7 @@ function answersMatch(questions, answers) {
|
|
|
1365
1370
|
|
|
1366
1371
|
// src/judge/state-summary.ts
|
|
1367
1372
|
var bytes = (v) => Buffer.byteLength(JSON.stringify(v));
|
|
1368
|
-
var clipText = (s,
|
|
1373
|
+
var clipText = (s, n) => s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
1369
1374
|
function fitSummary(input, maxBytes = MAX_STATE_BYTES) {
|
|
1370
1375
|
const s = structuredClone(input);
|
|
1371
1376
|
s.goal = clipText(s.goal, 2e3);
|
|
@@ -1799,7 +1804,7 @@ async function settleMilestones(run) {
|
|
|
1799
1804
|
m.doneAtCycle = run.state.cycle;
|
|
1800
1805
|
const tag = checkpointTag(run.state.runId, m.id);
|
|
1801
1806
|
await tagCheckpoint(run.worktree, tag, run.state.lastGreen);
|
|
1802
|
-
const tests = run.state.baseline ? Object.values(run.state.baseline).reduce((
|
|
1807
|
+
const tests = run.state.baseline ? Object.values(run.state.baseline).reduce((n, g) => n + (g.tests?.passed ?? 0), 0) : void 0;
|
|
1803
1808
|
run.state.checkpoints.push({
|
|
1804
1809
|
milestoneId: m.id,
|
|
1805
1810
|
sha: run.state.lastGreen,
|
|
@@ -1858,11 +1863,11 @@ async function consolidateNotes(run) {
|
|
|
1858
1863
|
);
|
|
1859
1864
|
if (!parsed.success) return false;
|
|
1860
1865
|
const today = new Date(run.clock.now()).toISOString().slice(0, 10);
|
|
1861
|
-
const next = parsed.data.notes.map((
|
|
1862
|
-
...
|
|
1863
|
-
date: notes.find((o) => o.id ===
|
|
1866
|
+
const next = parsed.data.notes.map((n) => ({
|
|
1867
|
+
...n,
|
|
1868
|
+
date: notes.find((o) => o.id === n.id)?.date ?? today
|
|
1864
1869
|
}));
|
|
1865
|
-
const keepsFacts = notes.filter((
|
|
1870
|
+
const keepsFacts = notes.filter((n) => n.type === "env" || n.type === "command").every((f) => next.some((n) => n.text === f.text));
|
|
1866
1871
|
const shorter = renderNotes(next).length < text.length;
|
|
1867
1872
|
run.events.emit("notes.consolidation", {
|
|
1868
1873
|
applied: keepsFacts && shorter,
|
|
@@ -2237,7 +2242,7 @@ async function recordFlaky(run, task, flaky) {
|
|
|
2237
2242
|
for (const f of flaky) {
|
|
2238
2243
|
run.events.emit("verify.flaky", { task: task.id, gate: f.gate, ids: f.ids });
|
|
2239
2244
|
for (const id of f.ids) {
|
|
2240
|
-
if (notes.some((
|
|
2245
|
+
if (notes.some((n) => n.type === "flaky" && n.text.includes(id))) continue;
|
|
2241
2246
|
const r = applyRemember(
|
|
2242
2247
|
notes,
|
|
2243
2248
|
{
|
|
@@ -2266,7 +2271,7 @@ async function recordAntiCheatLessons(run, violations) {
|
|
|
2266
2271
|
let changed = false;
|
|
2267
2272
|
for (const rule of new Set(violations.map((v) => v.rule))) {
|
|
2268
2273
|
const text = ANTI_CHEAT_LESSON[rule];
|
|
2269
|
-
if (notes.some((
|
|
2274
|
+
if (notes.some((n) => n.text === text)) continue;
|
|
2270
2275
|
const r = applyRemember(
|
|
2271
2276
|
notes,
|
|
2272
2277
|
{ action: "add", type: "pitfall", text },
|
|
@@ -2326,7 +2331,7 @@ function contextBreakdown(ctx) {
|
|
|
2326
2331
|
return out;
|
|
2327
2332
|
}
|
|
2328
2333
|
function toolContext(run, edited) {
|
|
2329
|
-
let
|
|
2334
|
+
let n = 0;
|
|
2330
2335
|
const { judge } = run;
|
|
2331
2336
|
const ctx = {
|
|
2332
2337
|
jail: new PathJail(run.worktree),
|
|
@@ -2341,7 +2346,7 @@ function toolContext(run, edited) {
|
|
|
2341
2346
|
notesMaxTokens: run.config.context.notes_max_tokens,
|
|
2342
2347
|
today: new Date(run.clock.now()).toISOString().slice(0, 10),
|
|
2343
2348
|
signal: run.abort.signal,
|
|
2344
|
-
nextCommandId: () => `cmd-${run.state.cycle}-${++
|
|
2349
|
+
nextCommandId: () => `cmd-${run.state.cycle}-${++n}`,
|
|
2345
2350
|
edited,
|
|
2346
2351
|
reads: /* @__PURE__ */ new Map(),
|
|
2347
2352
|
// Advisory, log-only, never awaited by the tool, skipped while the breaker is open.
|
|
@@ -2394,7 +2399,7 @@ async function stepAct(run) {
|
|
|
2394
2399
|
const task = getNode(plan, run.state.taskId ?? "");
|
|
2395
2400
|
await prepareWorktree(run);
|
|
2396
2401
|
const shared = new Set(
|
|
2397
|
-
plan.nodes.filter((
|
|
2402
|
+
plan.nodes.filter((n) => n.id !== task.id).flatMap((n) => n.checks.map((c) => c.trim()))
|
|
2398
2403
|
);
|
|
2399
2404
|
const ownCheck = task.checks.some((c) => !shared.has(c.trim()));
|
|
2400
2405
|
if (task.checks.length && task.attempts === 0 && run.state.acceptedCommits > 0 && ownCheck) {
|
|
@@ -2447,7 +2452,7 @@ async function stepAct(run) {
|
|
|
2447
2452
|
});
|
|
2448
2453
|
run.events.emit("cycle.context", {
|
|
2449
2454
|
task: task.id,
|
|
2450
|
-
prefixBytes: ctx.system.reduce((
|
|
2455
|
+
prefixBytes: ctx.system.reduce((n, b) => n + b.text.length, 0),
|
|
2451
2456
|
stateBytes: JSON.stringify(ctx.first).length,
|
|
2452
2457
|
tokens: contextBreakdown(ctx),
|
|
2453
2458
|
memory: memory.stats()
|
|
@@ -2544,8 +2549,8 @@ async function stepVerify(run) {
|
|
|
2544
2549
|
const { changes: changes2, patch, treeHash } = await workingTreeDiff(run.worktree, run.state.lastGreen);
|
|
2545
2550
|
const diffStats = {
|
|
2546
2551
|
files: changes2.length,
|
|
2547
|
-
added: changes2.reduce((
|
|
2548
|
-
removed: changes2.reduce((
|
|
2552
|
+
added: changes2.reduce((n, c) => n + c.added, 0),
|
|
2553
|
+
removed: changes2.reduce((n, c) => n + c.removed, 0)
|
|
2549
2554
|
};
|
|
2550
2555
|
const reasons = [];
|
|
2551
2556
|
const evidence = [];
|
|
@@ -2585,7 +2590,7 @@ ${c.output}`);
|
|
|
2585
2590
|
for (const v of verdicts) if (!v.pass) reasons.push(`gate ${v.gate}: ${v.reason ?? "failed"}`);
|
|
2586
2591
|
const failText = renderFailures(results, verdicts);
|
|
2587
2592
|
if (failText) evidence.push(failText);
|
|
2588
|
-
testsPassed = results.reduce((
|
|
2593
|
+
testsPassed = results.reduce((n, r) => n + (r.tests?.passed ?? 0), 0) || void 0;
|
|
2589
2594
|
const cheats = antiCheat({
|
|
2590
2595
|
changes: changes2,
|
|
2591
2596
|
patch,
|
|
@@ -2754,6 +2759,7 @@ async function stepCommit(run) {
|
|
|
2754
2759
|
}
|
|
2755
2760
|
run.state.lastProgressAt = run.clock.now();
|
|
2756
2761
|
run.state.lastProgressCycle = run.state.cycle;
|
|
2762
|
+
run.state.recoveries = 0;
|
|
2757
2763
|
await run.setPhase("record");
|
|
2758
2764
|
}
|
|
2759
2765
|
async function stepRollback(run) {
|
|
@@ -2917,7 +2923,7 @@ var flakyIds = (events) => {
|
|
|
2917
2923
|
);
|
|
2918
2924
|
return ids.size ? `${ids.size} (${[...ids].slice(0, 10).join(", ")})` : "none";
|
|
2919
2925
|
};
|
|
2920
|
-
var usd = (
|
|
2926
|
+
var usd = (n) => `$${n.toFixed(2)}`;
|
|
2921
2927
|
function suggestions(events) {
|
|
2922
2928
|
const seen = /* @__PURE__ */ new Set();
|
|
2923
2929
|
const out = [];
|
|
@@ -2976,12 +2982,12 @@ function deltas(state) {
|
|
|
2976
2982
|
const after = state.baseline ?? {};
|
|
2977
2983
|
const names = [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].sort();
|
|
2978
2984
|
if (!names.length) return ["No gates were configured."];
|
|
2979
|
-
const rows = names.map((
|
|
2980
|
-
const b = before[
|
|
2981
|
-
const a = after[
|
|
2985
|
+
const rows = names.map((n) => {
|
|
2986
|
+
const b = before[n];
|
|
2987
|
+
const a = after[n];
|
|
2982
2988
|
const fixed = b && a ? b.failureIds.filter((id) => !a.failureIds.includes(id)).length : 0;
|
|
2983
2989
|
const tests = (g) => g?.tests ? `${g.tests.passed}/${g.tests.total}` : "-";
|
|
2984
|
-
return `| ${
|
|
2990
|
+
return `| ${n} | ${b?.failureIds.length ?? "-"} | ${a?.failureIds.length ?? "-"} | ${fixed} | ${tests(b)} | ${tests(a)} |`;
|
|
2985
2991
|
});
|
|
2986
2992
|
return [
|
|
2987
2993
|
"Baseline is the starting commit; final is `lastGreen`.",
|
|
@@ -2998,7 +3004,7 @@ async function writeReport(store, state, plan, now) {
|
|
|
2998
3004
|
const input = s.tokens.uncached + s.tokens.cacheWrite + s.tokens.cacheRead;
|
|
2999
3005
|
const stuck = events.filter((e) => e.type === "stuck.signal");
|
|
3000
3006
|
const rungs = events.filter((e) => e.type === "ladder.rung");
|
|
3001
|
-
const parked = plan?.nodes.filter((
|
|
3007
|
+
const parked = plan?.nodes.filter((n) => n.status === "parked") ?? [];
|
|
3002
3008
|
const workers = events.filter((e) => e.type === "worker.result");
|
|
3003
3009
|
const assumptions = intentAssumptions(await readIntent(store));
|
|
3004
3010
|
const lines = [
|
|
@@ -3049,7 +3055,7 @@ async function writeReport(store, state, plan, now) {
|
|
|
3049
3055
|
"",
|
|
3050
3056
|
`## ${REPORT_SECTIONS[6]}`,
|
|
3051
3057
|
"",
|
|
3052
|
-
...parked.length ? parked.map((
|
|
3058
|
+
...parked.length ? parked.map((n) => `- ${n.id} ${n.title}: ${n.parkedReason ?? ""}`) : ["- Nothing parked."],
|
|
3053
3059
|
...state.status === "needs-human" ? [`- The run stopped for you: ${state.statusReason ?? ""}`] : [],
|
|
3054
3060
|
...suggestions(events),
|
|
3055
3061
|
...assumptions.length ? [
|
|
@@ -3239,9 +3245,9 @@ function probs(v) {
|
|
|
3239
3245
|
if (typeof v !== "object" || v === null) return void 0;
|
|
3240
3246
|
const out = {};
|
|
3241
3247
|
for (const [k, x] of Object.entries(v)) {
|
|
3242
|
-
const
|
|
3243
|
-
if (
|
|
3244
|
-
out[k] =
|
|
3248
|
+
const n = num(x);
|
|
3249
|
+
if (n === void 0) return void 0;
|
|
3250
|
+
out[k] = n;
|
|
3245
3251
|
}
|
|
3246
3252
|
return out;
|
|
3247
3253
|
}
|
|
@@ -3976,7 +3982,7 @@ function splitDepth(plan, id) {
|
|
|
3976
3982
|
let depth = 0;
|
|
3977
3983
|
let cur = id;
|
|
3978
3984
|
for (; ; ) {
|
|
3979
|
-
const parent = plan.nodes.find((
|
|
3985
|
+
const parent = plan.nodes.find((n) => n.splitInto.includes(cur));
|
|
3980
3986
|
if (!parent) return depth;
|
|
3981
3987
|
depth++;
|
|
3982
3988
|
cur = parent.id;
|
|
@@ -4156,7 +4162,7 @@ var Supervisor = class {
|
|
|
4156
4162
|
async splitTask(taskId, reason) {
|
|
4157
4163
|
const r = this.run;
|
|
4158
4164
|
await r.savePlan();
|
|
4159
|
-
const before = new Set(r.requirePlan().nodes.map((
|
|
4165
|
+
const before = new Set(r.requirePlan().nodes.map((n) => n.id));
|
|
4160
4166
|
const parentId = getNode(r.requirePlan(), taskId).parentId;
|
|
4161
4167
|
try {
|
|
4162
4168
|
await runPlanner(r, { kind: "split", taskId, reason });
|
|
@@ -4166,15 +4172,15 @@ var Supervisor = class {
|
|
|
4166
4172
|
return false;
|
|
4167
4173
|
}
|
|
4168
4174
|
const plan = r.requirePlan();
|
|
4169
|
-
const fresh = plan.nodes.filter((
|
|
4175
|
+
const fresh = plan.nodes.filter((n) => n.type === "task" && n.parentId === parentId && !before.has(n.id)).map((n) => n.id);
|
|
4170
4176
|
if (!fresh.length) return false;
|
|
4171
4177
|
const task = getNode(plan, taskId);
|
|
4172
4178
|
task.splitInto = fresh;
|
|
4173
4179
|
task.status = "parked";
|
|
4174
4180
|
task.parkedReason = `split into ${fresh.join(", ")}`;
|
|
4175
|
-
for (const
|
|
4176
|
-
if (
|
|
4177
|
-
|
|
4181
|
+
for (const n of plan.nodes) {
|
|
4182
|
+
if (n.dependsOn.includes(taskId) && !fresh.includes(n.id)) {
|
|
4183
|
+
n.dependsOn = [...n.dependsOn.filter((d) => d !== taskId), ...fresh];
|
|
4178
4184
|
}
|
|
4179
4185
|
}
|
|
4180
4186
|
await r.savePlan();
|
|
@@ -4186,12 +4192,12 @@ var Supervisor = class {
|
|
|
4186
4192
|
const r = this.run;
|
|
4187
4193
|
const plan = r.requirePlan();
|
|
4188
4194
|
let changed = false;
|
|
4189
|
-
for (const
|
|
4190
|
-
if (
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
r.events.emit("task.done", { task:
|
|
4195
|
+
for (const n of plan.nodes) {
|
|
4196
|
+
if (n.status === "parked" && n.splitInto.length && n.splitInto.every((id) => getNode(plan, id).status === "done")) {
|
|
4197
|
+
n.status = "done";
|
|
4198
|
+
n.doneAtCycle = r.state.cycle;
|
|
4199
|
+
n.parkedReason = void 0;
|
|
4200
|
+
r.events.emit("task.done", { task: n.id, via: "split" });
|
|
4195
4201
|
changed = true;
|
|
4196
4202
|
}
|
|
4197
4203
|
}
|
|
@@ -4325,6 +4331,7 @@ var Supervisor = class {
|
|
|
4325
4331
|
const sinceMs = r.clock.now() - (r.state.lastProgressAt ?? r.state.startedAt);
|
|
4326
4332
|
if (sinceCycles >= r.config.stuck.no_progress_cycles || sinceMs >= r.config.stuck.no_progress_hours * 36e5) {
|
|
4327
4333
|
r.events.emit("stuck.signal", { signal: "no_progress", cycles: sinceCycles, ms: sinceMs });
|
|
4334
|
+
if (await this.recoverNoProgress(sinceCycles)) return void 0;
|
|
4328
4335
|
return { status: "needs-human", reason: `no accepted commit for ${sinceCycles} cycles` };
|
|
4329
4336
|
}
|
|
4330
4337
|
if (r.state.noChecks && r.state.cycle >= 1)
|
|
@@ -4466,20 +4473,49 @@ ${clipDiff(diff, cfg.max_diff_chars * 2)}`
|
|
|
4466
4473
|
r.state.idleRounds = found ? 0 : r.state.idleRounds + 1;
|
|
4467
4474
|
if (!found) r.events.emit("autonomous.idle", { round, idle: r.state.idleRounds });
|
|
4468
4475
|
}
|
|
4476
|
+
/**
|
|
4477
|
+
* Autonomous mode has nobody to ask when nothing gets accepted for a while: park the work in
|
|
4478
|
+
* progress (it is listed in the report) and let the run move on to other tasks or the next
|
|
4479
|
+
* improvement round. Only `max_idle_rounds` recoveries in a row; an accepted commit resets it.
|
|
4480
|
+
*/
|
|
4481
|
+
async recoverNoProgress(sinceCycles) {
|
|
4482
|
+
const r = this.run;
|
|
4483
|
+
if (!r.config.autonomous.enabled) return false;
|
|
4484
|
+
if (r.state.recoveries >= r.config.autonomous.max_idle_rounds) return false;
|
|
4485
|
+
const plan = r.requirePlan();
|
|
4486
|
+
const stuck = plan.nodes.filter(
|
|
4487
|
+
(n) => n.type === "task" && (n.status === "doing" || n.id === r.state.taskId) && n.status !== "done"
|
|
4488
|
+
);
|
|
4489
|
+
for (const n of stuck) {
|
|
4490
|
+
n.status = "parked";
|
|
4491
|
+
n.parkedReason = `autonomous: no progress for ${sinceCycles} cycles; moved on`;
|
|
4492
|
+
}
|
|
4493
|
+
r.state.recoveries++;
|
|
4494
|
+
r.state.lastProgressCycle = r.state.cycle;
|
|
4495
|
+
r.state.lastProgressAt = r.clock.now();
|
|
4496
|
+
await r.savePlan();
|
|
4497
|
+
await r.save();
|
|
4498
|
+
r.events.emit("autonomous.recover", {
|
|
4499
|
+
parked: stuck.map((n) => n.id),
|
|
4500
|
+
recovery: r.state.recoveries,
|
|
4501
|
+
cycles: sinceCycles
|
|
4502
|
+
});
|
|
4503
|
+
return true;
|
|
4504
|
+
}
|
|
4469
4505
|
/**
|
|
4470
4506
|
* Autonomous mode has nobody to ask: work that can't proceed because something it needs was
|
|
4471
4507
|
* parked is parked too, so the run moves on to the next improvement round. The report lists it.
|
|
4472
4508
|
*/
|
|
4473
4509
|
async parkBlocked() {
|
|
4474
4510
|
const r = this.run;
|
|
4475
|
-
const blocked = r.requirePlan().nodes.filter((
|
|
4511
|
+
const blocked = r.requirePlan().nodes.filter((n) => n.status !== "done" && n.status !== "parked");
|
|
4476
4512
|
if (!blocked.length) return false;
|
|
4477
|
-
for (const
|
|
4478
|
-
|
|
4479
|
-
|
|
4513
|
+
for (const n of blocked) {
|
|
4514
|
+
n.status = "parked";
|
|
4515
|
+
n.parkedReason = "autonomous: blocked by parked work";
|
|
4480
4516
|
}
|
|
4481
4517
|
await r.savePlan();
|
|
4482
|
-
r.events.emit("autonomous.skipped", { nodes: blocked.map((
|
|
4518
|
+
r.events.emit("autonomous.skipped", { nodes: blocked.map((n) => n.id) });
|
|
4483
4519
|
return true;
|
|
4484
4520
|
}
|
|
4485
4521
|
/** Milestones, rolling-wave expansion, and the end-of-plan decision. Returns a task id or an outcome. */
|
|
@@ -4515,9 +4551,9 @@ ${clipDiff(diff, cfg.max_diff_chars * 2)}`
|
|
|
4515
4551
|
} else {
|
|
4516
4552
|
m.status = "parked";
|
|
4517
4553
|
m.parkedReason = "the planner could not expand it into tasks";
|
|
4518
|
-
const released = r.requirePlan().nodes.filter((
|
|
4519
|
-
|
|
4520
|
-
return
|
|
4554
|
+
const released = r.requirePlan().nodes.filter((n) => n.dependsOn.includes(m.id)).map((n) => {
|
|
4555
|
+
n.dependsOn = n.dependsOn.filter((d) => d !== m.id);
|
|
4556
|
+
return n.id;
|
|
4521
4557
|
});
|
|
4522
4558
|
if (released.length) r.events.emit("milestone.released", { parked: m.id, released });
|
|
4523
4559
|
}
|
|
@@ -4527,10 +4563,10 @@ ${clipDiff(diff, cfg.max_diff_chars * 2)}`
|
|
|
4527
4563
|
}
|
|
4528
4564
|
const counts = planCounts(plan);
|
|
4529
4565
|
const openMilestones = plan.nodes.filter(
|
|
4530
|
-
(
|
|
4566
|
+
(n) => n.type === "milestone" && n.status !== "done" && n.status !== "parked"
|
|
4531
4567
|
);
|
|
4532
4568
|
const auto = r.config.autonomous.enabled;
|
|
4533
|
-
if (counts.todo === 0 && openMilestones.length === 0 && (auto || plan.nodes.every((
|
|
4569
|
+
if (counts.todo === 0 && openMilestones.length === 0 && (auto || plan.nodes.every((n) => n.status !== "parked"))) {
|
|
4534
4570
|
if (await this.planAudit()) return this.select();
|
|
4535
4571
|
if (await this.planBeyond()) return this.select();
|
|
4536
4572
|
await this.finalVerify();
|
|
@@ -4541,7 +4577,7 @@ ${clipDiff(diff, cfg.max_diff_chars * 2)}`
|
|
|
4541
4577
|
};
|
|
4542
4578
|
}
|
|
4543
4579
|
if (r.config.autonomous.enabled && await this.parkBlocked()) return this.select();
|
|
4544
|
-
const parked = plan.nodes.filter((
|
|
4580
|
+
const parked = plan.nodes.filter((n) => n.status === "parked").length;
|
|
4545
4581
|
r.events.emit("ladder.rung", { rung: "stop_and_ask", parked });
|
|
4546
4582
|
return {
|
|
4547
4583
|
status: "needs-human",
|
|
@@ -4934,112 +4970,14 @@ function banner(io, columns = 80, version) {
|
|
|
4934
4970
|
return ["", mark, "", `${b.cyan("$ omnexx")}${ver}`, `> ${TAGLINE}`, ""].join("\n");
|
|
4935
4971
|
}
|
|
4936
4972
|
|
|
4937
|
-
// src/telemetry/aggregate.ts
|
|
4938
|
-
function emptyTelemetry() {
|
|
4939
|
-
return {
|
|
4940
|
-
cycle: 0,
|
|
4941
|
-
turns: 0,
|
|
4942
|
-
tokens: { input: 0, cacheRead: 0, cacheWrite: 0, output: 0 },
|
|
4943
|
-
usd: 0,
|
|
4944
|
-
toolCalls: 0,
|
|
4945
|
-
toolErrors: 0,
|
|
4946
|
-
commits: 0,
|
|
4947
|
-
rejects: 0,
|
|
4948
|
-
rollbacks: 0,
|
|
4949
|
-
gateRuns: 0,
|
|
4950
|
-
gatePasses: 0,
|
|
4951
|
-
compactions: 0,
|
|
4952
|
-
failovers: 0,
|
|
4953
|
-
quotaOut: [],
|
|
4954
|
-
stuck: 0,
|
|
4955
|
-
milestonesDone: 0
|
|
4956
|
-
};
|
|
4957
|
-
}
|
|
4958
|
-
var n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
4959
|
-
function fold(t, e) {
|
|
4960
|
-
t.startedAt ??= e.ts;
|
|
4961
|
-
t.lastAt = e.ts;
|
|
4962
|
-
t.cycle = Math.max(t.cycle, e.cycle);
|
|
4963
|
-
switch (e.type) {
|
|
4964
|
-
case "cycle.start":
|
|
4965
|
-
if (typeof e.task === "string") t.task = e.task;
|
|
4966
|
-
break;
|
|
4967
|
-
case "phase":
|
|
4968
|
-
if (typeof e.phase === "string") t.phase = e.phase;
|
|
4969
|
-
break;
|
|
4970
|
-
case "turn": {
|
|
4971
|
-
const k = e.tokens ?? {};
|
|
4972
|
-
t.turns++;
|
|
4973
|
-
t.tokens.input += n(k.uncached) + n(k.cacheWrite) + n(k.cacheRead);
|
|
4974
|
-
t.tokens.cacheRead += n(k.cacheRead);
|
|
4975
|
-
t.tokens.cacheWrite += n(k.cacheWrite);
|
|
4976
|
-
t.tokens.output += n(k.output);
|
|
4977
|
-
t.usd += n(e.usd);
|
|
4978
|
-
if (typeof e.model === "string") t.model = e.model;
|
|
4979
|
-
if (typeof e.provider === "string") t.provider = e.provider;
|
|
4980
|
-
break;
|
|
4981
|
-
}
|
|
4982
|
-
case "tool.call":
|
|
4983
|
-
t.toolCalls++;
|
|
4984
|
-
if (e.isError) t.toolErrors++;
|
|
4985
|
-
break;
|
|
4986
|
-
case "verify.gates":
|
|
4987
|
-
for (const g of Array.isArray(e.gates) ? e.gates : []) {
|
|
4988
|
-
t.gateRuns++;
|
|
4989
|
-
if (n(g.exitCode) === 0) t.gatePasses++;
|
|
4990
|
-
}
|
|
4991
|
-
break;
|
|
4992
|
-
case "verify.result":
|
|
4993
|
-
if (e.verdict !== "accept") t.rejects++;
|
|
4994
|
-
break;
|
|
4995
|
-
case "commit":
|
|
4996
|
-
t.commits++;
|
|
4997
|
-
if (typeof e.sha === "string") t.lastCommit = e.sha;
|
|
4998
|
-
break;
|
|
4999
|
-
case "rollback":
|
|
5000
|
-
t.rollbacks++;
|
|
5001
|
-
break;
|
|
5002
|
-
case "context.compacted":
|
|
5003
|
-
t.compactions++;
|
|
5004
|
-
break;
|
|
5005
|
-
case "run.finish":
|
|
5006
|
-
if (typeof e.status === "string") t.status = e.status;
|
|
5007
|
-
break;
|
|
5008
|
-
case "provider.failover":
|
|
5009
|
-
t.failovers++;
|
|
5010
|
-
t.lastFailover = `${String(e.provider)}${typeof e.model === "string" ? `:${e.model}` : ""}`;
|
|
5011
|
-
break;
|
|
5012
|
-
case "provider.quota_exhausted":
|
|
5013
|
-
if (typeof e.model === "string" && !t.quotaOut.includes(e.model)) t.quotaOut.push(e.model);
|
|
5014
|
-
break;
|
|
5015
|
-
case "route.decision":
|
|
5016
|
-
if (typeof e.by === "string") t.routeBy = e.by;
|
|
5017
|
-
break;
|
|
5018
|
-
case "stuck.signal":
|
|
5019
|
-
case "ladder.rung":
|
|
5020
|
-
t.stuck++;
|
|
5021
|
-
break;
|
|
5022
|
-
case "milestone.done":
|
|
5023
|
-
t.milestonesDone++;
|
|
5024
|
-
break;
|
|
5025
|
-
}
|
|
5026
|
-
return t;
|
|
5027
|
-
}
|
|
5028
|
-
function cacheHitRate(t) {
|
|
5029
|
-
return t.tokens.input ? t.tokens.cacheRead / t.tokens.input : 0;
|
|
5030
|
-
}
|
|
5031
|
-
function tokensPerCommit(t) {
|
|
5032
|
-
return t.commits ? Math.round((t.tokens.input + t.tokens.output) / t.commits) : void 0;
|
|
5033
|
-
}
|
|
5034
|
-
|
|
5035
4973
|
// src/telemetry/humanize.ts
|
|
5036
4974
|
var RANK2 = { quiet: 0, normal: 1, verbose: 2, debug: 3 };
|
|
5037
4975
|
var str2 = (v) => typeof v === "string" ? v : typeof v === "number" || typeof v === "boolean" ? String(v) : v == null ? "" : JSON.stringify(v);
|
|
5038
4976
|
var num2 = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
5039
|
-
function fmtTokens(
|
|
5040
|
-
if (
|
|
5041
|
-
if (
|
|
5042
|
-
return String(Math.round(
|
|
4977
|
+
function fmtTokens(n) {
|
|
4978
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
4979
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
4980
|
+
return String(Math.round(n));
|
|
5043
4981
|
}
|
|
5044
4982
|
function fmtMs(ms) {
|
|
5045
4983
|
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
@@ -5056,9 +4994,9 @@ function toolInput(e) {
|
|
|
5056
4994
|
return void 0;
|
|
5057
4995
|
}
|
|
5058
4996
|
}
|
|
5059
|
-
function clip(s,
|
|
4997
|
+
function clip(s, n) {
|
|
5060
4998
|
const one = s.replace(/\s+/g, " ").trim();
|
|
5061
|
-
return one.length >
|
|
4999
|
+
return one.length > n ? `${one.slice(0, n - 1)}\u2026` : one;
|
|
5062
5000
|
}
|
|
5063
5001
|
function toolLine(e) {
|
|
5064
5002
|
const name = str2(e.tool);
|
|
@@ -5445,7 +5383,7 @@ function debugLine(e, opts) {
|
|
|
5445
5383
|
}
|
|
5446
5384
|
function clockOf(ts, utc = false) {
|
|
5447
5385
|
const d = new Date(ts);
|
|
5448
|
-
const p = (
|
|
5386
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
5449
5387
|
return utc ? `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}` : `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
5450
5388
|
}
|
|
5451
5389
|
function verbosityFrom(f) {
|
|
@@ -5591,10 +5529,6 @@ export {
|
|
|
5591
5529
|
TAGLINE,
|
|
5592
5530
|
brand,
|
|
5593
5531
|
banner,
|
|
5594
|
-
emptyTelemetry,
|
|
5595
|
-
fold,
|
|
5596
|
-
cacheHitRate,
|
|
5597
|
-
tokensPerCommit,
|
|
5598
5532
|
fmtTokens,
|
|
5599
5533
|
fmtMs,
|
|
5600
5534
|
humanize,
|
|
@@ -5602,4 +5536,4 @@ export {
|
|
|
5602
5536
|
EventTail,
|
|
5603
5537
|
LiveFeed
|
|
5604
5538
|
};
|
|
5605
|
-
//# sourceMappingURL=chunk-
|
|
5539
|
+
//# sourceMappingURL=chunk-2TKRLH7N.js.map
|