automata-cli 0.1.0-feature-030-do-work.204 → 0.1.0-feature-030-do-work.208
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.
|
@@ -73,7 +73,7 @@ function handleMenu(input, key, menu, cancel) {
|
|
|
73
73
|
cancel();
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
function TextEntryScreen({ title, label, value, hint }) {
|
|
76
|
+
function TextEntryScreen({ title, label, value, hint, error }) {
|
|
77
77
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginY: 1, children: [
|
|
78
78
|
/* @__PURE__ */ jsx(Text, { bold: true, children: title }),
|
|
79
79
|
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
@@ -85,7 +85,7 @@ function TextEntryScreen({ title, label, value, hint }) {
|
|
|
85
85
|
/* @__PURE__ */ jsx(Text, { children: "_" })
|
|
86
86
|
] })
|
|
87
87
|
] }),
|
|
88
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
88
|
+
error ? /* @__PURE__ */ jsx(Text, { color: "red", children: error }) : /* @__PURE__ */ jsx(Text, { children: " " }),
|
|
89
89
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hint })
|
|
90
90
|
] });
|
|
91
91
|
}
|
|
@@ -102,6 +102,12 @@ function MenuEntryScreen({ title, options, index, hint }) {
|
|
|
102
102
|
] });
|
|
103
103
|
}
|
|
104
104
|
var BACK = "Esc to go back \xB7 Ctrl+C to cancel";
|
|
105
|
+
function parseWholeInt(value) {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
108
|
+
const parsed = Number(trimmed);
|
|
109
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
110
|
+
}
|
|
105
111
|
function savePrompt(filename, content, merge) {
|
|
106
112
|
let value;
|
|
107
113
|
if (content) {
|
|
@@ -143,6 +149,10 @@ function ConfigWizard() {
|
|
|
143
149
|
const [doWorkExecutorIndex, setDoWorkExecutorIndex] = useState(Math.max(initialExecutorIndex, 0));
|
|
144
150
|
const [doWorkClaudeModel, setDoWorkClaudeModel] = useState(existing.doWork?.models?.claude ?? "");
|
|
145
151
|
const [doWorkCodexModel, setDoWorkCodexModel] = useState(existing.doWork?.models?.codex ?? "");
|
|
152
|
+
const [doWorkLockStale, setDoWorkLockStale] = useState(
|
|
153
|
+
String(existing.doWork?.lockStaleMinutes ?? DEFAULT_DO_WORK.lockStaleMinutes)
|
|
154
|
+
);
|
|
155
|
+
const [validationError, setValidationError] = useState("");
|
|
146
156
|
const [doWorkMaxRuns, setDoWorkMaxRuns] = useState(
|
|
147
157
|
String(existing.doWork?.maxRunsPerTick ?? DEFAULT_DO_WORK.maxRunsPerTick)
|
|
148
158
|
);
|
|
@@ -250,10 +260,33 @@ function ConfigWizard() {
|
|
|
250
260
|
onBack: () => setScreen("do-work-claude-model")
|
|
251
261
|
},
|
|
252
262
|
"do-work-max-runs": {
|
|
253
|
-
setValue:
|
|
263
|
+
setValue: (update) => {
|
|
264
|
+
setValidationError("");
|
|
265
|
+
setDoWorkMaxRuns(update);
|
|
266
|
+
},
|
|
254
267
|
onSubmit: () => {
|
|
255
|
-
const
|
|
256
|
-
|
|
268
|
+
const parsed = parseWholeInt(doWorkMaxRuns);
|
|
269
|
+
if (parsed === null || parsed < 0) {
|
|
270
|
+
setValidationError("Enter a non-negative whole number (0 = unlimited).");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
setValidationError("");
|
|
274
|
+
setScreen("do-work-lock-stale");
|
|
275
|
+
},
|
|
276
|
+
onBack: () => setScreen("do-work-codex-model")
|
|
277
|
+
},
|
|
278
|
+
"do-work-lock-stale": {
|
|
279
|
+
setValue: (update) => {
|
|
280
|
+
setValidationError("");
|
|
281
|
+
setDoWorkLockStale(update);
|
|
282
|
+
},
|
|
283
|
+
onSubmit: () => {
|
|
284
|
+
const parsed = parseWholeInt(doWorkLockStale);
|
|
285
|
+
if (parsed === null || parsed <= 0) {
|
|
286
|
+
setValidationError("Enter a whole number of minutes greater than zero.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const maxRuns = parseWholeInt(doWorkMaxRuns);
|
|
257
290
|
const current = readRawConfig();
|
|
258
291
|
writeConfig({
|
|
259
292
|
...current,
|
|
@@ -265,12 +298,13 @@ function ConfigWizard() {
|
|
|
265
298
|
claude: doWorkClaudeModel.trim() || void 0,
|
|
266
299
|
codex: doWorkCodexModel.trim() || void 0
|
|
267
300
|
},
|
|
268
|
-
maxRunsPerTick:
|
|
301
|
+
maxRunsPerTick: maxRuns ?? void 0,
|
|
302
|
+
lockStaleMinutes: parsed
|
|
269
303
|
}
|
|
270
304
|
});
|
|
271
305
|
exit();
|
|
272
306
|
},
|
|
273
|
-
onBack: () => setScreen("do-work-
|
|
307
|
+
onBack: () => setScreen("do-work-max-runs")
|
|
274
308
|
},
|
|
275
309
|
"do-work-discuss-prompt": {
|
|
276
310
|
setValue: setDoWorkDiscussPrompt,
|
|
@@ -421,6 +455,12 @@ function ConfigWizard() {
|
|
|
421
455
|
title: "Do Work \u2014 Max Runs Per Tick",
|
|
422
456
|
label: "Model runs allowed per tick (0 = unlimited):",
|
|
423
457
|
value: doWorkMaxRuns,
|
|
458
|
+
hint: `Type a number \xB7 Enter to continue \xB7 ${BACK}`
|
|
459
|
+
},
|
|
460
|
+
"do-work-lock-stale": {
|
|
461
|
+
title: "Do Work \u2014 Lock Staleness",
|
|
462
|
+
label: "Minutes before a run lock from another host is treated as stale:",
|
|
463
|
+
value: doWorkLockStale,
|
|
424
464
|
hint: `Type a number \xB7 Enter to save \xB7 ${BACK}`
|
|
425
465
|
},
|
|
426
466
|
"do-work-discuss-prompt": {
|
|
@@ -469,7 +509,7 @@ function ConfigWizard() {
|
|
|
469
509
|
}
|
|
470
510
|
};
|
|
471
511
|
const textView = textViews[screen];
|
|
472
|
-
if (textView) return /* @__PURE__ */ jsx(TextEntryScreen, { ...textView });
|
|
512
|
+
if (textView) return /* @__PURE__ */ jsx(TextEntryScreen, { ...textView, error: validationError });
|
|
473
513
|
const menuView = menuViews[screen];
|
|
474
514
|
if (menuView) return /* @__PURE__ */ jsx(MenuEntryScreen, { ...menuView });
|
|
475
515
|
return null;
|
package/dist/index.js
CHANGED
|
@@ -183,7 +183,7 @@ var configCommand = new Command("config").description("Configure automata settin
|
|
|
183
183
|
const [{ render }, React, { ConfigWizard }] = await Promise.all([
|
|
184
184
|
import("ink"),
|
|
185
185
|
import("react"),
|
|
186
|
-
import("./ConfigWizard-
|
|
186
|
+
import("./ConfigWizard-OYNUMRTO.js")
|
|
187
187
|
]);
|
|
188
188
|
const { waitUntilExit } = render(React.createElement(ConfigWizard));
|
|
189
189
|
await waitUntilExit();
|
|
@@ -1529,29 +1529,38 @@ function trackChild(child) {
|
|
|
1529
1529
|
function untrackChild(child) {
|
|
1530
1530
|
active.delete(child);
|
|
1531
1531
|
}
|
|
1532
|
-
function
|
|
1532
|
+
function waitForExit(child) {
|
|
1533
|
+
return new Promise((resolve2) => {
|
|
1534
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
1535
|
+
resolve2();
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
child.once("exit", () => resolve2());
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
function afterDelay(ms) {
|
|
1542
|
+
return new Promise((resolve2) => {
|
|
1543
|
+
const timer = setTimeout(() => resolve2("timeout"), ms);
|
|
1544
|
+
timer.unref();
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
async function terminateTrackedChildren(timeoutMs = 1e4, killGraceMs = 5e3) {
|
|
1533
1548
|
const children = [...active];
|
|
1534
|
-
if (children.length === 0) return
|
|
1535
|
-
const exits = children.map(
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
new Promise((resolve2) => {
|
|
1548
|
-
const timer = setTimeout(() => {
|
|
1549
|
-
for (const child of children) child.kill("SIGKILL");
|
|
1550
|
-
resolve2();
|
|
1551
|
-
}, timeoutMs);
|
|
1552
|
-
timer.unref();
|
|
1553
|
-
})
|
|
1549
|
+
if (children.length === 0) return true;
|
|
1550
|
+
const exits = children.map((child) => waitForExit(child));
|
|
1551
|
+
for (const child of children) {
|
|
1552
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
1553
|
+
}
|
|
1554
|
+
const settled = await Promise.race([Promise.all(exits).then(() => "exited"), afterDelay(timeoutMs)]);
|
|
1555
|
+
if (settled === "exited") return true;
|
|
1556
|
+
for (const child of children) {
|
|
1557
|
+
if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL");
|
|
1558
|
+
}
|
|
1559
|
+
const escalated = await Promise.race([
|
|
1560
|
+
Promise.all(exits).then(() => "exited"),
|
|
1561
|
+
afterDelay(killGraceMs)
|
|
1554
1562
|
]);
|
|
1563
|
+
return escalated === "exited";
|
|
1555
1564
|
}
|
|
1556
1565
|
|
|
1557
1566
|
// src/claude/claudeService.ts
|
|
@@ -2916,23 +2925,32 @@ var UNKNOWN_OWNER = {
|
|
|
2916
2925
|
command: "unknown",
|
|
2917
2926
|
token: ""
|
|
2918
2927
|
};
|
|
2928
|
+
function claimStaleLock(path, token) {
|
|
2929
|
+
try {
|
|
2930
|
+
renameSync(path, `${path}.stale.${token}`);
|
|
2931
|
+
return true;
|
|
2932
|
+
} catch (err) {
|
|
2933
|
+
if (err.code === "ENOENT") return false;
|
|
2934
|
+
throw err;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2919
2937
|
function reclaim(path, command, token) {
|
|
2920
|
-
|
|
2938
|
+
if (!claimStaleLock(path, token)) {
|
|
2939
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER };
|
|
2940
|
+
}
|
|
2941
|
+
const claimed = `${path}.stale.${token}`;
|
|
2921
2942
|
try {
|
|
2922
|
-
write(
|
|
2923
|
-
|
|
2943
|
+
write(path, command, token, "wx");
|
|
2944
|
+
return { ok: true, handle: makeHandle(path, token) };
|
|
2924
2945
|
} catch (err) {
|
|
2946
|
+
if (err.code !== "EEXIST") throw err;
|
|
2947
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER };
|
|
2948
|
+
} finally {
|
|
2925
2949
|
try {
|
|
2926
|
-
unlinkSync(
|
|
2950
|
+
unlinkSync(claimed);
|
|
2927
2951
|
} catch {
|
|
2928
2952
|
}
|
|
2929
|
-
throw err;
|
|
2930
2953
|
}
|
|
2931
|
-
const winner = readOwner(path);
|
|
2932
|
-
if (winner !== null && winner.token === token) {
|
|
2933
|
-
return { ok: true, handle: makeHandle(path, token) };
|
|
2934
|
-
}
|
|
2935
|
-
return { ok: false, heldBy: winner ?? UNKNOWN_OWNER };
|
|
2936
2954
|
}
|
|
2937
2955
|
|
|
2938
2956
|
// src/git/workspaceService.ts
|
|
@@ -3202,7 +3220,8 @@ function describePlan(decisions) {
|
|
|
3202
3220
|
});
|
|
3203
3221
|
return lines.length === 0 ? " (no issues matched the discovery filter)\n" : lines.join("\n") + "\n";
|
|
3204
3222
|
}
|
|
3205
|
-
function refreshItem(item, settings
|
|
3223
|
+
function refreshItem(item, settings) {
|
|
3224
|
+
const linkMap = getOpenPrLinkMap();
|
|
3206
3225
|
return decideWork(
|
|
3207
3226
|
buildIssueState(item.issue, linkMap),
|
|
3208
3227
|
settings.participants,
|
|
@@ -3299,26 +3318,32 @@ function repairIssueLink(item, baseBranch) {
|
|
|
3299
3318
|
`);
|
|
3300
3319
|
}
|
|
3301
3320
|
}
|
|
3302
|
-
async function processItem(planned, settings,
|
|
3303
|
-
const base = {
|
|
3304
|
-
issue: planned.issue.number,
|
|
3305
|
-
title: planned.issue.title,
|
|
3306
|
-
turn: planned.turn
|
|
3307
|
-
};
|
|
3321
|
+
async function processItem(planned, settings, silent) {
|
|
3308
3322
|
progress(`
|
|
3309
3323
|
#${String(planned.issue.number)} ${planned.turn}: ${planned.reason}
|
|
3310
3324
|
`);
|
|
3311
|
-
const refreshed = refreshItem(planned, settings
|
|
3325
|
+
const refreshed = refreshItem(planned, settings);
|
|
3312
3326
|
if (refreshed.kind === "skip") {
|
|
3313
3327
|
progress(` skipped: ${refreshed.detail}
|
|
3314
3328
|
`);
|
|
3315
|
-
return {
|
|
3329
|
+
return {
|
|
3330
|
+
issue: planned.issue.number,
|
|
3331
|
+
title: planned.issue.title,
|
|
3332
|
+
turn: planned.turn,
|
|
3333
|
+
outcome: "skipped",
|
|
3334
|
+
detail: `no longer actionable: ${refreshed.detail}`
|
|
3335
|
+
};
|
|
3316
3336
|
}
|
|
3317
3337
|
const item = refreshed.item;
|
|
3318
3338
|
if (item.turn !== planned.turn) {
|
|
3319
3339
|
progress(` turn changed to ${item.turn} since the plan was built; using the current state.
|
|
3320
3340
|
`);
|
|
3321
3341
|
}
|
|
3342
|
+
const base = {
|
|
3343
|
+
issue: item.issue.number,
|
|
3344
|
+
title: item.issue.title,
|
|
3345
|
+
turn: item.turn
|
|
3346
|
+
};
|
|
3322
3347
|
const prepared = item.turn === "issue-discuss" ? prepareBaseBranch(item.branch) : preparePrBranch(item.branch);
|
|
3323
3348
|
if (!prepared.ok) {
|
|
3324
3349
|
progress(` skipped: ${prepared.reason} \u2014 ${prepared.detail}
|
|
@@ -3397,8 +3422,14 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3397
3422
|
if (shuttingDown) return;
|
|
3398
3423
|
shuttingDown = true;
|
|
3399
3424
|
progress("\nInterrupted: stopping the executor before releasing the run lock\u2026\n");
|
|
3400
|
-
void terminateTrackedChildren().then(() => {
|
|
3401
|
-
|
|
3425
|
+
void terminateTrackedChildren().then((allExited) => {
|
|
3426
|
+
if (allExited) {
|
|
3427
|
+
handle.release();
|
|
3428
|
+
} else {
|
|
3429
|
+
progress(
|
|
3430
|
+
"Warning: could not confirm the executor exited; leaving the run lock in place. Check for a stray executor process before the next tick.\n"
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3402
3433
|
process.exit(130);
|
|
3403
3434
|
});
|
|
3404
3435
|
};
|
|
@@ -3434,11 +3465,17 @@ ${describePlan(decisions)}`;
|
|
|
3434
3465
|
reportDryRun(items, decisions, settings, options);
|
|
3435
3466
|
return 0;
|
|
3436
3467
|
}
|
|
3437
|
-
const runnable = settings.maxRuns > 0 ? items.slice(0, settings.maxRuns) : items;
|
|
3438
|
-
const deferred = items.slice(runnable.length);
|
|
3439
3468
|
const reports = [];
|
|
3440
|
-
|
|
3441
|
-
|
|
3469
|
+
const deferred = [];
|
|
3470
|
+
let runsUsed = 0;
|
|
3471
|
+
for (const item of items) {
|
|
3472
|
+
if (settings.maxRuns > 0 && runsUsed >= settings.maxRuns) {
|
|
3473
|
+
deferred.push(item);
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
const report = await processItem(item, settings, options.silent === true);
|
|
3477
|
+
if (report.outcome !== "skipped") runsUsed++;
|
|
3478
|
+
reports.push(report);
|
|
3442
3479
|
}
|
|
3443
3480
|
for (const item of deferred) {
|
|
3444
3481
|
progress(`
|