@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/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
- break;
109
+ throw error;
110
110
  await delay(25 * (attempt + 1));
111
111
  }
112
112
  }
113
- return emptyState();
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
- await writeControlRequest(directory, request);
363
- const deadline = Date.now() + timeoutMs;
364
- while (Date.now() < deadline) {
365
- const response = await readControlResponse(directory, request.requestID);
366
- if (response)
367
- return response;
368
- await delay2(100);
369
- }
370
- const late = await readControlResponse(directory, request.requestID).catch(() => {
371
- return;
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
- 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 {}
382
- return {
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: "Running", hint: "worker acting now" },
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 describeGoalState(status, phase) {
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.phase === "running").length || 0;
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(` acting now`), _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");
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(() => runtime().phase === "running");
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, () => phaseLabel(runtime().phase).short.toUpperCase(), null);
2539
+ _$insert(_el$111, () => activity().short.toUpperCase(), null);
2485
2540
  _$effect((_$p) => _$setProp(_el$111, "style", {
2486
- fg: turnColor(),
2487
- bold: runtime().phase === "running"
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, () => phaseIcon(rt().phase), _el$165);
2654
- _$insert(_el$164, () => phaseLabel(rt().phase).short, _el$166);
2655
- _$insert(_el$164, () => phaseLabel(rt().phase).hint, null);
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()?.phase));
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$18 = _$memo(() => goal().workerTopology === "v2-native-child");
2976
- return () => _c$18() ? "native child" : goal().workerTopology === "v2-root-fallback" ? "root fallback" : "v1 child";
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$19 = _$memo(() => hiddenFinishedCommands() > 0);
3321
- return () => _c$19() ? ` \u2014 ${hiddenFinishedCommands()} finished hidden` : "";
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 &lt;command&gt;`));
3324
3383
  _$insertNode(_el$304, _el$305);
3325
3384
  _$insert(_el$304, () => hiddenFinishedCommands() > 0 ? ", or " : "; ", null);
3326
3385
  _$insert(_el$298, (() => {
3327
- var _c$20 = _$memo(() => hiddenFinishedCommands() > 0);
3328
- return () => _c$20() && (() => {
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$21 = _$memo(() => !!isActive());
3439
- return () => _c$21() ? `\u25B6 ${cmd.title}` : ` ${commandStatusIcon(cmd.status)} ${cmd.title}`;
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$22 = _$memo(() => cmd().args.length > 0);
3575
- return () => _c$22() && (() => {
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$23 = _$memo(() => !!cmd().goalID);
3590
- return () => _c$23() && [(() => {
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$24 = _$memo(() => !!cmd().truncated);
3612
- return () => _c$24() && (() => {
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$25 = _$memo(() => !!cmd().lastError);
3626
- return () => _c$25() && [(() => {
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.0",
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",