@bojackduy/opencode-loopd 1.13.0 → 1.13.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/CHANGELOG.md +37 -0
- package/dist/server.js +797 -461
- package/dist/tui.js +117 -58
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -106,11 +106,11 @@ async function readState(directory) {
|
|
|
106
106
|
return emptyState();
|
|
107
107
|
const transient = error instanceof SyntaxError || error?.code === "EPERM" || error?.code === "EACCES" || error?.code === "EBUSY";
|
|
108
108
|
if (!transient || attempt === attempts - 1)
|
|
109
|
-
|
|
109
|
+
throw error;
|
|
110
110
|
await delay(25 * (attempt + 1));
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
throw new Error("State could not be read.");
|
|
114
114
|
}
|
|
115
115
|
function migrate(state) {
|
|
116
116
|
if (state.version === CURRENT_VERSION)
|
|
@@ -359,33 +359,66 @@ function createControlClient(directory) {
|
|
|
359
359
|
args: command.args,
|
|
360
360
|
requestedAt: new Date().toISOString()
|
|
361
361
|
};
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
362
|
+
let submitted = false;
|
|
363
|
+
let expired = false;
|
|
364
|
+
let timeoutReply;
|
|
365
|
+
const deadline = performance.now() + timeoutMs;
|
|
366
|
+
function timedOut() {
|
|
367
|
+
return timeoutReply ??= (async () => {
|
|
368
|
+
let probeTimer;
|
|
369
|
+
let claimed;
|
|
370
|
+
try {
|
|
371
|
+
claimed = await Promise.race([
|
|
372
|
+
statProcessingRequest(directory, request.requestID),
|
|
373
|
+
new Promise((resolve) => {
|
|
374
|
+
probeTimer = setTimeout(() => resolve(undefined), 100);
|
|
375
|
+
})
|
|
376
|
+
]);
|
|
377
|
+
} catch {} finally {
|
|
378
|
+
if (probeTimer)
|
|
379
|
+
clearTimeout(probeTimer);
|
|
380
|
+
}
|
|
381
|
+
const stage = claimed ? `accepted by the server ${(claimed.requestAgeMs / 1000).toFixed(1)}s ago, still processing` : submitted ? "request submitted; server acceptance or response could not be confirmed" : "request submission is still pending";
|
|
382
|
+
return {
|
|
383
|
+
requestID: request.requestID,
|
|
384
|
+
ok: false,
|
|
385
|
+
errorCode: "timeout",
|
|
386
|
+
message: `timeout waiting for response (${stage}; the operation may complete later under this requestID \u2014 check the server log before retrying)`,
|
|
387
|
+
completedAt: new Date().toISOString()
|
|
388
|
+
};
|
|
389
|
+
})();
|
|
390
|
+
}
|
|
391
|
+
async function exchange() {
|
|
392
|
+
await writeControlRequest(directory, request);
|
|
393
|
+
submitted = true;
|
|
394
|
+
while (!expired && performance.now() < deadline) {
|
|
395
|
+
const response = await readControlResponse(directory, request.requestID);
|
|
396
|
+
if (response)
|
|
397
|
+
return response;
|
|
398
|
+
if (!expired)
|
|
399
|
+
await delay2(Math.min(100, Math.max(0, deadline - performance.now())));
|
|
400
|
+
}
|
|
401
|
+
if (!expired) {
|
|
402
|
+
const late = await readControlResponse(directory, request.requestID);
|
|
403
|
+
if (late)
|
|
404
|
+
return late;
|
|
405
|
+
}
|
|
406
|
+
return timedOut();
|
|
407
|
+
}
|
|
408
|
+
let timer;
|
|
409
|
+
const timeout = new Promise((resolve) => {
|
|
410
|
+
timer = setTimeout(() => {
|
|
411
|
+
expired = true;
|
|
412
|
+
timedOut().then(resolve);
|
|
413
|
+
}, timeoutMs);
|
|
372
414
|
});
|
|
373
|
-
if (late)
|
|
374
|
-
return late;
|
|
375
|
-
let stage = "not yet accepted by the server";
|
|
376
415
|
try {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
requestID: request.requestID,
|
|
384
|
-
ok: false,
|
|
385
|
-
message: `timeout waiting for response (${stage})`,
|
|
386
|
-
errorCode: "timeout",
|
|
387
|
-
completedAt: new Date().toISOString()
|
|
388
|
-
};
|
|
416
|
+
return await Promise.race([exchange(), timeout]);
|
|
417
|
+
} finally {
|
|
418
|
+
expired = true;
|
|
419
|
+
if (timer)
|
|
420
|
+
clearTimeout(timer);
|
|
421
|
+
}
|
|
389
422
|
}
|
|
390
423
|
async function getState() {
|
|
391
424
|
return readState(directory);
|
|
@@ -642,7 +675,7 @@ var GOAL_STATUS_META = {
|
|
|
642
675
|
var PHASE_META = {
|
|
643
676
|
idle: { short: "Idle", hint: "between turns" },
|
|
644
677
|
queued: { short: "Queued", hint: "waiting to start" },
|
|
645
|
-
running: { short: "
|
|
678
|
+
running: { short: "Reserved", hint: "turn reserved; activity unconfirmed" },
|
|
646
679
|
compacting: { short: "Compacting", hint: "summarizing context" },
|
|
647
680
|
waiting_retry: { short: "Retrying", hint: "backing off" },
|
|
648
681
|
stopping: { short: "Stopping", hint: "abort in flight" }
|
|
@@ -653,9 +686,30 @@ function goalStatusLabel(status) {
|
|
|
653
686
|
function phaseLabel(phase) {
|
|
654
687
|
return PHASE_META[phase] ?? { short: phase, hint: "" };
|
|
655
688
|
}
|
|
656
|
-
function
|
|
689
|
+
function workerActivityLabel(runtime, now = Date.now()) {
|
|
690
|
+
if (runtime.phase !== "running")
|
|
691
|
+
return { ...phaseLabel(runtime.phase), acting: false };
|
|
692
|
+
const expires = runtime.leaseExpiresAt ? Date.parse(runtime.leaseExpiresAt) : NaN;
|
|
693
|
+
if (Number.isFinite(expires) && expires <= now)
|
|
694
|
+
return { short: "Stale", hint: "lease expired; activity unconfirmed (not proof of worker exit)", acting: false };
|
|
695
|
+
if (!Number.isFinite(expires) || (runtime.unknownStatusCount ?? 0) > 0)
|
|
696
|
+
return { short: "Unconfirmed", hint: "worker status unavailable; do not assume the prompt failed", acting: false };
|
|
697
|
+
if (runtime.activeAssistantCompletedAt)
|
|
698
|
+
return { short: "Finishing", hint: "response completed; waiting for turn reconciliation", acting: false };
|
|
699
|
+
if (runtime.activePromptObservedAt && runtime.lastWorkerStatus !== "idle" && runtime.lastWorkerStatus !== "retry" && (runtime.activeAssistantMessageID || runtime.activeToolCallIDs?.length)) {
|
|
700
|
+
return { short: "Running", hint: "correlated assistant or tool activity observed", acting: true };
|
|
701
|
+
}
|
|
702
|
+
if (runtime.activePromptObservedAt)
|
|
703
|
+
return { short: "Prompt seen", hint: "prompt observed; response activity not confirmed", acting: false };
|
|
704
|
+
if (runtime.activePromptAcceptedAt)
|
|
705
|
+
return { short: "Awaiting", hint: "host accepted prompt; transcript activity not confirmed", acting: false };
|
|
706
|
+
if (runtime.activePromptDispatchAt)
|
|
707
|
+
return { short: "Sending", hint: "dispatch requested; awaiting host acceptance", acting: false };
|
|
708
|
+
return { short: "Requested", hint: "run reserved; no dispatch evidence yet", acting: false };
|
|
709
|
+
}
|
|
710
|
+
function describeGoalState(status, phase, now = Date.now()) {
|
|
657
711
|
const goal = goalStatusLabel(status);
|
|
658
|
-
const activity = phase ? phaseLabel(phase) : undefined;
|
|
712
|
+
const activity = phase ? typeof phase === "string" ? phaseLabel(phase) : workerActivityLabel(phase, now) : undefined;
|
|
659
713
|
const workerClause = activity ? `worker is ${activity.hint || activity.short.toLowerCase()}` : "worker state unknown";
|
|
660
714
|
if (status === "active")
|
|
661
715
|
return `Loopd owns this; ${workerClause}.`;
|
|
@@ -2019,7 +2073,7 @@ function LoopDashboard(props) {
|
|
|
2019
2073
|
returnToNormalMode();
|
|
2020
2074
|
}
|
|
2021
2075
|
const activeGoals = () => state()?.goals.filter((g) => showCompleted() || g.status !== "complete") || [];
|
|
2022
|
-
const runningCount = () => state()?.runtimes.filter((runtime) => runtime
|
|
2076
|
+
const runningCount = () => state()?.runtimes.filter((runtime) => workerActivityLabel(runtime, clock()).acting).length || 0;
|
|
2023
2077
|
const runningFrame = () => ["|", "/", "-", "\\"][Math.floor(clock() / 500) % 4];
|
|
2024
2078
|
function handleBugReport() {
|
|
2025
2079
|
const goal = selectedGoal();
|
|
@@ -2040,7 +2094,7 @@ function LoopDashboard(props) {
|
|
|
2040
2094
|
return `loopd-goal-${goalID}`;
|
|
2041
2095
|
}
|
|
2042
2096
|
return (() => {
|
|
2043
|
-
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(`
|
|
2097
|
+
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");
|
|
2044
2098
|
_$insertNode(_el$4, _el$5);
|
|
2045
2099
|
_$setProp(_el$4, "flexDirection", "column");
|
|
2046
2100
|
_$setProp(_el$4, "width", "100%");
|
|
@@ -2414,6 +2468,7 @@ function LoopDashboard(props) {
|
|
|
2414
2468
|
},
|
|
2415
2469
|
children: (goal, i) => {
|
|
2416
2470
|
const runtime = () => state()?.runtimes.find((r) => r.goalID === goal.id);
|
|
2471
|
+
const activity = () => runtime() ? workerActivityLabel(runtime(), clock()) : undefined;
|
|
2417
2472
|
const isActive = () => i() === selected();
|
|
2418
2473
|
const maxTurns = goal.config?.maxTurns;
|
|
2419
2474
|
const turnColor = () => {
|
|
@@ -2478,13 +2533,13 @@ function LoopDashboard(props) {
|
|
|
2478
2533
|
var _el$111 = _$createElement("span"), _el$112 = _$createTextNode(` `);
|
|
2479
2534
|
_$insertNode(_el$111, _el$112);
|
|
2480
2535
|
_$insert(_el$111, (() => {
|
|
2481
|
-
var _c$3 = _$memo(() =>
|
|
2482
|
-
return () => _c$3() ? runningFrame() : phaseIcon(runtime().phase);
|
|
2536
|
+
var _c$3 = _$memo(() => !!activity()?.acting);
|
|
2537
|
+
return () => _c$3() ? runningFrame() : _$memo(() => runtime().phase === "running")() ? "\u25F7" : phaseIcon(runtime().phase);
|
|
2483
2538
|
})(), _el$112);
|
|
2484
|
-
_$insert(_el$111, () =>
|
|
2539
|
+
_$insert(_el$111, () => activity().short.toUpperCase(), null);
|
|
2485
2540
|
_$effect((_$p) => _$setProp(_el$111, "style", {
|
|
2486
|
-
fg: turnColor(),
|
|
2487
|
-
bold:
|
|
2541
|
+
fg: activity()?.acting ? turnColor() : theme().textMuted,
|
|
2542
|
+
bold: activity()?.acting
|
|
2488
2543
|
}, _$p));
|
|
2489
2544
|
return _el$111;
|
|
2490
2545
|
})(), (() => {
|
|
@@ -2603,6 +2658,7 @@ function LoopDashboard(props) {
|
|
|
2603
2658
|
},
|
|
2604
2659
|
children: (goal) => {
|
|
2605
2660
|
const rt = () => state()?.runtimes.find((r) => r.goalID === goal().id);
|
|
2661
|
+
const activity = () => rt() ? workerActivityLabel(rt(), clock()) : undefined;
|
|
2606
2662
|
const lp = () => goal().lastProgress;
|
|
2607
2663
|
const blk = () => goal().blocker;
|
|
2608
2664
|
return (() => {
|
|
@@ -2650,11 +2706,14 @@ function LoopDashboard(props) {
|
|
|
2650
2706
|
var _el$164 = _$createElement("span"), _el$165 = _$createTextNode(` `), _el$166 = _$createTextNode(` \u2014 `);
|
|
2651
2707
|
_$insertNode(_el$164, _el$165);
|
|
2652
2708
|
_$insertNode(_el$164, _el$166);
|
|
2653
|
-
_$insert(_el$164, () =>
|
|
2654
|
-
|
|
2655
|
-
|
|
2709
|
+
_$insert(_el$164, (() => {
|
|
2710
|
+
var _c$18 = _$memo(() => !!activity()?.acting);
|
|
2711
|
+
return () => _c$18() ? runningFrame() : _$memo(() => rt().phase === "running")() ? "\u25F7" : phaseIcon(rt().phase);
|
|
2712
|
+
})(), _el$165);
|
|
2713
|
+
_$insert(_el$164, () => activity().short, _el$166);
|
|
2714
|
+
_$insert(_el$164, () => activity().hint, null);
|
|
2656
2715
|
_$effect((_$p) => _$setProp(_el$164, "style", {
|
|
2657
|
-
fg: phaseColor(rt().phase, theme()),
|
|
2716
|
+
fg: activity()?.acting ? phaseColor(rt().phase, theme()) : theme().textMuted,
|
|
2658
2717
|
bold: true
|
|
2659
2718
|
}, _$p));
|
|
2660
2719
|
return _el$164;
|
|
@@ -2671,7 +2730,7 @@ function LoopDashboard(props) {
|
|
|
2671
2730
|
return _el$167;
|
|
2672
2731
|
})()];
|
|
2673
2732
|
})(), _el$149);
|
|
2674
|
-
_$insert(_el$150, () => describeGoalState(goal().status, rt()
|
|
2733
|
+
_$insert(_el$150, () => describeGoalState(goal().status, rt(), clock()));
|
|
2675
2734
|
_$insert(_el$142, (() => {
|
|
2676
2735
|
var _c$5 = _$memo(() => !!rt()?.workerAbortedAt);
|
|
2677
2736
|
return () => _c$5() && [(() => {
|
|
@@ -2972,8 +3031,8 @@ function LoopDashboard(props) {
|
|
|
2972
3031
|
})(), (() => {
|
|
2973
3032
|
var _el$237 = _$createElement("span");
|
|
2974
3033
|
_$insert(_el$237, (() => {
|
|
2975
|
-
var _c$
|
|
2976
|
-
return () => _c$
|
|
3034
|
+
var _c$19 = _$memo(() => goal().workerTopology === "v2-native-child");
|
|
3035
|
+
return () => _c$19() ? "native child" : goal().workerTopology === "v2-root-fallback" ? "root fallback" : "v1 child";
|
|
2977
3036
|
})());
|
|
2978
3037
|
_$effect((_$p) => _$setProp(_el$237, "style", {
|
|
2979
3038
|
fg: theme().text
|
|
@@ -3317,15 +3376,15 @@ Write scope: `);
|
|
|
3317
3376
|
_$insertNode(_el$299, _el$300);
|
|
3318
3377
|
_$insertNode(_el$299, _el$301);
|
|
3319
3378
|
_$insert(_el$299, (() => {
|
|
3320
|
-
var _c$
|
|
3321
|
-
return () => _c$
|
|
3379
|
+
var _c$20 = _$memo(() => hiddenFinishedCommands() > 0);
|
|
3380
|
+
return () => _c$20() ? ` \u2014 ${hiddenFinishedCommands()} finished hidden` : "";
|
|
3322
3381
|
})(), _el$301);
|
|
3323
3382
|
_$insertNode(_el$302, _$createTextNode(`:new <command>`));
|
|
3324
3383
|
_$insertNode(_el$304, _el$305);
|
|
3325
3384
|
_$insert(_el$304, () => hiddenFinishedCommands() > 0 ? ", or " : "; ", null);
|
|
3326
3385
|
_$insert(_el$298, (() => {
|
|
3327
|
-
var _c$
|
|
3328
|
-
return () => _c$
|
|
3386
|
+
var _c$21 = _$memo(() => hiddenFinishedCommands() > 0);
|
|
3387
|
+
return () => _c$21() && (() => {
|
|
3329
3388
|
var _el$321 = _$createElement("span"), _el$322 = _$createElement("span"), _el$324 = _$createElement("span");
|
|
3330
3389
|
_$insertNode(_el$321, _el$322);
|
|
3331
3390
|
_$insertNode(_el$321, _el$324);
|
|
@@ -3435,8 +3494,8 @@ Write scope: `);
|
|
|
3435
3494
|
return [(() => {
|
|
3436
3495
|
var _el$326 = _$createElement("span");
|
|
3437
3496
|
_$insert(_el$326, (() => {
|
|
3438
|
-
var _c$
|
|
3439
|
-
return () => _c$
|
|
3497
|
+
var _c$22 = _$memo(() => !!isActive());
|
|
3498
|
+
return () => _c$22() ? `\u25B6 ${cmd.title}` : ` ${commandStatusIcon(cmd.status)} ${cmd.title}`;
|
|
3440
3499
|
})());
|
|
3441
3500
|
_$effect((_$p) => _$setProp(_el$326, "style", {
|
|
3442
3501
|
fg: commandStatusColor(cmd.status, theme()),
|
|
@@ -3571,8 +3630,8 @@ Write scope: `);
|
|
|
3571
3630
|
_$insertNode(_el$354, _$createTextNode(`\u2B22 Spawn: `));
|
|
3572
3631
|
_$insert(_el$356, () => cmd().command);
|
|
3573
3632
|
_$insert(_el$346, (() => {
|
|
3574
|
-
var _c$
|
|
3575
|
-
return () => _c$
|
|
3633
|
+
var _c$23 = _$memo(() => cmd().args.length > 0);
|
|
3634
|
+
return () => _c$23() && (() => {
|
|
3576
3635
|
var _el$375 = _$createElement("span"), _el$376 = _$createTextNode(` `);
|
|
3577
3636
|
_$insertNode(_el$375, _el$376);
|
|
3578
3637
|
_$insert(_el$375, () => cmd().args.join(" "), null);
|
|
@@ -3586,8 +3645,8 @@ Write scope: `);
|
|
|
3586
3645
|
_$insertNode(_el$360, _$createTextNode(`Cwd: `));
|
|
3587
3646
|
_$insert(_el$362, () => cmd().cwd.replace(String(props.directory), "."));
|
|
3588
3647
|
_$insert(_el$346, (() => {
|
|
3589
|
-
var _c$
|
|
3590
|
-
return () => _c$
|
|
3648
|
+
var _c$24 = _$memo(() => !!cmd().goalID);
|
|
3649
|
+
return () => _c$24() && [(() => {
|
|
3591
3650
|
var _el$377 = _$createElement("span");
|
|
3592
3651
|
_$insertNode(_el$377, _$createTextNode(` \u2502 \uD83D\uDD17 linked goal `));
|
|
3593
3652
|
_$effect((_$p) => _$setProp(_el$377, "style", {
|
|
@@ -3608,8 +3667,8 @@ Write scope: `);
|
|
|
3608
3667
|
_$insertNode(_el$368, _el$369);
|
|
3609
3668
|
_$insert(_el$368, () => cmd().outputBytes, _el$369);
|
|
3610
3669
|
_$insert(_el$346, (() => {
|
|
3611
|
-
var _c$
|
|
3612
|
-
return () => _c$
|
|
3670
|
+
var _c$25 = _$memo(() => !!cmd().truncated);
|
|
3671
|
+
return () => _c$25() && (() => {
|
|
3613
3672
|
var _el$380 = _$createElement("span");
|
|
3614
3673
|
_$insertNode(_el$380, _$createTextNode(` \xB7 \u26A0 truncated`));
|
|
3615
3674
|
_$effect((_$p) => _$setProp(_el$380, "style", {
|
|
@@ -3622,8 +3681,8 @@ Write scope: `);
|
|
|
3622
3681
|
_$insertNode(_el$370, _el$371);
|
|
3623
3682
|
_$insert(_el$370, () => ageLabel(cmd().updatedAt, clock()), null);
|
|
3624
3683
|
_$insert(_el$346, (() => {
|
|
3625
|
-
var _c$
|
|
3626
|
-
return () => _c$
|
|
3684
|
+
var _c$26 = _$memo(() => !!cmd().lastError);
|
|
3685
|
+
return () => _c$26() && [(() => {
|
|
3627
3686
|
var _el$382 = _$createElement("span"), _el$383 = _$createTextNode(`
|
|
3628
3687
|
\u26A0 Error: `);
|
|
3629
3688
|
_$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.13.
|
|
4
|
+
"version": "1.13.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-or-later",
|