@phamvuhoang/otto-core 0.1.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 -0
- package/dist/branch.d.ts +61 -0
- package/dist/branch.d.ts.map +1 -0
- package/dist/branch.js +215 -0
- package/dist/branch.js.map +1 -0
- package/dist/cli-help.d.ts +68 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +365 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/detach.d.ts +36 -0
- package/dist/detach.d.ts.map +1 -0
- package/dist/detach.js +52 -0
- package/dist/detach.js.map +1 -0
- package/dist/gh-main.d.ts +5 -0
- package/dist/gh-main.d.ts.map +1 -0
- package/dist/gh-main.js +16 -0
- package/dist/gh-main.js.map +1 -0
- package/dist/git.d.ts +14 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +50 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/keepalive.d.ts +24 -0
- package/dist/keepalive.d.ts.map +1 -0
- package/dist/keepalive.js +138 -0
- package/dist/keepalive.js.map +1 -0
- package/dist/loop.d.ts +42 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +238 -0
- package/dist/loop.js.map +1 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/notify.d.ts +28 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +119 -0
- package/dist/notify.js.map +1 -0
- package/dist/pacing.d.ts +8 -0
- package/dist/pacing.d.ts.map +1 -0
- package/dist/pacing.js +33 -0
- package/dist/pacing.js.map +1 -0
- package/dist/panel.d.ts +24 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +202 -0
- package/dist/panel.js.map +1 -0
- package/dist/rate-limit.d.ts +16 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +35 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/render.d.ts +8 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +130 -0
- package/dist/render.js.map +1 -0
- package/dist/retry.d.ts +17 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +34 -0
- package/dist/retry.js.map +1 -0
- package/dist/run-bin.d.ts +35 -0
- package/dist/run-bin.d.ts.map +1 -0
- package/dist/run-bin.js +241 -0
- package/dist/run-bin.js.map +1 -0
- package/dist/runner.d.ts +55 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +297 -0
- package/dist/runner.js.map +1 -0
- package/dist/stage-exec.d.ts +16 -0
- package/dist/stage-exec.d.ts.map +1 -0
- package/dist/stage-exec.js +35 -0
- package/dist/stage-exec.js.map +1 -0
- package/dist/stages.d.ts +38 -0
- package/dist/stages.d.ts.map +1 -0
- package/dist/stages.js +38 -0
- package/dist/stages.js.map +1 -0
- package/dist/state.d.ts +25 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +30 -0
- package/dist/state.js.map +1 -0
- package/dist/stream-render.d.ts +68 -0
- package/dist/stream-render.d.ts.map +1 -0
- package/dist/stream-render.js +162 -0
- package/dist/stream-render.js.map +1 -0
- package/dist/watch.d.ts +22 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +93 -0
- package/dist/watch.js.map +1 -0
- package/package.json +67 -0
- package/templates/afk.md +21 -0
- package/templates/apply-review.md +71 -0
- package/templates/ghafk-issue.md +29 -0
- package/templates/ghafk.md +29 -0
- package/templates/ghprompt-workflow.md +83 -0
- package/templates/ghprompt.md +39 -0
- package/templates/prompt.md +97 -0
- package/templates/review-lens.md +41 -0
- package/templates/review-synth.md +29 -0
- package/templates/review-verify.md +52 -0
- package/templates/review.md +62 -0
- package/templates/superpowers.md +70 -0
- package/templates/verify.md +74 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
const NOOP_RELEASER = { release: () => { } };
|
|
4
|
+
const DEFAULT_REASON = "otto-afk loop";
|
|
5
|
+
function defaultSpawner(command, args, options) {
|
|
6
|
+
return spawn(command, args, {
|
|
7
|
+
detached: options.detached ?? false,
|
|
8
|
+
stdio: options.stdio ?? "ignore",
|
|
9
|
+
windowsHide: true,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function defaultWslHint() {
|
|
13
|
+
try {
|
|
14
|
+
if (!existsSync("/proc/version"))
|
|
15
|
+
return false;
|
|
16
|
+
return /microsoft/i.test(readFileSync("/proc/version", "utf8"));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function defaultWarn(msg) {
|
|
23
|
+
process.stderr.write(`[keepalive] ${msg}\n`);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build the powershell argv that holds ES_SYSTEM_REQUIRED for the lifetime of
|
|
27
|
+
* the spawned child. Killing the child lets the OS clear ES_CONTINUOUS on
|
|
28
|
+
* process exit (per SetThreadExecutionState docs).
|
|
29
|
+
*/
|
|
30
|
+
function windowsArgs() {
|
|
31
|
+
const script = "Add-Type -TypeDefinition 'using System;using System.Runtime.InteropServices;" +
|
|
32
|
+
'public static class P{[DllImport("kernel32.dll")]' +
|
|
33
|
+
"public static extern uint SetThreadExecutionState(uint e);}';" +
|
|
34
|
+
"[P]::SetThreadExecutionState(0x80000000 -bor 0x00000001) | Out-Null;" +
|
|
35
|
+
"while($true){Start-Sleep -Seconds 60}";
|
|
36
|
+
return {
|
|
37
|
+
cmd: "powershell",
|
|
38
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", script],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function macosArgs(parentPid) {
|
|
42
|
+
return {
|
|
43
|
+
cmd: "caffeinate",
|
|
44
|
+
args: ["-i", "-w", String(parentPid)],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function linuxArgs(reason) {
|
|
48
|
+
return {
|
|
49
|
+
cmd: "systemd-inhibit",
|
|
50
|
+
args: [
|
|
51
|
+
"--what=sleep",
|
|
52
|
+
`--why=${reason}`,
|
|
53
|
+
"--mode=block",
|
|
54
|
+
"sleep",
|
|
55
|
+
"infinity",
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Acquire an OS wake-lock for the lifetime of the returned Releaser. Platform
|
|
61
|
+
* dispatch is per-OS; missing utilities degrade to a no-op (warning emitted
|
|
62
|
+
* once) so the loop never crashes on a stripped image.
|
|
63
|
+
*/
|
|
64
|
+
export function acquire(opts = {}) {
|
|
65
|
+
const platform = opts.platform ?? process.platform;
|
|
66
|
+
const spawner = opts.spawner ?? defaultSpawner;
|
|
67
|
+
const parentPid = opts.parentPid ?? process.pid;
|
|
68
|
+
const wslHint = opts.wslHint ?? defaultWslHint;
|
|
69
|
+
const warn = opts.warn ?? defaultWarn;
|
|
70
|
+
const reason = opts.reason ?? DEFAULT_REASON;
|
|
71
|
+
if (platform === "linux" && wslHint()) {
|
|
72
|
+
warn("WSL2 detected: systemd-inhibit blocks WSL idle only, not Windows host sleep. " +
|
|
73
|
+
"Configure the Windows power plan to keep the host awake.");
|
|
74
|
+
}
|
|
75
|
+
let spec;
|
|
76
|
+
if (platform === "win32") {
|
|
77
|
+
spec = windowsArgs();
|
|
78
|
+
}
|
|
79
|
+
else if (platform === "darwin") {
|
|
80
|
+
spec = macosArgs(parentPid);
|
|
81
|
+
}
|
|
82
|
+
else if (platform === "linux") {
|
|
83
|
+
spec = linuxArgs(reason);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
warn(`unsupported platform ${platform}: skipping wake-lock`);
|
|
87
|
+
return NOOP_RELEASER;
|
|
88
|
+
}
|
|
89
|
+
let child;
|
|
90
|
+
try {
|
|
91
|
+
child = spawner(spec.cmd, spec.args, { stdio: "ignore" });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const code = err.code;
|
|
95
|
+
if (code === "ENOENT") {
|
|
96
|
+
warn(`${spec.cmd} not found: continuing without wake-lock`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
warn(`failed to spawn ${spec.cmd}: ${err.message}. Continuing without wake-lock.`);
|
|
100
|
+
}
|
|
101
|
+
return NOOP_RELEASER;
|
|
102
|
+
}
|
|
103
|
+
// Swallow spawn-time errors emitted asynchronously (e.g. ENOENT surfaced on
|
|
104
|
+
// the child rather than thrown by spawn itself on some platforms).
|
|
105
|
+
let released = false;
|
|
106
|
+
child.on("error", (err) => {
|
|
107
|
+
if (released)
|
|
108
|
+
return;
|
|
109
|
+
released = true;
|
|
110
|
+
if (err.code === "ENOENT") {
|
|
111
|
+
warn(`${spec.cmd} not found: continuing without wake-lock`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
warn(`wake-lock child error: ${err.message}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
child.on("close", (code, signal) => {
|
|
118
|
+
if (released)
|
|
119
|
+
return;
|
|
120
|
+
released = true;
|
|
121
|
+
const reason = code == null ? `signal ${signal ?? "unknown"}` : `exit ${code}`;
|
|
122
|
+
warn(`${spec.cmd} wake-lock exited early (${reason}); continuing without wake-lock`);
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
release: () => {
|
|
126
|
+
if (released)
|
|
127
|
+
return;
|
|
128
|
+
released = true;
|
|
129
|
+
try {
|
|
130
|
+
child.kill();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Already dead; ignore.
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=keepalive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keepalive.js","sourceRoot":"","sources":["../src/keepalive.ts"],"names":[],"mappings":"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"}
|
package/dist/loop.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Stage } from "./stages.js";
|
|
2
|
+
export type LoopOptions = {
|
|
3
|
+
stages: [Stage, ...Stage[]];
|
|
4
|
+
inputs: string;
|
|
5
|
+
iterations: number;
|
|
6
|
+
/** Host repo Claude runs against (cwd). */
|
|
7
|
+
workspaceDir: string;
|
|
8
|
+
/** Installed @phamvuhoang/otto-core dir; stage templates are read from <packageDir>/templates. */
|
|
9
|
+
packageDir: string;
|
|
10
|
+
/** When true, skip OS wake-lock acquisition. Default: false. */
|
|
11
|
+
noKeepAlive?: boolean;
|
|
12
|
+
/** Per-stage retry budget. Default: 3. Set to 0 to disable retries. */
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
/** When true, fire OS notification + bell on loop terminal events. Default: false. */
|
|
15
|
+
notify?: boolean;
|
|
16
|
+
/** Bin name for the init-time version banner (e.g. "otto-afk"). */
|
|
17
|
+
bin?: string;
|
|
18
|
+
/** CLI version for the init-time version banner. */
|
|
19
|
+
cliVersion?: string;
|
|
20
|
+
/** Stop the loop when cumulative stage cost reaches this USD ceiling. */
|
|
21
|
+
budgetUsd?: number;
|
|
22
|
+
/** Milliseconds to wait between iterations. 0 = no cooldown. */
|
|
23
|
+
cooldownMs?: number;
|
|
24
|
+
/** Opt-in reviewer panel: replace the single reviewer stage with K read-only lens reviewers + one synth commit. */
|
|
25
|
+
reviewLenses?: string[];
|
|
26
|
+
/** Injected AbortSignal for daemon callers (e.g. watch mode). When provided,
|
|
27
|
+
* runLoop skips wake-lock acquisition and process signal handler installation;
|
|
28
|
+
* the caller owns both. */
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
/** Run mode for state.json identity (e.g. "afk" / "ghafk"). Default "afk". */
|
|
31
|
+
mode?: string;
|
|
32
|
+
/** Cap on the rate-limit wait before halting. Default 6h. */
|
|
33
|
+
maxWaitMs?: number;
|
|
34
|
+
/** Force a fresh run, ignoring/clearing prior state. Default false. */
|
|
35
|
+
fresh?: boolean;
|
|
36
|
+
};
|
|
37
|
+
export type LoopOutcome = {
|
|
38
|
+
costUsd: number;
|
|
39
|
+
sentinelHit: boolean;
|
|
40
|
+
};
|
|
41
|
+
export declare function runLoop(opts: LoopOptions): Promise<LoopOutcome>;
|
|
42
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAUzC,MAAM,MAAM,WAAW,GAAG;IAGxB,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mHAAmH;IACnH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;gCAE4B;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAuRrE"}
|
package/dist/loop.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { readCoreVersion } from "./cli-help.js";
|
|
3
|
+
import { acquire } from "./keepalive.js";
|
|
4
|
+
import { notifyComplete, notifyError } from "./notify.js";
|
|
5
|
+
import { sleep, isThrottle, nextCooldownFactor } from "./pacing.js";
|
|
6
|
+
import { computeWaitMs } from "./rate-limit.js";
|
|
7
|
+
import { DEFAULT_MAX_RETRIES } from "./retry.js";
|
|
8
|
+
import { stageLogPath } from "./runner.js";
|
|
9
|
+
import { executeStage } from "./stage-exec.js";
|
|
10
|
+
import { clearState, matchesResume, readState, writeState, } from "./state.js";
|
|
11
|
+
import { USE_COLOR, dim, bold, red, greenOut, boldOut, dimOut, SYM, SYM_OUT, } from "./stream-render.js";
|
|
12
|
+
// The agent emits this literal when there is no more work; the same string is
|
|
13
|
+
// mirrored in the playbook templates (prompt.md / ghprompt.md) that instruct it.
|
|
14
|
+
const SENTINEL = "<promise>NO MORE TASKS</promise>";
|
|
15
|
+
const RATE_LIMIT_BUFFER_MS = 30_000;
|
|
16
|
+
const RATE_LIMIT_FALLBACK_MS = 15 * 60_000;
|
|
17
|
+
const DEFAULT_MAX_WAIT_MS = 6 * 3600_000;
|
|
18
|
+
export async function runLoop(opts) {
|
|
19
|
+
const { stages, inputs, iterations, workspaceDir, packageDir, noKeepAlive = false, maxRetries = DEFAULT_MAX_RETRIES, notify = false, bin = "otto", cliVersion = "?", budgetUsd, cooldownMs = 0, reviewLenses, signal: externalSignal, mode = "afk", maxWaitMs = DEFAULT_MAX_WAIT_MS, fresh = false, } = opts;
|
|
20
|
+
const versionLine = `${bin} ${cliVersion} (core ${readCoreVersion()})`;
|
|
21
|
+
process.stderr.write(`${USE_COLOR ? `${dim("━━━")} ${bold(versionLine)} ${dim("━━━")}` : `== ${versionLine} ==`}\n`);
|
|
22
|
+
// When an external signal is injected (daemon/watch mode), the caller owns
|
|
23
|
+
// wake-lock + process signal handlers. Skip both here.
|
|
24
|
+
const releaser = externalSignal || noKeepAlive
|
|
25
|
+
? { release: () => { } }
|
|
26
|
+
: acquire({ reason: `${bin} loop` });
|
|
27
|
+
const stageAbort = externalSignal ? undefined : new AbortController();
|
|
28
|
+
const activeSignal = externalSignal ?? stageAbort.signal;
|
|
29
|
+
// Single release path: signal handlers and the finally below all funnel
|
|
30
|
+
// through releaseOnce so the wake-lock child is killed exactly once.
|
|
31
|
+
let released = false;
|
|
32
|
+
const releaseOnce = () => {
|
|
33
|
+
if (released)
|
|
34
|
+
return;
|
|
35
|
+
released = true;
|
|
36
|
+
releaser.release();
|
|
37
|
+
};
|
|
38
|
+
let onSigint;
|
|
39
|
+
let onSigterm;
|
|
40
|
+
if (!externalSignal) {
|
|
41
|
+
const abortActiveStage = () => {
|
|
42
|
+
if (!stageAbort.signal.aborted)
|
|
43
|
+
stageAbort.abort();
|
|
44
|
+
};
|
|
45
|
+
onSigint = () => {
|
|
46
|
+
abortActiveStage();
|
|
47
|
+
if (notify)
|
|
48
|
+
notifyError("interrupted (SIGINT)");
|
|
49
|
+
releaseOnce();
|
|
50
|
+
process.exit(130);
|
|
51
|
+
};
|
|
52
|
+
onSigterm = () => {
|
|
53
|
+
abortActiveStage();
|
|
54
|
+
if (notify)
|
|
55
|
+
notifyError("terminated (SIGTERM)");
|
|
56
|
+
releaseOnce();
|
|
57
|
+
process.exit(143);
|
|
58
|
+
};
|
|
59
|
+
process.on("SIGINT", onSigint);
|
|
60
|
+
process.on("SIGTERM", onSigterm);
|
|
61
|
+
}
|
|
62
|
+
let completedIterations = 0;
|
|
63
|
+
let sentinelHit = false;
|
|
64
|
+
let runCostUsd = 0;
|
|
65
|
+
let cooldownFactor = 1;
|
|
66
|
+
// Single source of truth for per-stage accounting: tally cost, report it,
|
|
67
|
+
// advance the adaptive cooldown factor on throttle, and report whether the
|
|
68
|
+
// budget is now exhausted. Used once per non-panel stage AND once per panel
|
|
69
|
+
// sub-agent (passed to runPanel as onStage), so budget + adaptive pacing
|
|
70
|
+
// apply uniformly to lenses, synth, and ordinary stages alike.
|
|
71
|
+
const accountStage = (sr) => {
|
|
72
|
+
runCostUsd += sr.costUsd;
|
|
73
|
+
process.stderr.write(`${dim(`· $${sr.costUsd.toFixed(2)} (run $${runCostUsd.toFixed(2)})`)}\n`);
|
|
74
|
+
cooldownFactor = nextCooldownFactor(cooldownFactor, isThrottle(sr.apiErrorStatus));
|
|
75
|
+
return {
|
|
76
|
+
stop: budgetUsd != null && runCostUsd >= budgetUsd,
|
|
77
|
+
cooldownFactor,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const nowIso = () => new Date().toISOString();
|
|
81
|
+
if (fresh)
|
|
82
|
+
clearState(workspaceDir);
|
|
83
|
+
const prior = fresh ? null : readState(workspaceDir);
|
|
84
|
+
const resuming = matchesResume(prior, { bin, mode, inputs });
|
|
85
|
+
const startIteration = resuming ? prior.iteration : 1;
|
|
86
|
+
const total = resuming ? prior.of : iterations;
|
|
87
|
+
let resumeNote = "";
|
|
88
|
+
if (resuming) {
|
|
89
|
+
resumeNote = `Resumed run (iteration ${startIteration} of ${total}). Prior work is committed — reconcile against git history and the working tree before acting; do not redo completed tasks.`;
|
|
90
|
+
process.stdout.write(`${greenOut(SYM_OUT.bullet)} ${boldOut("resuming")}${dimOut(` from iteration ${startIteration}/${total}`)}\n`);
|
|
91
|
+
}
|
|
92
|
+
const persist = (iteration, status, resetsAt) => writeState(workspaceDir, {
|
|
93
|
+
bin,
|
|
94
|
+
mode,
|
|
95
|
+
inputs,
|
|
96
|
+
iteration,
|
|
97
|
+
of: total,
|
|
98
|
+
status,
|
|
99
|
+
resetsAt: resetsAt ?? null,
|
|
100
|
+
startedAt: prior?.startedAt ?? nowIso(),
|
|
101
|
+
updatedAt: nowIso(),
|
|
102
|
+
});
|
|
103
|
+
if (resuming && prior.status === "waiting-rate-limit") {
|
|
104
|
+
const waitMs = computeWaitMs(prior.resetsAt ?? null, Date.now(), RATE_LIMIT_BUFFER_MS, 0);
|
|
105
|
+
if (waitMs > 0 && waitMs <= maxWaitMs) {
|
|
106
|
+
process.stderr.write(`${dim(`waiting ${Math.round(waitMs / 60000)}m to clear the prior rate limit`)}\n`);
|
|
107
|
+
await sleep(waitMs, activeSignal);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
for (let i = startIteration; i <= total; i++) {
|
|
112
|
+
persist(i, "running");
|
|
113
|
+
for (let s = 0; s < stages.length; s++) {
|
|
114
|
+
const stage = stages[s];
|
|
115
|
+
// Budget gate: check before running each stage.
|
|
116
|
+
if (budgetUsd != null && runCostUsd >= budgetUsd) {
|
|
117
|
+
process.stdout.write(`${greenOut(SYM_OUT.bullet)} ${boldOut("budget reached")}${dimOut(` $${runCostUsd.toFixed(2)} ≥ $${budgetUsd.toFixed(2)} after ${i - 1} iterations`)}\n`);
|
|
118
|
+
return { costUsd: runCostUsd, sentinelHit };
|
|
119
|
+
}
|
|
120
|
+
const banner = USE_COLOR
|
|
121
|
+
? `${dim("━━━")} ${bold(`iteration ${i}/${total}`)} ${dim("·")} ${bold(stage.name)} ${dim(`(stage ${s + 1}/${stages.length})`)} ${dim("━━━")}`
|
|
122
|
+
: `== iteration ${i}/${total} · ${stage.name} (stage ${s + 1}/${stages.length}) ==`;
|
|
123
|
+
process.stderr.write(`\n${banner}\n`);
|
|
124
|
+
const usePanel = reviewLenses && reviewLenses.length > 0 && stage.name === "reviewer";
|
|
125
|
+
let sr;
|
|
126
|
+
const runOnce = async () => {
|
|
127
|
+
if (usePanel) {
|
|
128
|
+
const { runPanel } = await import("./panel.js");
|
|
129
|
+
return runPanel({
|
|
130
|
+
lenses: reviewLenses,
|
|
131
|
+
workspaceDir,
|
|
132
|
+
packageDir,
|
|
133
|
+
iteration: i,
|
|
134
|
+
maxRetries,
|
|
135
|
+
cooldownMs,
|
|
136
|
+
signal: activeSignal,
|
|
137
|
+
onStage: accountStage,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const r = await executeStage({
|
|
141
|
+
stage,
|
|
142
|
+
vars: { INPUTS: inputs, RESUME: resumeNote },
|
|
143
|
+
workspaceDir,
|
|
144
|
+
packageDir,
|
|
145
|
+
iteration: i,
|
|
146
|
+
maxRetries,
|
|
147
|
+
signal: activeSignal,
|
|
148
|
+
});
|
|
149
|
+
accountStage(r);
|
|
150
|
+
return r;
|
|
151
|
+
};
|
|
152
|
+
try {
|
|
153
|
+
for (;;) {
|
|
154
|
+
try {
|
|
155
|
+
sr = await runOnce();
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err?.name !== "RateLimitError")
|
|
160
|
+
throw err;
|
|
161
|
+
const resetsAt = err.resetsAt;
|
|
162
|
+
const waitMs = computeWaitMs(resetsAt, Date.now(), RATE_LIMIT_BUFFER_MS, RATE_LIMIT_FALLBACK_MS);
|
|
163
|
+
if (waitMs > maxWaitMs) {
|
|
164
|
+
persist(i, "interrupted", resetsAt);
|
|
165
|
+
process.stdout.write(`${red(SYM.cross)} ${bold("rate limit")}${dim(` — reset is beyond --max-wait; halting at iteration ${i}. Re-run to resume.`)}\n`);
|
|
166
|
+
return { costUsd: runCostUsd, sentinelHit };
|
|
167
|
+
}
|
|
168
|
+
persist(i, "waiting-rate-limit", resetsAt);
|
|
169
|
+
const mins = Math.round(waitMs / 60000);
|
|
170
|
+
process.stderr.write(`${dim(`⏸ rate limit — waiting ~${mins}m until reset, then resuming`)}\n`);
|
|
171
|
+
await sleep(waitMs, activeSignal);
|
|
172
|
+
persist(i, "running");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (activeSignal.aborted) {
|
|
178
|
+
return { costUsd: runCostUsd, sentinelHit };
|
|
179
|
+
}
|
|
180
|
+
const stageLog = stageLogPath(workspaceDir, i, stage.name);
|
|
181
|
+
const failureMarker = `[failure] iteration ${i} stage ${stage.name} failed after ${maxRetries} retries: ${err.message}`;
|
|
182
|
+
try {
|
|
183
|
+
appendFileSync(stageLog, failureMarker + "\n");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// log file may be unwritable; stderr still carries the failure.
|
|
187
|
+
}
|
|
188
|
+
const msg = `${red(SYM.cross)} ${bold("iteration " + i + " stage " + stage.name + " failed")} after ${maxRetries} retries: ${err.message}`;
|
|
189
|
+
process.stderr.write(msg + "\n");
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
// Cost/pacing accounting is handled by accountStage — called once per
|
|
193
|
+
// non-panel stage above, and once per sub-agent inside runPanel.
|
|
194
|
+
if (s === 0) {
|
|
195
|
+
if (sr.result.includes(SENTINEL)) {
|
|
196
|
+
const msg = greenOut(SYM_OUT.bullet) +
|
|
197
|
+
" " +
|
|
198
|
+
boldOut("Otto complete") +
|
|
199
|
+
dimOut(" after " + i + " iterations");
|
|
200
|
+
process.stdout.write(msg + "\n");
|
|
201
|
+
sentinelHit = true;
|
|
202
|
+
completedIterations = i;
|
|
203
|
+
persist(i, "complete");
|
|
204
|
+
clearState(workspaceDir);
|
|
205
|
+
return { costUsd: runCostUsd, sentinelHit };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
completedIterations = i;
|
|
210
|
+
// Cooldown between iterations.
|
|
211
|
+
if (cooldownMs > 0 && i < total) {
|
|
212
|
+
const wait = cooldownMs * cooldownFactor;
|
|
213
|
+
if (cooldownFactor > 1) {
|
|
214
|
+
process.stderr.write(`${dim(`cooldown ×${cooldownFactor} → ${wait}ms (throttle backoff)`)}\n`);
|
|
215
|
+
}
|
|
216
|
+
await sleep(wait, activeSignal);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
if (notify)
|
|
222
|
+
notifyError(err.message);
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
if (onSigint)
|
|
227
|
+
process.off("SIGINT", onSigint);
|
|
228
|
+
if (onSigterm)
|
|
229
|
+
process.off("SIGTERM", onSigterm);
|
|
230
|
+
releaseOnce();
|
|
231
|
+
if (notify && (sentinelHit || completedIterations === total)) {
|
|
232
|
+
notifyComplete(completedIterations, sentinelHit);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
clearState(workspaceDir);
|
|
236
|
+
return { costUsd: runCostUsd, sentinelHit };
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=loop.js.map
|
package/dist/loop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runBin } from "./run-bin.js";
|
|
2
|
+
import { STAGES } from "./stages.js";
|
|
3
|
+
export async function runAfk(argv, opts = {}) {
|
|
4
|
+
await runBin(argv, {
|
|
5
|
+
bin: "otto-afk",
|
|
6
|
+
usage: "<plan-and-prd> <iterations>",
|
|
7
|
+
desc: "plan/PRD-driven Claude Code AFK loop",
|
|
8
|
+
stages: [STAGES.implementer, STAGES.reviewer],
|
|
9
|
+
takesInputArg: true,
|
|
10
|
+
cliVersion: opts.cliVersion,
|
|
11
|
+
verifyStage: STAGES.verifier,
|
|
12
|
+
applyReviewStage: STAGES.applyReviewImplementer,
|
|
13
|
+
mode: "afk",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,IAAc,EACd,OAAsB,EAAE;IAExB,MAAM,MAAM,CAAC,IAAI,EAAE;QACjB,GAAG,EAAE,UAAU;QACf,KAAK,EAAE,6BAA6B;QACpC,IAAI,EAAE,sCAAsC;QAC5C,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,QAAQ,CAAC;QAC7C,aAAa,EAAE,IAAI;QACnB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,WAAW,EAAE,MAAM,CAAC,QAAQ;QAC5B,gBAAgB,EAAE,MAAM,CAAC,sBAAsB;QAC/C,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC"}
|
package/dist/notify.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
export type NotifyLevel = "info" | "error";
|
|
3
|
+
export type NotifySpawnedChild = Pick<ChildProcess, "on" | "unref">;
|
|
4
|
+
export type NotifySpawner = (command: string, args: readonly string[]) => NotifySpawnedChild;
|
|
5
|
+
export type NotifyOptions = {
|
|
6
|
+
title: string;
|
|
7
|
+
body: string;
|
|
8
|
+
level: NotifyLevel;
|
|
9
|
+
/** When false, suppress the terminal bell. Default: true. */
|
|
10
|
+
sound?: boolean;
|
|
11
|
+
platform?: NodeJS.Platform;
|
|
12
|
+
spawner?: NotifySpawner;
|
|
13
|
+
stderr?: {
|
|
14
|
+
write: (s: string) => void;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Fire a best-effort OS notification + terminal bell. Synchronous return;
|
|
19
|
+
* never blocks on toast delivery, never crashes on a missing utility.
|
|
20
|
+
*
|
|
21
|
+
* Bell (\x07) is written to stderr on every call regardless of OS path.
|
|
22
|
+
* Apostrophes / quotes in `title` / `body` are escaped per-platform so a
|
|
23
|
+
* `'` in the message doesn't break the shell wrapping.
|
|
24
|
+
*/
|
|
25
|
+
export declare function notify(opts: NotifyOptions): void;
|
|
26
|
+
export declare function notifyComplete(iterations: number, sentinel: boolean): void;
|
|
27
|
+
export declare function notifyError(message: string): void;
|
|
28
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE9D,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3C,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;AAEpE,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,KACpB,kBAAkB,CAAC;AAExB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACzC,CAAC;AAmGF;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAsBhD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAQ1E;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMjD"}
|
package/dist/notify.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
function defaultSpawner(command, args) {
|
|
3
|
+
return spawn(command, args, {
|
|
4
|
+
detached: true,
|
|
5
|
+
stdio: "ignore",
|
|
6
|
+
windowsHide: true,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Spawn a fire-and-forget child. Swallows spawn-time + async errors so a
|
|
11
|
+
* missing utility (ENOENT) never crashes the loop.
|
|
12
|
+
*/
|
|
13
|
+
function fireAndForget(spawner, cmd, args) {
|
|
14
|
+
try {
|
|
15
|
+
const child = spawner(cmd, args);
|
|
16
|
+
child.on("error", () => {
|
|
17
|
+
// Async ENOENT (Linux): swallow. The whole point is fire-and-forget.
|
|
18
|
+
});
|
|
19
|
+
child.unref();
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function escDoubleQuote(s) {
|
|
27
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
28
|
+
}
|
|
29
|
+
function escPsString(s) {
|
|
30
|
+
// PowerShell single-quoted literal: ' is escaped by doubling.
|
|
31
|
+
return s.replace(/'/g, "''");
|
|
32
|
+
}
|
|
33
|
+
function windowsToast(spawner, title, body) {
|
|
34
|
+
// Try BurntToast first; fall back to msg.exe. Both fire-and-forget; if the
|
|
35
|
+
// module is missing, the powershell child exits non-zero — we don't observe
|
|
36
|
+
// it, the caller has already gotten the bell.
|
|
37
|
+
const psScript = `if (Get-Module -ListAvailable -Name BurntToast) { ` +
|
|
38
|
+
`Import-Module BurntToast; ` +
|
|
39
|
+
`New-BurntToastNotification -Text '${escPsString(title)}','${escPsString(body)}' ` +
|
|
40
|
+
`} else { exit 1 }`;
|
|
41
|
+
const ok = fireAndForget(spawner, "powershell", [
|
|
42
|
+
"-NoProfile",
|
|
43
|
+
"-NonInteractive",
|
|
44
|
+
"-Command",
|
|
45
|
+
psScript,
|
|
46
|
+
]);
|
|
47
|
+
if (!ok)
|
|
48
|
+
return false;
|
|
49
|
+
// Always also try msg.exe as a secondary path (cheap, mostly a no-op when
|
|
50
|
+
// BurntToast wins). msg.exe is not on every Windows SKU (Home edition lacks
|
|
51
|
+
// it) — swallow failure.
|
|
52
|
+
fireAndForget(spawner, "msg.exe", ["*", `${title}: ${body}`]);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
function macosBanner(spawner, title, body, level) {
|
|
56
|
+
const sound = level === "info" ? "Glass" : "Basso";
|
|
57
|
+
const script = `display notification "${escDoubleQuote(body)}" ` +
|
|
58
|
+
`with title "${escDoubleQuote(title)}" ` +
|
|
59
|
+
`sound name "${sound}"`;
|
|
60
|
+
return fireAndForget(spawner, "osascript", ["-e", script]);
|
|
61
|
+
}
|
|
62
|
+
function linuxNotifySend(spawner, title, body, level) {
|
|
63
|
+
const urgency = level === "error" ? "critical" : "normal";
|
|
64
|
+
return fireAndForget(spawner, "notify-send", [
|
|
65
|
+
"--urgency",
|
|
66
|
+
urgency,
|
|
67
|
+
title,
|
|
68
|
+
body,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Fire a best-effort OS notification + terminal bell. Synchronous return;
|
|
73
|
+
* never blocks on toast delivery, never crashes on a missing utility.
|
|
74
|
+
*
|
|
75
|
+
* Bell (\x07) is written to stderr on every call regardless of OS path.
|
|
76
|
+
* Apostrophes / quotes in `title` / `body` are escaped per-platform so a
|
|
77
|
+
* `'` in the message doesn't break the shell wrapping.
|
|
78
|
+
*/
|
|
79
|
+
export function notify(opts) {
|
|
80
|
+
const platform = opts.platform ?? process.platform;
|
|
81
|
+
const spawner = opts.spawner ?? defaultSpawner;
|
|
82
|
+
const stderr = opts.stderr ?? process.stderr;
|
|
83
|
+
const sound = opts.sound ?? true;
|
|
84
|
+
if (sound) {
|
|
85
|
+
try {
|
|
86
|
+
stderr.write("\x07");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// never crash on a bell write.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (platform === "win32") {
|
|
93
|
+
windowsToast(spawner, opts.title, opts.body);
|
|
94
|
+
}
|
|
95
|
+
else if (platform === "darwin") {
|
|
96
|
+
macosBanner(spawner, opts.title, opts.body, opts.level);
|
|
97
|
+
}
|
|
98
|
+
else if (platform === "linux") {
|
|
99
|
+
linuxNotifySend(spawner, opts.title, opts.body, opts.level);
|
|
100
|
+
}
|
|
101
|
+
// unsupported platforms: bell-only, no toast attempt.
|
|
102
|
+
}
|
|
103
|
+
export function notifyComplete(iterations, sentinel) {
|
|
104
|
+
notify({
|
|
105
|
+
level: "info",
|
|
106
|
+
title: "Otto complete",
|
|
107
|
+
body: sentinel
|
|
108
|
+
? `Sentinel hit after ${iterations} iteration${iterations === 1 ? "" : "s"}.`
|
|
109
|
+
: `Reached iteration cap (${iterations}).`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export function notifyError(message) {
|
|
113
|
+
notify({
|
|
114
|
+
level: "error",
|
|
115
|
+
title: "Otto failed",
|
|
116
|
+
body: message,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"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"}
|
package/dist/pacing.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Abortable delay. Resolves after `ms`; rejects with an AbortError if `signal` fires. */
|
|
2
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
3
|
+
export declare const THROTTLE_RE: RegExp;
|
|
4
|
+
/** True when a result's `api_error_status` looks like provider throttling. */
|
|
5
|
+
export declare function isThrottle(apiErrorStatus: string | null): boolean;
|
|
6
|
+
/** Adaptive cooldown multiplier: reset to 1 when healthy, else double up to `cap`. */
|
|
7
|
+
export declare function nextCooldownFactor(prev: number, throttled: boolean, cap?: number): number;
|
|
8
|
+
//# sourceMappingURL=pacing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pacing.d.ts","sourceRoot":"","sources":["../src/pacing.ts"],"names":[],"mappings":"AAMA,0FAA0F;AAC1F,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrE;AAED,eAAO,MAAM,WAAW,QAA8B,CAAC;AAEvD,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAEjE;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,OAAO,EAClB,GAAG,SAAI,GACN,MAAM,CAER"}
|