@bojackduy/opencode-loopd 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +25 -4
- package/dist/tui.js +47 -47
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -425,6 +425,9 @@ function describeError(value) {
|
|
|
425
425
|
return value.message;
|
|
426
426
|
if (typeof value === "string")
|
|
427
427
|
return value;
|
|
428
|
+
if (typeof value === "object" && value !== null && "message" in value) {
|
|
429
|
+
return String(value.message);
|
|
430
|
+
}
|
|
428
431
|
try {
|
|
429
432
|
return JSON.stringify(value, errorReplacer);
|
|
430
433
|
} catch {
|
|
@@ -776,7 +779,6 @@ function createGoal(input) {
|
|
|
776
779
|
const now = new Date().toISOString();
|
|
777
780
|
return { ...input, tokensUsed: 0, timeUsedSeconds: 0, createdAt: now, updatedAt: now };
|
|
778
781
|
}
|
|
779
|
-
|
|
780
782
|
// src/application/loop-engine.ts
|
|
781
783
|
var HANDLED_EVENT_TYPES = new Set([
|
|
782
784
|
"session.idle",
|
|
@@ -989,7 +991,7 @@ function createLoopEngine(options) {
|
|
|
989
991
|
if (!runtime)
|
|
990
992
|
return false;
|
|
991
993
|
const error = event.properties?.error;
|
|
992
|
-
const message = error
|
|
994
|
+
const message = describeError(error) || "unknown error";
|
|
993
995
|
runtime.consecutiveFailures += 1;
|
|
994
996
|
runtime.lastError = message;
|
|
995
997
|
runtime.updatedAt = new Date().toISOString();
|
|
@@ -1252,6 +1254,9 @@ function buildContinuationSteering(goal, runtime, context) {
|
|
|
1252
1254
|
}
|
|
1253
1255
|
if (v.artifactSummary)
|
|
1254
1256
|
parts.push(`- artifacts: ${v.artifactSummary}`);
|
|
1257
|
+
if (v.evaluatorRejectionCount && v.evaluatorRejectionCount > 0) {
|
|
1258
|
+
parts.push(`- evaluator rejected ${v.evaluatorRejectionCount} time(s): previous completion claim had weak evidence \u2014 fix the issues and call complete_goal again with stronger evidence`);
|
|
1259
|
+
}
|
|
1255
1260
|
}
|
|
1256
1261
|
parts.push(``, `## COMPLETION AUDIT \u2014 you ARE the evaluator`, `Before deciding the goal is achieved, treat completion as unproven:`, `1. Derive concrete requirements from the objective and any referenced files/plans/specs/issues. Preserve original scope; do not redefine success.`, `2. For _every_ explicit requirement, numbered item, named artifact, command, test, gate, invariant, deliverable \u2192 identify authoritative evidence: files, command output, test results, PR state, rendered artifacts, runtime behavior.`, `3. Judge each per-requirement: proves | contradicts | incomplete | too weak/indirect | missing \u2014 matching scope narrowly (narrow check \u2260 broad claim).`, `4. Treat tests/manifests/verifiers as evidence only after confirming they cover the relevant requirement. Treat uncertain/indirect as NOT achieved.`, `5. Only call complete_goal when _every_ requirement's current-state evidence proves it and no required work remains. If any requirement is missing/incomplete/weak \u2192 keep working, do not call complete_goal.`);
|
|
1257
1262
|
if (context?.forceFinish) {
|
|
@@ -1437,6 +1442,9 @@ function createGoalService(host) {
|
|
|
1437
1442
|
const c = `checks configured: ${goal.config.checks.length} \u2014 run them before claiming completion`;
|
|
1438
1443
|
verification = { ...verification || {}, failedChecks: [c], checksPassed: undefined };
|
|
1439
1444
|
}
|
|
1445
|
+
if (runtime.evaluatorRejectionCount && runtime.evaluatorRejectionCount > 0) {
|
|
1446
|
+
verification = { ...verification || {}, evaluatorRejectionCount: runtime.evaluatorRejectionCount };
|
|
1447
|
+
}
|
|
1440
1448
|
} catch {}
|
|
1441
1449
|
const context = {
|
|
1442
1450
|
inboxMessages: inboxMessages.length > 0 ? inboxMessages : undefined,
|
|
@@ -1943,12 +1951,25 @@ function goalTools(dir, goalService, hostSessionID) {
|
|
|
1943
1951
|
if (goal.config.checks?.length) {
|
|
1944
1952
|
const checkResults = await runCompletionChecks(goal.config.checks);
|
|
1945
1953
|
if (!checkResults.passed) {
|
|
1954
|
+
const runtime2 = state.runtimes.find((r) => r.goalID === goal.id);
|
|
1955
|
+
if (runtime2) {
|
|
1956
|
+
runtime2.evaluatorRejectionCount = (runtime2.evaluatorRejectionCount || 0) + 1;
|
|
1957
|
+
if (runtime2.evaluatorRejectionCount >= 3) {
|
|
1958
|
+
runtime2.forceFinishRequested = true;
|
|
1959
|
+
} else {
|
|
1960
|
+
runtime2.forceFinishRequested = false;
|
|
1961
|
+
runtime2.turnCount = Math.max(0, runtime2.turnCount - 1);
|
|
1962
|
+
}
|
|
1963
|
+
runtime2.updatedAt = new Date().toISOString();
|
|
1964
|
+
await writeState(dir, state);
|
|
1965
|
+
}
|
|
1946
1966
|
return {
|
|
1947
|
-
title: "
|
|
1967
|
+
title: "Completion rejected \u2014 keep working",
|
|
1948
1968
|
output: JSON.stringify({
|
|
1949
1969
|
passed: false,
|
|
1950
1970
|
failedChecks: checkResults.failures,
|
|
1951
|
-
message: "
|
|
1971
|
+
message: "Evaluator rejected completion. Fix the issues above and try again.",
|
|
1972
|
+
rejectionCount: runtime2?.evaluatorRejectionCount || 0
|
|
1952
1973
|
})
|
|
1953
1974
|
};
|
|
1954
1975
|
}
|
package/dist/tui.js
CHANGED
|
@@ -249,7 +249,7 @@ function tokenize(input) {
|
|
|
249
249
|
function commandHelp() {
|
|
250
250
|
return [
|
|
251
251
|
"Modes: : insert \u2192 send/commands | Ctrl+N \u2192 normal | ? toggle help | Shift+B bug report",
|
|
252
|
-
"Nav: j/k move | g/G top/bottom | o open child | p/r/R/x pause/resume/retry/clear | L logs | q close",
|
|
252
|
+
"Nav: j/k move | g/G top/bottom | o open child | c toggle done | p/r/R/x pause/resume/retry/clear | L logs | q close",
|
|
253
253
|
"Commands (insert mode, : prefix):",
|
|
254
254
|
" :send <message> Send instruction to selected goal",
|
|
255
255
|
" :open Open child session (same as o)",
|
|
@@ -464,12 +464,13 @@ function LoopDashboard(props) {
|
|
|
464
464
|
const [mode, setMode] = createSignal("normal");
|
|
465
465
|
const [selected, setSelected] = createSignal(0);
|
|
466
466
|
const [commandInput, setCommandInput] = createSignal("");
|
|
467
|
-
const [statusText, setStatusText] = createSignal("Press : to send/command, ? help, o open, q close");
|
|
467
|
+
const [statusText, setStatusText] = createSignal("Press : to send/command, ? help, c toggle done, o open, q close");
|
|
468
468
|
const [state, setState] = createSignal(null);
|
|
469
469
|
const [events, setEvents] = createSignal([]);
|
|
470
470
|
const [selectedGoal, setSelectedGoal] = createSignal(null);
|
|
471
471
|
const [showLogs, setShowLogs] = createSignal(false);
|
|
472
472
|
const [showHelp, setShowHelp] = createSignal(false);
|
|
473
|
+
const [showCompleted, setShowCompleted] = createSignal(false);
|
|
473
474
|
const [clock, setClock] = createSignal(Date.now());
|
|
474
475
|
let inputEl;
|
|
475
476
|
let focusTimer;
|
|
@@ -489,7 +490,7 @@ function LoopDashboard(props) {
|
|
|
489
490
|
try {
|
|
490
491
|
const s = await client.getState();
|
|
491
492
|
setState(s);
|
|
492
|
-
const goals2 = s.goals.filter((g) => g.status !== "complete");
|
|
493
|
+
const goals2 = s.goals.filter((g) => showCompleted() || g.status !== "complete");
|
|
493
494
|
if (goals2.length > 0 && selected() >= goals2.length)
|
|
494
495
|
setSelected(goals2.length - 1);
|
|
495
496
|
setSelectedGoal(goals2[selected()] || null);
|
|
@@ -568,7 +569,13 @@ function LoopDashboard(props) {
|
|
|
568
569
|
return;
|
|
569
570
|
}
|
|
570
571
|
const key = raw || seq || name;
|
|
571
|
-
|
|
572
|
+
if (key === "c") {
|
|
573
|
+
prevent(evt);
|
|
574
|
+
setShowCompleted((v) => !v);
|
|
575
|
+
debugLog("toggle completed");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const currentGoals = state()?.goals.filter((goal) => showCompleted() || goal.status !== "complete") || [];
|
|
572
579
|
if (name === "down" || key === "j") {
|
|
573
580
|
prevent(evt);
|
|
574
581
|
setSelected((index) => Math.min(currentGoals.length - 1, index + 1));
|
|
@@ -843,7 +850,7 @@ function LoopDashboard(props) {
|
|
|
843
850
|
}
|
|
844
851
|
returnToNormalMode();
|
|
845
852
|
}
|
|
846
|
-
const activeGoals = () => state()?.goals.filter((g) => g.status !== "complete") || [];
|
|
853
|
+
const activeGoals = () => state()?.goals.filter((g) => showCompleted() || g.status !== "complete") || [];
|
|
847
854
|
const runningCount = () => state()?.runtimes.filter((runtime) => runtime.phase === "running").length || 0;
|
|
848
855
|
const runningFrame = () => ["|", "/", "-", "\\"][Math.floor(clock() / 500) % 4];
|
|
849
856
|
function handleBugReport() {
|
|
@@ -948,7 +955,7 @@ function LoopDashboard(props) {
|
|
|
948
955
|
_$setProp(_el$30, "maxHeight", 14);
|
|
949
956
|
_$setProp(_el$30, "overflow", "hidden");
|
|
950
957
|
_$insertNode(_el$31, _el$32);
|
|
951
|
-
_$insertNode(_el$32, _$createTextNode(`\u2501\u2501\u2501 Keys: ? toggle : insert Ctrl+N normal o open
|
|
958
|
+
_$insertNode(_el$32, _$createTextNode(`\u2501\u2501\u2501 Keys: ? toggle help c toggle done : insert Ctrl+N normal o open Bug report q close \u2501\u2501\u2501`));
|
|
952
959
|
_$setProp(_el$32, "style", {
|
|
953
960
|
fg: "yellow",
|
|
954
961
|
bold: true
|
|
@@ -1319,6 +1326,8 @@ function LoopDashboard(props) {
|
|
|
1319
1326
|
},
|
|
1320
1327
|
children: (goal) => {
|
|
1321
1328
|
const rt = () => state()?.runtimes.find((r) => r.goalID === goal().id);
|
|
1329
|
+
const lp = () => goal().lastProgress;
|
|
1330
|
+
const blk = () => goal().blocker;
|
|
1322
1331
|
return (() => {
|
|
1323
1332
|
var _el$108 = _$createElement("box"), _el$109 = _$createElement("text"), _el$110 = _$createElement("span"), _el$111 = _$createTextNode(` `), _el$112 = _$createElement("span"), _el$113 = _$createTextNode(` `), _el$114 = _$createTextNode(`
|
|
1324
1333
|
`), _el$115 = _$createElement("span");
|
|
@@ -1368,7 +1377,7 @@ function LoopDashboard(props) {
|
|
|
1368
1377
|
})(), _el$114);
|
|
1369
1378
|
_$insert(_el$115, () => goal().objective.slice(0, 160));
|
|
1370
1379
|
_$insert(_el$109, (() => {
|
|
1371
|
-
var _c$8 = _$memo(() => !!
|
|
1380
|
+
var _c$8 = _$memo(() => !!lp());
|
|
1372
1381
|
return () => _c$8() && [(() => {
|
|
1373
1382
|
var _el$122 = _$createElement("span"), _el$123 = _$createTextNode(`
|
|
1374
1383
|
\u2714 `);
|
|
@@ -1379,7 +1388,7 @@ function LoopDashboard(props) {
|
|
|
1379
1388
|
return _el$122;
|
|
1380
1389
|
})(), (() => {
|
|
1381
1390
|
var _el$125 = _$createElement("span");
|
|
1382
|
-
_$insert(_el$125, () =>
|
|
1391
|
+
_$insert(_el$125, () => lp().summary.slice(0, 100));
|
|
1383
1392
|
_$effect((_$p) => _$setProp(_el$125, "style", {
|
|
1384
1393
|
fg: theme().text
|
|
1385
1394
|
}, _$p));
|
|
@@ -1387,7 +1396,7 @@ function LoopDashboard(props) {
|
|
|
1387
1396
|
})(), (() => {
|
|
1388
1397
|
var _el$126 = _$createElement("span"), _el$127 = _$createTextNode(` \u2192 `);
|
|
1389
1398
|
_$insertNode(_el$126, _el$127);
|
|
1390
|
-
_$insert(_el$126, () =>
|
|
1399
|
+
_$insert(_el$126, () => lp().next?.slice(0, 60) || "", null);
|
|
1391
1400
|
_$effect((_$p) => _$setProp(_el$126, "style", {
|
|
1392
1401
|
fg: theme().textMuted
|
|
1393
1402
|
}, _$p));
|
|
@@ -1395,7 +1404,7 @@ function LoopDashboard(props) {
|
|
|
1395
1404
|
})()];
|
|
1396
1405
|
})(), null);
|
|
1397
1406
|
_$insert(_el$109, (() => {
|
|
1398
|
-
var _c$9 = _$memo(() => !!
|
|
1407
|
+
var _c$9 = _$memo(() => !!blk());
|
|
1399
1408
|
return () => _c$9() && [(() => {
|
|
1400
1409
|
var _el$128 = _$createElement("span"), _el$129 = _$createTextNode(`
|
|
1401
1410
|
\u2716 blocked: `);
|
|
@@ -1407,7 +1416,7 @@ function LoopDashboard(props) {
|
|
|
1407
1416
|
return _el$128;
|
|
1408
1417
|
})(), (() => {
|
|
1409
1418
|
var _el$131 = _$createElement("span");
|
|
1410
|
-
_$insert(_el$131, () =>
|
|
1419
|
+
_$insert(_el$131, () => blk().reason.slice(0, 140));
|
|
1411
1420
|
_$effect((_$p) => _$setProp(_el$131, "style", {
|
|
1412
1421
|
fg: theme().error
|
|
1413
1422
|
}, _$p));
|
|
@@ -1415,7 +1424,7 @@ function LoopDashboard(props) {
|
|
|
1415
1424
|
})(), (() => {
|
|
1416
1425
|
var _el$132 = _$createElement("span"), _el$133 = _$createTextNode(` \u2014 `);
|
|
1417
1426
|
_$insertNode(_el$132, _el$133);
|
|
1418
|
-
_$insert(_el$132, () =>
|
|
1427
|
+
_$insert(_el$132, () => blk().needed.slice(0, 60), null);
|
|
1419
1428
|
_$effect((_$p) => _$setProp(_el$132, "style", {
|
|
1420
1429
|
fg: theme().textMuted
|
|
1421
1430
|
}, _$p));
|
|
@@ -1520,45 +1529,36 @@ function LoopDashboard(props) {
|
|
|
1520
1529
|
_$insertNode(_el$36, _el$39);
|
|
1521
1530
|
_$insertNode(_el$37, _$createTextNode(`\u25C8 Recent Events`));
|
|
1522
1531
|
_$insertNode(_el$39, _$createTextNode(` \u2014 :logs to hide`));
|
|
1523
|
-
_$insert(_el$
|
|
1532
|
+
_$insert(_el$36, _$createComponent(For, {
|
|
1524
1533
|
get each() {
|
|
1525
1534
|
return events().slice(-10);
|
|
1526
1535
|
},
|
|
1527
|
-
children: (ev) =>
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
_$
|
|
1531
|
-
_$
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
_$
|
|
1535
|
-
var _c$11 = _$memo(() => !!ev.summary);
|
|
1536
|
-
return () => _c$11() && (() => {
|
|
1537
|
-
var _el$150 = _$createElement("span"), _el$151 = _$createTextNode(` \u2014 `);
|
|
1538
|
-
_$insertNode(_el$150, _el$151);
|
|
1539
|
-
_$insert(_el$150, () => String(ev.summary).slice(0, 60), null);
|
|
1540
|
-
_$effect((_$p) => _$setProp(_el$150, "style", {
|
|
1541
|
-
fg: theme().text
|
|
1542
|
-
}, _$p));
|
|
1543
|
-
return _el$150;
|
|
1544
|
-
})();
|
|
1545
|
-
})(), null);
|
|
1546
|
-
_$effect((_p$) => {
|
|
1547
|
-
var _v$39 = {
|
|
1548
|
-
fg: eventColor(String(ev.type), theme()),
|
|
1549
|
-
bold: true
|
|
1550
|
-
}, _v$40 = {
|
|
1551
|
-
fg: theme().textMuted
|
|
1552
|
-
};
|
|
1553
|
-
_v$39 !== _p$.e && (_p$.e = _$setProp(_el$147, "style", _v$39, _p$.e));
|
|
1554
|
-
_v$40 !== _p$.t && (_p$.t = _$setProp(_el$148, "style", _v$40, _p$.t));
|
|
1555
|
-
return _p$;
|
|
1556
|
-
}, {
|
|
1557
|
-
e: undefined,
|
|
1558
|
-
t: undefined
|
|
1559
|
-
});
|
|
1536
|
+
children: (ev) => [`
|
|
1537
|
+
`, (() => {
|
|
1538
|
+
var _el$146 = _$createElement("span");
|
|
1539
|
+
_$insert(_el$146, () => String(ev.type));
|
|
1540
|
+
_$effect((_$p) => _$setProp(_el$146, "style", {
|
|
1541
|
+
fg: eventColor(String(ev.type), theme()),
|
|
1542
|
+
bold: true
|
|
1543
|
+
}, _$p));
|
|
1560
1544
|
return _el$146;
|
|
1561
|
-
})()
|
|
1545
|
+
})(), (() => {
|
|
1546
|
+
var _el$147 = _$createElement("span"), _el$148 = _$createTextNode(` `);
|
|
1547
|
+
_$insertNode(_el$147, _el$148);
|
|
1548
|
+
_$insert(_el$147, () => ev.goalID?.slice(0, 8), null);
|
|
1549
|
+
_$effect((_$p) => _$setProp(_el$147, "style", {
|
|
1550
|
+
fg: theme().textMuted
|
|
1551
|
+
}, _$p));
|
|
1552
|
+
return _el$147;
|
|
1553
|
+
})(), _$memo(() => _$memo(() => !!ev.summary)() && (() => {
|
|
1554
|
+
var _el$149 = _$createElement("span"), _el$150 = _$createTextNode(` \u2014 `);
|
|
1555
|
+
_$insertNode(_el$149, _el$150);
|
|
1556
|
+
_$insert(_el$149, () => String(ev.summary).slice(0, 60), null);
|
|
1557
|
+
_$effect((_$p) => _$setProp(_el$149, "style", {
|
|
1558
|
+
fg: theme().text
|
|
1559
|
+
}, _$p));
|
|
1560
|
+
return _el$149;
|
|
1561
|
+
})())]
|
|
1562
1562
|
}), null);
|
|
1563
1563
|
_$effect((_p$) => {
|
|
1564
1564
|
var _v$ = theme().border, _v$2 = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@bojackduy/opencode-loopd",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.2",
|
|
5
5
|
"description": "Codex-inspired background goal engine for OpenCode — autonomous subagents, engine-driven loop, child worker sessions and modal TUI dashboard. Like Claude Code loop for OpenCode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "AGPL-3.0-only",
|