@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/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: "timeout waiting for response",
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: "Running", hint: "worker acting now" },
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 describeGoalState(status, phase) {
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.phase === "running").length || 0;
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(` 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");
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(() => runtime().phase === "running");
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, () => phaseLabel(runtime().phase).short.toUpperCase(), null);
2506
+ _$insert(_el$111, () => activity().short.toUpperCase(), null);
2462
2507
  _$effect((_$p) => _$setProp(_el$111, "style", {
2463
- fg: turnColor(),
2464
- bold: runtime().phase === "running"
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, () => phaseIcon(rt().phase), _el$165);
2631
- _$insert(_el$164, () => phaseLabel(rt().phase).short, _el$166);
2632
- _$insert(_el$164, () => phaseLabel(rt().phase).hint, null);
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()?.phase));
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$18 = _$memo(() => goal().workerTopology === "v2-native-child");
2953
- return () => _c$18() ? "native child" : goal().workerTopology === "v2-root-fallback" ? "root fallback" : "v1 child";
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$19 = _$memo(() => hiddenFinishedCommands() > 0);
3298
- return () => _c$19() ? ` \u2014 ${hiddenFinishedCommands()} finished hidden` : "";
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 &lt;command&gt;`));
3301
3350
  _$insertNode(_el$304, _el$305);
3302
3351
  _$insert(_el$304, () => hiddenFinishedCommands() > 0 ? ", or " : "; ", null);
3303
3352
  _$insert(_el$298, (() => {
3304
- var _c$20 = _$memo(() => hiddenFinishedCommands() > 0);
3305
- return () => _c$20() && (() => {
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$21 = _$memo(() => !!isActive());
3416
- return () => _c$21() ? `\u25B6 ${cmd.title}` : ` ${commandStatusIcon(cmd.status)} ${cmd.title}`;
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$22 = _$memo(() => cmd().args.length > 0);
3552
- return () => _c$22() && (() => {
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$23 = _$memo(() => !!cmd().goalID);
3567
- return () => _c$23() && [(() => {
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$24 = _$memo(() => !!cmd().truncated);
3589
- return () => _c$24() && (() => {
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$25 = _$memo(() => !!cmd().lastError);
3603
- return () => _c$25() && [(() => {
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.12.4",
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",