@bojackduy/opencode-loopd 1.12.4 → 1.13.1
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/CHANGELOG.md +17 -0
- package/dist/server.js +1203 -656
- package/dist/tui.js +81 -32
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -259,6 +259,9 @@ function controlDir(directory) {
|
|
|
259
259
|
function requestFile(directory, requestID) {
|
|
260
260
|
return path.join(controlDir(directory), "requests", `${requestID}.json`);
|
|
261
261
|
}
|
|
262
|
+
function processingFile(directory, requestID) {
|
|
263
|
+
return path.join(controlDir(directory), "processing", `${requestID}.json`);
|
|
264
|
+
}
|
|
262
265
|
function responseFile(directory, requestID) {
|
|
263
266
|
return path.join(controlDir(directory), "responses", `${requestID}.json`);
|
|
264
267
|
}
|
|
@@ -275,6 +278,14 @@ async function readControlResponse(directory, requestID) {
|
|
|
275
278
|
return;
|
|
276
279
|
}
|
|
277
280
|
}
|
|
281
|
+
async function statProcessingRequest(directory, requestID) {
|
|
282
|
+
try {
|
|
283
|
+
const st = await fs2.stat(processingFile(directory, requestID));
|
|
284
|
+
return { requestAgeMs: Math.max(0, Date.now() - st.mtimeMs) };
|
|
285
|
+
} catch {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
278
289
|
function commandLogFile(directory, commandID) {
|
|
279
290
|
return path.join(loopDir(directory), "commands", `${commandID}.log`);
|
|
280
291
|
}
|
|
@@ -356,10 +367,22 @@ function createControlClient(directory) {
|
|
|
356
367
|
return response;
|
|
357
368
|
await delay2(100);
|
|
358
369
|
}
|
|
370
|
+
const late = await readControlResponse(directory, request.requestID).catch(() => {
|
|
371
|
+
return;
|
|
372
|
+
});
|
|
373
|
+
if (late)
|
|
374
|
+
return late;
|
|
375
|
+
let stage = "not yet accepted by the server";
|
|
376
|
+
try {
|
|
377
|
+
const claimed = await statProcessingRequest(directory, request.requestID);
|
|
378
|
+
if (claimed) {
|
|
379
|
+
stage = `accepted by the server ${(claimed.requestAgeMs / 1000).toFixed(1)}s ago, still processing \u2014 ` + `the operation may complete later (late reply under this requestID); ` + `check the server log before retrying`;
|
|
380
|
+
}
|
|
381
|
+
} catch {}
|
|
359
382
|
return {
|
|
360
383
|
requestID: request.requestID,
|
|
361
384
|
ok: false,
|
|
362
|
-
message:
|
|
385
|
+
message: `timeout waiting for response (${stage})`,
|
|
363
386
|
errorCode: "timeout",
|
|
364
387
|
completedAt: new Date().toISOString()
|
|
365
388
|
};
|
|
@@ -619,7 +642,7 @@ var GOAL_STATUS_META = {
|
|
|
619
642
|
var PHASE_META = {
|
|
620
643
|
idle: { short: "Idle", hint: "between turns" },
|
|
621
644
|
queued: { short: "Queued", hint: "waiting to start" },
|
|
622
|
-
running: { short: "
|
|
645
|
+
running: { short: "Reserved", hint: "turn reserved; activity unconfirmed" },
|
|
623
646
|
compacting: { short: "Compacting", hint: "summarizing context" },
|
|
624
647
|
waiting_retry: { short: "Retrying", hint: "backing off" },
|
|
625
648
|
stopping: { short: "Stopping", hint: "abort in flight" }
|
|
@@ -630,9 +653,30 @@ function goalStatusLabel(status) {
|
|
|
630
653
|
function phaseLabel(phase) {
|
|
631
654
|
return PHASE_META[phase] ?? { short: phase, hint: "" };
|
|
632
655
|
}
|
|
633
|
-
function
|
|
656
|
+
function workerActivityLabel(runtime, now = Date.now()) {
|
|
657
|
+
if (runtime.phase !== "running")
|
|
658
|
+
return { ...phaseLabel(runtime.phase), acting: false };
|
|
659
|
+
const expires = runtime.leaseExpiresAt ? Date.parse(runtime.leaseExpiresAt) : NaN;
|
|
660
|
+
if (Number.isFinite(expires) && expires <= now)
|
|
661
|
+
return { short: "Stale", hint: "lease expired; activity unconfirmed (not proof of worker exit)", acting: false };
|
|
662
|
+
if (!Number.isFinite(expires) || (runtime.unknownStatusCount ?? 0) > 0)
|
|
663
|
+
return { short: "Unconfirmed", hint: "worker status unavailable; do not assume the prompt failed", acting: false };
|
|
664
|
+
if (runtime.activeAssistantCompletedAt)
|
|
665
|
+
return { short: "Finishing", hint: "response completed; waiting for turn reconciliation", acting: false };
|
|
666
|
+
if (runtime.activePromptObservedAt && runtime.lastWorkerStatus !== "idle" && runtime.lastWorkerStatus !== "retry" && (runtime.activeAssistantMessageID || runtime.activeToolCallIDs?.length)) {
|
|
667
|
+
return { short: "Running", hint: "correlated assistant or tool activity observed", acting: true };
|
|
668
|
+
}
|
|
669
|
+
if (runtime.activePromptObservedAt)
|
|
670
|
+
return { short: "Prompt seen", hint: "prompt observed; response activity not confirmed", acting: false };
|
|
671
|
+
if (runtime.activePromptAcceptedAt)
|
|
672
|
+
return { short: "Awaiting", hint: "host accepted prompt; transcript activity not confirmed", acting: false };
|
|
673
|
+
if (runtime.activePromptDispatchAt)
|
|
674
|
+
return { short: "Sending", hint: "dispatch requested; awaiting host acceptance", acting: false };
|
|
675
|
+
return { short: "Requested", hint: "run reserved; no dispatch evidence yet", acting: false };
|
|
676
|
+
}
|
|
677
|
+
function describeGoalState(status, phase, now = Date.now()) {
|
|
634
678
|
const goal = goalStatusLabel(status);
|
|
635
|
-
const activity = phase ? phaseLabel(phase) : undefined;
|
|
679
|
+
const activity = phase ? typeof phase === "string" ? phaseLabel(phase) : workerActivityLabel(phase, now) : undefined;
|
|
636
680
|
const workerClause = activity ? `worker is ${activity.hint || activity.short.toLowerCase()}` : "worker state unknown";
|
|
637
681
|
if (status === "active")
|
|
638
682
|
return `Loopd owns this; ${workerClause}.`;
|
|
@@ -1996,7 +2040,7 @@ function LoopDashboard(props) {
|
|
|
1996
2040
|
returnToNormalMode();
|
|
1997
2041
|
}
|
|
1998
2042
|
const activeGoals = () => state()?.goals.filter((g) => showCompleted() || g.status !== "complete") || [];
|
|
1999
|
-
const runningCount = () => state()?.runtimes.filter((runtime) => runtime
|
|
2043
|
+
const runningCount = () => state()?.runtimes.filter((runtime) => workerActivityLabel(runtime, clock()).acting).length || 0;
|
|
2000
2044
|
const runningFrame = () => ["|", "/", "-", "\\"][Math.floor(clock() / 500) % 4];
|
|
2001
2045
|
function handleBugReport() {
|
|
2002
2046
|
const goal = selectedGoal();
|
|
@@ -2017,7 +2061,7 @@ function LoopDashboard(props) {
|
|
|
2017
2061
|
return `loopd-goal-${goalID}`;
|
|
2018
2062
|
}
|
|
2019
2063
|
return (() => {
|
|
2020
|
-
var _el$4 = _$createElement("box"), _el$5 = _$createElement("box"), _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createElement("span"), _el$0 = _$createElement("span"), _el$10 = _$createElement("span"), _el$11 = _$createTextNode(` `), _el$12 = _$createTextNode(` `), _el$13 = _$createElement("span"), _el$15 = _$createElement("span"), _el$16 = _$createElement("span"), _el$18 = _$createElement("span"), _el$20 = _$createElement("span"), _el$21 = _$createTextNode(` `), _el$22 = _$createTextNode(`
|
|
2064
|
+
var _el$4 = _$createElement("box"), _el$5 = _$createElement("box"), _el$6 = _$createElement("box"), _el$7 = _$createElement("text"), _el$8 = _$createElement("span"), _el$0 = _$createElement("span"), _el$10 = _$createElement("span"), _el$11 = _$createTextNode(` `), _el$12 = _$createTextNode(` `), _el$13 = _$createElement("span"), _el$15 = _$createElement("span"), _el$16 = _$createElement("span"), _el$18 = _$createElement("span"), _el$20 = _$createElement("span"), _el$21 = _$createTextNode(` `), _el$22 = _$createTextNode(` observed active`), _el$23 = _$createElement("span"), _el$25 = _$createElement("span"), _el$26 = _$createElement("span"), _el$28 = _$createElement("span"), _el$30 = _$createElement("span"), _el$32 = _$createElement("span"), _el$34 = _$createElement("span"), _el$36 = _$createElement("span"), _el$38 = _$createElement("box"), _el$39 = _$createElement("text"), _el$40 = _$createElement("span"), _el$42 = _$createElement("box"), _el$56 = _$createElement("box"), _el$57 = _$createElement("text"), _el$58 = _$createElement("span"), _el$59 = _$createElement("input");
|
|
2021
2065
|
_$insertNode(_el$4, _el$5);
|
|
2022
2066
|
_$setProp(_el$4, "flexDirection", "column");
|
|
2023
2067
|
_$setProp(_el$4, "width", "100%");
|
|
@@ -2391,6 +2435,7 @@ function LoopDashboard(props) {
|
|
|
2391
2435
|
},
|
|
2392
2436
|
children: (goal, i) => {
|
|
2393
2437
|
const runtime = () => state()?.runtimes.find((r) => r.goalID === goal.id);
|
|
2438
|
+
const activity = () => runtime() ? workerActivityLabel(runtime(), clock()) : undefined;
|
|
2394
2439
|
const isActive = () => i() === selected();
|
|
2395
2440
|
const maxTurns = goal.config?.maxTurns;
|
|
2396
2441
|
const turnColor = () => {
|
|
@@ -2455,13 +2500,13 @@ function LoopDashboard(props) {
|
|
|
2455
2500
|
var _el$111 = _$createElement("span"), _el$112 = _$createTextNode(` `);
|
|
2456
2501
|
_$insertNode(_el$111, _el$112);
|
|
2457
2502
|
_$insert(_el$111, (() => {
|
|
2458
|
-
var _c$3 = _$memo(() =>
|
|
2459
|
-
return () => _c$3() ? runningFrame() : phaseIcon(runtime().phase);
|
|
2503
|
+
var _c$3 = _$memo(() => !!activity()?.acting);
|
|
2504
|
+
return () => _c$3() ? runningFrame() : _$memo(() => runtime().phase === "running")() ? "\u25F7" : phaseIcon(runtime().phase);
|
|
2460
2505
|
})(), _el$112);
|
|
2461
|
-
_$insert(_el$111, () =>
|
|
2506
|
+
_$insert(_el$111, () => activity().short.toUpperCase(), null);
|
|
2462
2507
|
_$effect((_$p) => _$setProp(_el$111, "style", {
|
|
2463
|
-
fg: turnColor(),
|
|
2464
|
-
bold:
|
|
2508
|
+
fg: activity()?.acting ? turnColor() : theme().textMuted,
|
|
2509
|
+
bold: activity()?.acting
|
|
2465
2510
|
}, _$p));
|
|
2466
2511
|
return _el$111;
|
|
2467
2512
|
})(), (() => {
|
|
@@ -2580,6 +2625,7 @@ function LoopDashboard(props) {
|
|
|
2580
2625
|
},
|
|
2581
2626
|
children: (goal) => {
|
|
2582
2627
|
const rt = () => state()?.runtimes.find((r) => r.goalID === goal().id);
|
|
2628
|
+
const activity = () => rt() ? workerActivityLabel(rt(), clock()) : undefined;
|
|
2583
2629
|
const lp = () => goal().lastProgress;
|
|
2584
2630
|
const blk = () => goal().blocker;
|
|
2585
2631
|
return (() => {
|
|
@@ -2627,11 +2673,14 @@ function LoopDashboard(props) {
|
|
|
2627
2673
|
var _el$164 = _$createElement("span"), _el$165 = _$createTextNode(` `), _el$166 = _$createTextNode(` \u2014 `);
|
|
2628
2674
|
_$insertNode(_el$164, _el$165);
|
|
2629
2675
|
_$insertNode(_el$164, _el$166);
|
|
2630
|
-
_$insert(_el$164, () =>
|
|
2631
|
-
|
|
2632
|
-
|
|
2676
|
+
_$insert(_el$164, (() => {
|
|
2677
|
+
var _c$18 = _$memo(() => !!activity()?.acting);
|
|
2678
|
+
return () => _c$18() ? runningFrame() : _$memo(() => rt().phase === "running")() ? "\u25F7" : phaseIcon(rt().phase);
|
|
2679
|
+
})(), _el$165);
|
|
2680
|
+
_$insert(_el$164, () => activity().short, _el$166);
|
|
2681
|
+
_$insert(_el$164, () => activity().hint, null);
|
|
2633
2682
|
_$effect((_$p) => _$setProp(_el$164, "style", {
|
|
2634
|
-
fg: phaseColor(rt().phase, theme()),
|
|
2683
|
+
fg: activity()?.acting ? phaseColor(rt().phase, theme()) : theme().textMuted,
|
|
2635
2684
|
bold: true
|
|
2636
2685
|
}, _$p));
|
|
2637
2686
|
return _el$164;
|
|
@@ -2648,7 +2697,7 @@ function LoopDashboard(props) {
|
|
|
2648
2697
|
return _el$167;
|
|
2649
2698
|
})()];
|
|
2650
2699
|
})(), _el$149);
|
|
2651
|
-
_$insert(_el$150, () => describeGoalState(goal().status, rt()
|
|
2700
|
+
_$insert(_el$150, () => describeGoalState(goal().status, rt(), clock()));
|
|
2652
2701
|
_$insert(_el$142, (() => {
|
|
2653
2702
|
var _c$5 = _$memo(() => !!rt()?.workerAbortedAt);
|
|
2654
2703
|
return () => _c$5() && [(() => {
|
|
@@ -2949,8 +2998,8 @@ function LoopDashboard(props) {
|
|
|
2949
2998
|
})(), (() => {
|
|
2950
2999
|
var _el$237 = _$createElement("span");
|
|
2951
3000
|
_$insert(_el$237, (() => {
|
|
2952
|
-
var _c$
|
|
2953
|
-
return () => _c$
|
|
3001
|
+
var _c$19 = _$memo(() => goal().workerTopology === "v2-native-child");
|
|
3002
|
+
return () => _c$19() ? "native child" : goal().workerTopology === "v2-root-fallback" ? "root fallback" : "v1 child";
|
|
2954
3003
|
})());
|
|
2955
3004
|
_$effect((_$p) => _$setProp(_el$237, "style", {
|
|
2956
3005
|
fg: theme().text
|
|
@@ -3294,15 +3343,15 @@ Write scope: `);
|
|
|
3294
3343
|
_$insertNode(_el$299, _el$300);
|
|
3295
3344
|
_$insertNode(_el$299, _el$301);
|
|
3296
3345
|
_$insert(_el$299, (() => {
|
|
3297
|
-
var _c$
|
|
3298
|
-
return () => _c$
|
|
3346
|
+
var _c$20 = _$memo(() => hiddenFinishedCommands() > 0);
|
|
3347
|
+
return () => _c$20() ? ` \u2014 ${hiddenFinishedCommands()} finished hidden` : "";
|
|
3299
3348
|
})(), _el$301);
|
|
3300
3349
|
_$insertNode(_el$302, _$createTextNode(`:new <command>`));
|
|
3301
3350
|
_$insertNode(_el$304, _el$305);
|
|
3302
3351
|
_$insert(_el$304, () => hiddenFinishedCommands() > 0 ? ", or " : "; ", null);
|
|
3303
3352
|
_$insert(_el$298, (() => {
|
|
3304
|
-
var _c$
|
|
3305
|
-
return () => _c$
|
|
3353
|
+
var _c$21 = _$memo(() => hiddenFinishedCommands() > 0);
|
|
3354
|
+
return () => _c$21() && (() => {
|
|
3306
3355
|
var _el$321 = _$createElement("span"), _el$322 = _$createElement("span"), _el$324 = _$createElement("span");
|
|
3307
3356
|
_$insertNode(_el$321, _el$322);
|
|
3308
3357
|
_$insertNode(_el$321, _el$324);
|
|
@@ -3412,8 +3461,8 @@ Write scope: `);
|
|
|
3412
3461
|
return [(() => {
|
|
3413
3462
|
var _el$326 = _$createElement("span");
|
|
3414
3463
|
_$insert(_el$326, (() => {
|
|
3415
|
-
var _c$
|
|
3416
|
-
return () => _c$
|
|
3464
|
+
var _c$22 = _$memo(() => !!isActive());
|
|
3465
|
+
return () => _c$22() ? `\u25B6 ${cmd.title}` : ` ${commandStatusIcon(cmd.status)} ${cmd.title}`;
|
|
3417
3466
|
})());
|
|
3418
3467
|
_$effect((_$p) => _$setProp(_el$326, "style", {
|
|
3419
3468
|
fg: commandStatusColor(cmd.status, theme()),
|
|
@@ -3548,8 +3597,8 @@ Write scope: `);
|
|
|
3548
3597
|
_$insertNode(_el$354, _$createTextNode(`\u2B22 Spawn: `));
|
|
3549
3598
|
_$insert(_el$356, () => cmd().command);
|
|
3550
3599
|
_$insert(_el$346, (() => {
|
|
3551
|
-
var _c$
|
|
3552
|
-
return () => _c$
|
|
3600
|
+
var _c$23 = _$memo(() => cmd().args.length > 0);
|
|
3601
|
+
return () => _c$23() && (() => {
|
|
3553
3602
|
var _el$375 = _$createElement("span"), _el$376 = _$createTextNode(` `);
|
|
3554
3603
|
_$insertNode(_el$375, _el$376);
|
|
3555
3604
|
_$insert(_el$375, () => cmd().args.join(" "), null);
|
|
@@ -3563,8 +3612,8 @@ Write scope: `);
|
|
|
3563
3612
|
_$insertNode(_el$360, _$createTextNode(`Cwd: `));
|
|
3564
3613
|
_$insert(_el$362, () => cmd().cwd.replace(String(props.directory), "."));
|
|
3565
3614
|
_$insert(_el$346, (() => {
|
|
3566
|
-
var _c$
|
|
3567
|
-
return () => _c$
|
|
3615
|
+
var _c$24 = _$memo(() => !!cmd().goalID);
|
|
3616
|
+
return () => _c$24() && [(() => {
|
|
3568
3617
|
var _el$377 = _$createElement("span");
|
|
3569
3618
|
_$insertNode(_el$377, _$createTextNode(` \u2502 \uD83D\uDD17 linked goal `));
|
|
3570
3619
|
_$effect((_$p) => _$setProp(_el$377, "style", {
|
|
@@ -3585,8 +3634,8 @@ Write scope: `);
|
|
|
3585
3634
|
_$insertNode(_el$368, _el$369);
|
|
3586
3635
|
_$insert(_el$368, () => cmd().outputBytes, _el$369);
|
|
3587
3636
|
_$insert(_el$346, (() => {
|
|
3588
|
-
var _c$
|
|
3589
|
-
return () => _c$
|
|
3637
|
+
var _c$25 = _$memo(() => !!cmd().truncated);
|
|
3638
|
+
return () => _c$25() && (() => {
|
|
3590
3639
|
var _el$380 = _$createElement("span");
|
|
3591
3640
|
_$insertNode(_el$380, _$createTextNode(` \xB7 \u26A0 truncated`));
|
|
3592
3641
|
_$effect((_$p) => _$setProp(_el$380, "style", {
|
|
@@ -3599,8 +3648,8 @@ Write scope: `);
|
|
|
3599
3648
|
_$insertNode(_el$370, _el$371);
|
|
3600
3649
|
_$insert(_el$370, () => ageLabel(cmd().updatedAt, clock()), null);
|
|
3601
3650
|
_$insert(_el$346, (() => {
|
|
3602
|
-
var _c$
|
|
3603
|
-
return () => _c$
|
|
3651
|
+
var _c$26 = _$memo(() => !!cmd().lastError);
|
|
3652
|
+
return () => _c$26() && [(() => {
|
|
3604
3653
|
var _el$382 = _$createElement("span"), _el$383 = _$createTextNode(`
|
|
3605
3654
|
\u26A0 Error: `);
|
|
3606
3655
|
_$insertNode(_el$382, _el$383);
|
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.
|
|
4
|
+
"version": "1.13.1",
|
|
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-or-later",
|