@useorgx/openclaw-plugin 0.7.23 → 0.7.25

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.
Files changed (125) hide show
  1. package/dashboard/dist/assets/{m2smti3F.js → B6VftyY6.js} +1 -1
  2. package/dashboard/dist/assets/B6VftyY6.js.br +0 -0
  3. package/dashboard/dist/assets/B6VftyY6.js.gz +0 -0
  4. package/dashboard/dist/assets/{D-FuHfT8.js → BANQdlC4.js} +1 -1
  5. package/dashboard/dist/assets/BANQdlC4.js.br +0 -0
  6. package/dashboard/dist/assets/BANQdlC4.js.gz +0 -0
  7. package/dashboard/dist/assets/{DDCPrZRt.js → BPL4CL3c.js} +1 -1
  8. package/dashboard/dist/assets/BPL4CL3c.js.br +0 -0
  9. package/dashboard/dist/assets/BPL4CL3c.js.gz +0 -0
  10. package/dashboard/dist/assets/{D0PN5_vY.js → BZCkOZ20.js} +1 -1
  11. package/dashboard/dist/assets/BZCkOZ20.js.br +0 -0
  12. package/dashboard/dist/assets/BZCkOZ20.js.gz +0 -0
  13. package/dashboard/dist/assets/{DNQ-iFO2.js → B_LdOJUa.js} +1 -1
  14. package/dashboard/dist/assets/B_LdOJUa.js.br +0 -0
  15. package/dashboard/dist/assets/B_LdOJUa.js.gz +0 -0
  16. package/dashboard/dist/assets/Bfp-wdwb.css +1 -0
  17. package/dashboard/dist/assets/Bfp-wdwb.css.br +0 -0
  18. package/dashboard/dist/assets/{C1u2SGin.css.gz → Bfp-wdwb.css.gz} +0 -0
  19. package/dashboard/dist/assets/{CZXS5i_5.js → BvFcH_Iy.js} +1 -1
  20. package/dashboard/dist/assets/BvFcH_Iy.js.br +0 -0
  21. package/dashboard/dist/assets/BvFcH_Iy.js.gz +0 -0
  22. package/dashboard/dist/assets/C0i7ABUU.js +212 -0
  23. package/dashboard/dist/assets/C0i7ABUU.js.br +0 -0
  24. package/dashboard/dist/assets/C0i7ABUU.js.gz +0 -0
  25. package/dashboard/dist/assets/CFB0MM7j.js +1 -0
  26. package/dashboard/dist/assets/CFB0MM7j.js.br +0 -0
  27. package/dashboard/dist/assets/CFB0MM7j.js.gz +0 -0
  28. package/dashboard/dist/assets/{OlLPtzdz.js → CQSRb1yu.js} +1 -1
  29. package/dashboard/dist/assets/CQSRb1yu.js.br +0 -0
  30. package/dashboard/dist/assets/CQSRb1yu.js.gz +0 -0
  31. package/dashboard/dist/assets/{CbVWL74-.js → CUoQoSm-.js} +1 -1
  32. package/dashboard/dist/assets/CUoQoSm-.js.br +0 -0
  33. package/dashboard/dist/assets/CUoQoSm-.js.gz +0 -0
  34. package/dashboard/dist/assets/Ckd1R1iE.js +1 -0
  35. package/dashboard/dist/assets/Ckd1R1iE.js.br +0 -0
  36. package/dashboard/dist/assets/Ckd1R1iE.js.gz +0 -0
  37. package/dashboard/dist/assets/{DhPuHPK7.js → CqRNb2EL.js} +1 -1
  38. package/dashboard/dist/assets/CqRNb2EL.js.br +0 -0
  39. package/dashboard/dist/assets/CqRNb2EL.js.gz +0 -0
  40. package/dashboard/dist/assets/{CGJiHCIx.js → DClUc9rw.js} +1 -1
  41. package/dashboard/dist/assets/DClUc9rw.js.br +0 -0
  42. package/dashboard/dist/assets/DClUc9rw.js.gz +0 -0
  43. package/dashboard/dist/assets/DF2PMTwT.js +1 -0
  44. package/dashboard/dist/assets/DF2PMTwT.js.br +0 -0
  45. package/dashboard/dist/assets/DF2PMTwT.js.gz +0 -0
  46. package/dashboard/dist/assets/{RN4M9u9W.js → DJYl7gyA.js} +1 -1
  47. package/dashboard/dist/assets/DJYl7gyA.js.br +0 -0
  48. package/dashboard/dist/assets/DJYl7gyA.js.gz +0 -0
  49. package/dashboard/dist/assets/{BrMXbzQ-.js → DZtNMX0t.js} +1 -1
  50. package/dashboard/dist/assets/DZtNMX0t.js.br +0 -0
  51. package/dashboard/dist/assets/DZtNMX0t.js.gz +0 -0
  52. package/dashboard/dist/assets/{LOFrVoPD.js → DlEa8PI0.js} +1 -1
  53. package/dashboard/dist/assets/DlEa8PI0.js.br +0 -0
  54. package/dashboard/dist/assets/DlEa8PI0.js.gz +0 -0
  55. package/dashboard/dist/assets/M4QxcXjh.js +1 -0
  56. package/dashboard/dist/assets/M4QxcXjh.js.br +0 -0
  57. package/dashboard/dist/assets/M4QxcXjh.js.gz +0 -0
  58. package/dashboard/dist/assets/{nra1yvJX.js → MrW1ixGx.js} +1 -1
  59. package/dashboard/dist/assets/MrW1ixGx.js.br +0 -0
  60. package/dashboard/dist/assets/MrW1ixGx.js.gz +0 -0
  61. package/dashboard/dist/index.html +2 -2
  62. package/dashboard/dist/index.html.br +0 -0
  63. package/dashboard/dist/index.html.gz +0 -0
  64. package/dist/activity-store.js +68 -8
  65. package/dist/contracts/shared-types.d.ts +28 -0
  66. package/dist/http/helpers/auto-continue-engine.js +235 -32
  67. package/dist/http/helpers/triage-mapper.js +285 -6
  68. package/dist/http/helpers/value-utils.d.ts +1 -0
  69. package/dist/http/helpers/value-utils.js +17 -0
  70. package/dist/http/index.js +89 -3
  71. package/dist/http/routes/live-triage.js +6 -1
  72. package/dist/http/routes/mission-control-actions.d.ts +9 -0
  73. package/dist/http/routes/mission-control-actions.js +157 -7
  74. package/dist/http/routes/mission-control-read.d.ts +9 -0
  75. package/dist/http/routes/mission-control-read.js +33 -0
  76. package/dist/openclaw.plugin.json +1 -1
  77. package/dist/stores/sqlite-state.d.ts +1 -1
  78. package/dist/stores/sqlite-state.js +153 -2
  79. package/openclaw.plugin.json +1 -1
  80. package/package.json +1 -1
  81. package/dashboard/dist/assets/9gFmK3Kr.js +0 -1
  82. package/dashboard/dist/assets/9gFmK3Kr.js.br +0 -0
  83. package/dashboard/dist/assets/9gFmK3Kr.js.gz +0 -0
  84. package/dashboard/dist/assets/BrMXbzQ-.js.br +0 -0
  85. package/dashboard/dist/assets/BrMXbzQ-.js.gz +0 -0
  86. package/dashboard/dist/assets/C1u2SGin.css +0 -1
  87. package/dashboard/dist/assets/C1u2SGin.css.br +0 -0
  88. package/dashboard/dist/assets/CGJiHCIx.js.br +0 -0
  89. package/dashboard/dist/assets/CGJiHCIx.js.gz +0 -0
  90. package/dashboard/dist/assets/CSd4rSuU.js +0 -212
  91. package/dashboard/dist/assets/CSd4rSuU.js.br +0 -0
  92. package/dashboard/dist/assets/CSd4rSuU.js.gz +0 -0
  93. package/dashboard/dist/assets/CZXS5i_5.js.br +0 -0
  94. package/dashboard/dist/assets/CZXS5i_5.js.gz +0 -0
  95. package/dashboard/dist/assets/CbVWL74-.js.br +0 -0
  96. package/dashboard/dist/assets/CbVWL74-.js.gz +0 -0
  97. package/dashboard/dist/assets/D-FuHfT8.js.br +0 -0
  98. package/dashboard/dist/assets/D-FuHfT8.js.gz +0 -0
  99. package/dashboard/dist/assets/D0PN5_vY.js.br +0 -0
  100. package/dashboard/dist/assets/D0PN5_vY.js.gz +0 -0
  101. package/dashboard/dist/assets/DDCPrZRt.js.br +0 -0
  102. package/dashboard/dist/assets/DDCPrZRt.js.gz +0 -0
  103. package/dashboard/dist/assets/DNQ-iFO2.js.br +0 -0
  104. package/dashboard/dist/assets/DNQ-iFO2.js.gz +0 -0
  105. package/dashboard/dist/assets/DhPuHPK7.js.br +0 -0
  106. package/dashboard/dist/assets/DhPuHPK7.js.gz +0 -0
  107. package/dashboard/dist/assets/Dhz7qPtn.js +0 -1
  108. package/dashboard/dist/assets/Dhz7qPtn.js.br +0 -0
  109. package/dashboard/dist/assets/Dhz7qPtn.js.gz +0 -0
  110. package/dashboard/dist/assets/LOFrVoPD.js.br +0 -0
  111. package/dashboard/dist/assets/LOFrVoPD.js.gz +0 -0
  112. package/dashboard/dist/assets/OlLPtzdz.js.br +0 -0
  113. package/dashboard/dist/assets/OlLPtzdz.js.gz +0 -0
  114. package/dashboard/dist/assets/RN4M9u9W.js.br +0 -0
  115. package/dashboard/dist/assets/RN4M9u9W.js.gz +0 -0
  116. package/dashboard/dist/assets/VCHu272d.js +0 -1
  117. package/dashboard/dist/assets/VCHu272d.js.br +0 -0
  118. package/dashboard/dist/assets/VCHu272d.js.gz +0 -0
  119. package/dashboard/dist/assets/m2smti3F.js.br +0 -0
  120. package/dashboard/dist/assets/m2smti3F.js.gz +0 -0
  121. package/dashboard/dist/assets/nra1yvJX.js.br +0 -0
  122. package/dashboard/dist/assets/nra1yvJX.js.gz +0 -0
  123. package/dashboard/dist/assets/qLX6NZ-J.js +0 -1
  124. package/dashboard/dist/assets/qLX6NZ-J.js.br +0 -0
  125. package/dashboard/dist/assets/qLX6NZ-J.js.gz +0 -0
@@ -9,6 +9,33 @@ const PLAY_QUEUE_LOOKUP_TIMEOUT_MS = (() => {
9
9
  return 350;
10
10
  return Math.max(200, Math.floor(parsed));
11
11
  })();
12
+ function playOutcomeMessage(input) {
13
+ const workstreamLabel = input.workstreamTitle?.trim() || "this workstream";
14
+ switch (input.outcome) {
15
+ case "dispatch_pending":
16
+ return `Dispatching ${workstreamLabel}; waiting for slice start.`;
17
+ case "started":
18
+ return `Started ${workstreamLabel}.`;
19
+ case "fallback_started":
20
+ return `Started ${workstreamLabel} using the fallback runner.`;
21
+ case "slice_completed":
22
+ return `${workstreamLabel} completed before the queue refreshed.`;
23
+ case "slice_blocked":
24
+ return input.startReasonLabel?.trim() || `${workstreamLabel} needs input before it can continue.`;
25
+ case "slice_error":
26
+ return `${workstreamLabel} hit an error before it could keep running.`;
27
+ case "initiative_run_active":
28
+ return "Autopilot is already running for this initiative.";
29
+ case "no_dispatchable_task":
30
+ return input.startReasonLabel?.trim() || `No dispatchable task is available for ${workstreamLabel}.`;
31
+ case "spawn_guard_rate_limited":
32
+ return input.fallbackBlockedReason?.trim() || `${workstreamLabel} is rate limited right now.`;
33
+ case "spawn_guard_blocked":
34
+ return input.fallbackBlockedReason?.trim() || `${workstreamLabel} is blocked by the current execution policy.`;
35
+ default:
36
+ return input.startReasonLabel?.trim() || `Unable to dispatch ${workstreamLabel} right now.`;
37
+ }
38
+ }
12
39
  const IN_PROGRESS_TASK_STATUSES = new Set([
13
40
  "in_progress",
14
41
  "inprogress",
@@ -412,6 +439,10 @@ export function registerMissionControlActionsRoutes(router, deps) {
412
439
  query.get("projectId") ??
413
440
  query.get("project_id") ??
414
441
  null;
442
+ const queueWorkstreamTitle = matchedQueueItem?.workstreamTitle ?? null;
443
+ const queueStartReasonCode = matchedQueueItem?.startReasonCode ?? null;
444
+ const queueStartReasonLabel = matchedQueueItem?.startReasonLabel ?? null;
445
+ const queueDispatchableTask = matchedQueueItem?.dispatchableTask ?? null;
415
446
  const existingRun = deps.autoContinueRuns.get(initiativeId) ?? null;
416
447
  const existingActiveRunIds = Array.isArray(existingRun?.activeSliceRunIds)
417
448
  ? (existingRun?.activeSliceRunIds)
@@ -428,6 +459,13 @@ export function registerMissionControlActionsRoutes(router, deps) {
428
459
  const activeWorkstreamTitle = activeSlice?.workstreamTitle ?? null;
429
460
  deps.sendJson(res, 409, {
430
461
  ok: false,
462
+ outcome: "initiative_run_active",
463
+ reasonCode: "initiative_run_active",
464
+ message: playOutcomeMessage({
465
+ outcome: "initiative_run_active",
466
+ workstreamTitle: queueWorkstreamTitle,
467
+ startReasonLabel: queueStartReasonLabel,
468
+ }),
431
469
  code: "auto_continue_already_running",
432
470
  error: activeWorkstreamId || activeWorkstreamTitle
433
471
  ? `Auto-continue is already running for ${activeWorkstreamTitle ?? activeWorkstreamId}. Stop it before launching another Play run.`
@@ -436,10 +474,35 @@ export function registerMissionControlActionsRoutes(router, deps) {
436
474
  activeRunIds: existingActiveRunIds,
437
475
  activeWorkstreamId,
438
476
  activeWorkstreamTitle,
477
+ dispatchableTask: queueDispatchableTask,
439
478
  error_location: "mission-control.next-up.play.concurrent_run",
440
479
  });
441
480
  return;
442
481
  }
482
+ if (matchedQueueItem?.canStartNow === false) {
483
+ const reasonCode = queueStartReasonCode && queueStartReasonCode.trim().length > 0
484
+ ? queueStartReasonCode.trim()
485
+ : "no_dispatchable_task";
486
+ const message = playOutcomeMessage({
487
+ outcome: reasonCode,
488
+ workstreamTitle: queueWorkstreamTitle,
489
+ startReasonLabel: queueStartReasonLabel,
490
+ });
491
+ deps.sendJson(res, 409, {
492
+ ok: false,
493
+ outcome: reasonCode,
494
+ reasonCode,
495
+ message,
496
+ code: reasonCode,
497
+ error: message,
498
+ initiativeId,
499
+ workstreamId,
500
+ agentId,
501
+ dispatchableTask: queueDispatchableTask,
502
+ error_location: "mission-control.next-up.play.preflight",
503
+ });
504
+ return;
505
+ }
443
506
  const run = await deps.startAutoContinueRun({
444
507
  initiativeId,
445
508
  workspaceId: requestedWorkspaceId,
@@ -491,7 +554,7 @@ export function registerMissionControlActionsRoutes(router, deps) {
491
554
  // Give short-lived workers a brief window to flush output so Play can resolve
492
555
  // in one request/response cycle without requiring extra manual ticks.
493
556
  if (run.activeRunId) {
494
- await new Promise((resolve) => setTimeout(resolve, 140));
557
+ await new Promise((resolve) => setTimeout(resolve, 500));
495
558
  await deps.tickAutoContinueRun(run);
496
559
  }
497
560
  fallbackDispatch = await maybeDispatchFallback();
@@ -522,12 +585,20 @@ export function registerMissionControlActionsRoutes(router, deps) {
522
585
  });
523
586
  deps.sendJson(res, 202, {
524
587
  ok: true,
588
+ outcome: "dispatch_pending",
589
+ reasonCode: "dispatch_pending",
590
+ message: playOutcomeMessage({
591
+ outcome: "dispatch_pending",
592
+ workstreamTitle: queueWorkstreamTitle,
593
+ startReasonLabel: queueStartReasonLabel,
594
+ }),
525
595
  run,
526
596
  initiativeId,
527
597
  workstreamId,
528
598
  agentId,
529
599
  ...playDispatchEnvelope("pending"),
530
600
  sessionId: null,
601
+ dispatchableTask: queueDispatchableTask,
531
602
  slice: {
532
603
  scope,
533
604
  taskIds: matchedQueueItem?.sliceTaskIds ?? [],
@@ -566,12 +637,20 @@ export function registerMissionControlActionsRoutes(router, deps) {
566
637
  : "slice_error";
567
638
  deps.sendJson(res, 200, {
568
639
  ok: true,
640
+ outcome: finalizedDispatchMode,
641
+ reasonCode: finalizedDispatchMode,
642
+ message: playOutcomeMessage({
643
+ outcome: finalizedDispatchMode,
644
+ workstreamTitle: queueWorkstreamTitle,
645
+ startReasonLabel: queueStartReasonLabel,
646
+ }),
569
647
  run,
570
648
  initiativeId,
571
649
  workstreamId,
572
650
  agentId,
573
651
  ...playDispatchEnvelope(finalizedDispatchMode),
574
652
  sessionId: run.lastRunId,
653
+ dispatchableTask: queueDispatchableTask,
575
654
  slice: {
576
655
  scope,
577
656
  taskIds: matchedQueueItem?.sliceTaskIds ?? [],
@@ -589,12 +668,20 @@ export function registerMissionControlActionsRoutes(router, deps) {
589
668
  if (dispatchMode === "none" && run.status === "running" && !run.stopReason) {
590
669
  deps.sendJson(res, 202, {
591
670
  ok: true,
671
+ outcome: "dispatch_pending",
672
+ reasonCode: "dispatch_pending",
673
+ message: playOutcomeMessage({
674
+ outcome: "dispatch_pending",
675
+ workstreamTitle: queueWorkstreamTitle,
676
+ startReasonLabel: queueStartReasonLabel,
677
+ }),
592
678
  run,
593
679
  initiativeId,
594
680
  workstreamId,
595
681
  agentId,
596
682
  ...playDispatchEnvelope("pending"),
597
683
  sessionId: null,
684
+ dispatchableTask: queueDispatchableTask,
598
685
  slice: {
599
686
  scope,
600
687
  taskIds: matchedQueueItem?.sliceTaskIds ?? [],
@@ -611,6 +698,15 @@ export function registerMissionControlActionsRoutes(router, deps) {
611
698
  }
612
699
  if (dispatchMode === "none") {
613
700
  const fallbackBlockedReason = fallbackDispatch?.blockedReason ?? null;
701
+ const reasonCode = fallbackBlockedReason
702
+ ? fallbackDispatch?.retryable
703
+ ? "spawn_guard_rate_limited"
704
+ : "spawn_guard_blocked"
705
+ : run.stopReason === "blocked"
706
+ ? "no_dispatchable_task"
707
+ : run.stopReason === "completed"
708
+ ? "no_dispatchable_task"
709
+ : "dispatch_failed";
614
710
  const reason = fallbackBlockedReason ??
615
711
  (run.stopReason === "blocked"
616
712
  ? "No dispatchable task is ready for this workstream yet."
@@ -619,29 +715,42 @@ export function registerMissionControlActionsRoutes(router, deps) {
619
715
  : "Unable to dispatch this workstream right now.");
620
716
  deps.sendJson(res, fallbackDispatch?.retryable ? 429 : 409, {
621
717
  ok: false,
622
- code: fallbackBlockedReason
623
- ? fallbackDispatch?.retryable
624
- ? "spawn_guard_rate_limited"
625
- : "spawn_guard_blocked"
626
- : undefined,
718
+ outcome: reasonCode,
719
+ reasonCode,
720
+ message: playOutcomeMessage({
721
+ outcome: reasonCode,
722
+ workstreamTitle: queueWorkstreamTitle,
723
+ startReasonLabel: queueStartReasonLabel ?? reason,
724
+ fallbackBlockedReason,
725
+ }),
726
+ code: reasonCode,
627
727
  error: reason,
628
728
  run,
629
729
  initiativeId,
630
730
  workstreamId,
631
731
  agentId,
632
732
  fallbackDispatch,
733
+ dispatchableTask: queueDispatchableTask,
633
734
  error_location: "mission-control.next-up.play.dispatch",
634
735
  });
635
736
  return;
636
737
  }
637
738
  deps.sendJson(res, 200, {
638
739
  ok: true,
740
+ outcome: dispatchMode === "fallback" ? "fallback_started" : "started",
741
+ reasonCode: dispatchMode === "fallback" ? "fallback_started" : "started",
742
+ message: playOutcomeMessage({
743
+ outcome: dispatchMode === "fallback" ? "fallback_started" : "started",
744
+ workstreamTitle: queueWorkstreamTitle,
745
+ startReasonLabel: queueStartReasonLabel,
746
+ }),
639
747
  run,
640
748
  initiativeId,
641
749
  workstreamId,
642
750
  agentId,
643
751
  ...playDispatchEnvelope(dispatchMode),
644
752
  sessionId: run.activeRunId ?? fallbackDispatch?.sessionId ?? null,
753
+ dispatchableTask: queueDispatchableTask,
645
754
  slice: {
646
755
  scope,
647
756
  taskIds: matchedQueueItem?.sliceTaskIds ?? [],
@@ -1614,7 +1723,48 @@ export function registerMissionControlActionsRoutes(router, deps) {
1614
1723
  initiativeId,
1615
1724
  workstreamIds: allowedWorkstreamIds,
1616
1725
  });
1617
- deps.sendJson(res, 200, { ok: true, ...dispatchEnvelope, run });
1726
+ await deps.tickAutoContinueRun(run);
1727
+ const activeRunIds = Array.isArray(run.activeSliceRunIds)
1728
+ ? run.activeSliceRunIds.filter((id) => typeof id === "string" && id.trim().length > 0)
1729
+ : typeof run.activeRunId === "string" && run.activeRunId.trim().length > 0
1730
+ ? [run.activeRunId]
1731
+ : [];
1732
+ if (activeRunIds.length > 0) {
1733
+ await new Promise((resolve) => setTimeout(resolve, 500));
1734
+ await deps.tickAutoContinueRun(run);
1735
+ }
1736
+ const reconciledActiveRunIds = Array.isArray(run.activeSliceRunIds)
1737
+ ? run.activeSliceRunIds.filter((id) => typeof id === "string" && id.trim().length > 0)
1738
+ : typeof run.activeRunId === "string" && run.activeRunId.trim().length > 0
1739
+ ? [run.activeRunId]
1740
+ : [];
1741
+ const outcome = reconciledActiveRunIds.length > 0 || (run.status === "running" && !run.stopReason)
1742
+ ? "started"
1743
+ : run.stopReason === "blocked"
1744
+ ? "blocked"
1745
+ : run.stopReason === "completed"
1746
+ ? "completed"
1747
+ : run.stopReason === "error"
1748
+ ? "error"
1749
+ : "pending";
1750
+ const message = outcome === "started"
1751
+ ? "Autopilot enabled and running."
1752
+ : outcome === "blocked"
1753
+ ? "Autopilot enabled, but it immediately needs your input."
1754
+ : outcome === "completed"
1755
+ ? "Autopilot enabled and immediately completed the available work."
1756
+ : outcome === "error"
1757
+ ? "Autopilot hit an error before it could keep running."
1758
+ : "Autopilot is starting.";
1759
+ deps.sendJson(res, 200, {
1760
+ ok: true,
1761
+ outcome,
1762
+ reasonCode: outcome === "blocked" ? "blocked" : outcome,
1763
+ message,
1764
+ ...dispatchEnvelope,
1765
+ workstreamIds: allowedWorkstreamIds,
1766
+ run,
1767
+ });
1618
1768
  }
1619
1769
  catch (err) {
1620
1770
  sendRouteException(res, "mission-control.auto-continue.start.handler", err);
@@ -39,6 +39,15 @@ type NextUpQueueItem = {
39
39
  sliceTaskCount?: number | null;
40
40
  sliceMilestoneId?: string | null;
41
41
  milestoneBreakdown?: MilestoneBreakdownEntry[];
42
+ canStartNow?: boolean;
43
+ startReasonCode?: string | null;
44
+ startReasonLabel?: string | null;
45
+ dispatchableTask?: {
46
+ id: string;
47
+ title: string;
48
+ scope: "task" | "milestone" | "workstream";
49
+ milestoneId?: string | null;
50
+ } | null;
42
51
  isPinned?: boolean;
43
52
  pinnedRank?: number | null;
44
53
  compositeScore?: number;
@@ -464,6 +464,21 @@ function normalizeQueueItems(input) {
464
464
  ? runnerSourceHint ?? "inferred"
465
465
  : "fallback";
466
466
  const queueState = normalizeQueueState(record.queueState ?? record.queue_state);
467
+ const dispatchableTaskRecord = asRecord(record.dispatchableTask ?? record.dispatchable_task);
468
+ const dispatchableTaskId = asString(dispatchableTaskRecord?.id) ??
469
+ asString(record.dispatchableTaskId) ??
470
+ asString(record.dispatchable_task_id);
471
+ const dispatchableTaskTitle = asString(dispatchableTaskRecord?.title) ??
472
+ asString(record.dispatchableTaskTitle) ??
473
+ asString(record.dispatchable_task_title);
474
+ const dispatchableTaskScopeRaw = asString(dispatchableTaskRecord?.scope) ??
475
+ asString(record.dispatchableTaskScope) ??
476
+ asString(record.dispatchable_task_scope);
477
+ const dispatchableTaskScope = dispatchableTaskScopeRaw === "task" ||
478
+ dispatchableTaskScopeRaw === "milestone" ||
479
+ dispatchableTaskScopeRaw === "workstream"
480
+ ? dispatchableTaskScopeRaw
481
+ : null;
467
482
  const normalizedSliceScope = normalizeMissionControlStatus(asString(record.sliceScope) ?? asString(record.slice_scope));
468
483
  const sliceScope = normalizedSliceScope === "task" ||
469
484
  normalizedSliceScope === "milestone" ||
@@ -502,6 +517,24 @@ function normalizeQueueItems(input) {
502
517
  ? Math.max(0, Math.floor(sliceTaskCountRaw))
503
518
  : sliceTaskIds.length,
504
519
  sliceMilestoneId: asString(record.sliceMilestoneId) ?? asString(record.slice_milestone_id),
520
+ canStartNow: typeof record.canStartNow === "boolean"
521
+ ? record.canStartNow
522
+ : typeof record.can_start_now === "boolean"
523
+ ? record.can_start_now
524
+ : queueState === "queued" || queueState === "idle",
525
+ startReasonCode: asString(record.startReasonCode) ?? asString(record.start_reason_code),
526
+ startReasonLabel: asString(record.startReasonLabel) ?? asString(record.start_reason_label),
527
+ dispatchableTask: dispatchableTaskId && dispatchableTaskTitle && dispatchableTaskScope
528
+ ? {
529
+ id: dispatchableTaskId,
530
+ title: dispatchableTaskTitle,
531
+ scope: dispatchableTaskScope,
532
+ milestoneId: asString(dispatchableTaskRecord?.milestoneId) ??
533
+ asString(dispatchableTaskRecord?.milestone_id) ??
534
+ asString(record.dispatchableTaskMilestoneId) ??
535
+ asString(record.dispatchable_task_milestone_id),
536
+ }
537
+ : null,
505
538
  isPinned: Boolean(record.isPinned ?? record.is_pinned),
506
539
  pinnedRank: asNumber(record.pinnedRank ?? record.pinned_rank),
507
540
  compositeScore: asNumber(record.compositeScore ?? record.composite_score) ?? undefined,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-plugin",
3
3
  "name": "OrgX Integration",
4
- "version": "1.0.7",
4
+ "version": "0.7.25",
5
5
  "description": "Connects Clawdbot to OrgX for agent orchestration, quality gates, and model routing",
6
6
  "entry": "./index.js",
7
7
  "author": "OrgX Team",
@@ -1,4 +1,4 @@
1
- import Database from "better-sqlite3";
1
+ import type Database from "better-sqlite3";
2
2
  export declare function closeStateDb(): void;
3
3
  export declare function getStateDb(): Database.Database;
4
4
  export declare function readStateMeta<T>(key: string): T | null;
@@ -1,11 +1,145 @@
1
- import Database from "better-sqlite3";
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { createRequire } from "node:module";
5
+ import { fileURLToPath } from "node:url";
2
6
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "../paths.js";
3
7
  import { ensureStoreDirSync, parseJsonSafe } from "./json-store.js";
4
8
  const STATE_DB_FILENAME = "orgx-state.sqlite";
5
9
  const USER_VERSION = 1;
10
+ const BETTER_SQLITE3_REPAIR_MARKER = ".orgx-runtime-deps-version";
6
11
  let dbInstance = null;
7
12
  let dbInstancePath = "";
8
13
  let stateDbHooksRegistered = false;
14
+ let databaseConstructor = null;
15
+ let runtimeDepsRepairAttempted = false;
16
+ const require = createRequire(import.meta.url);
17
+ function currentModuleDir() {
18
+ return dirname(fileURLToPath(import.meta.url));
19
+ }
20
+ function findPluginRoot(startDir) {
21
+ let cursor = startDir;
22
+ while (true) {
23
+ const packageJsonPath = join(cursor, "package.json");
24
+ if (existsSync(packageJsonPath))
25
+ return cursor;
26
+ const parent = dirname(cursor);
27
+ if (parent === cursor)
28
+ return startDir;
29
+ cursor = parent;
30
+ }
31
+ }
32
+ function readExpectedRuntimeDepVersion(pluginRoot) {
33
+ try {
34
+ const raw = readFileSync(join(pluginRoot, "package.json"), "utf8");
35
+ const parsed = JSON.parse(raw);
36
+ return String(parsed?.dependencies?.["better-sqlite3"] || "").trim();
37
+ }
38
+ catch {
39
+ return "";
40
+ }
41
+ }
42
+ function writeRuntimeDepMarker(pluginRoot) {
43
+ const expected = readExpectedRuntimeDepVersion(pluginRoot);
44
+ if (!expected)
45
+ return;
46
+ try {
47
+ writeFileSync(join(pluginRoot, BETTER_SQLITE3_REPAIR_MARKER), expected, "utf8");
48
+ }
49
+ catch {
50
+ // best effort
51
+ }
52
+ }
53
+ function betterSqlite3Installed(pluginRoot) {
54
+ return existsSync(join(pluginRoot, "node_modules", "better-sqlite3", "package.json"));
55
+ }
56
+ function resolveBetterSqlite3InstallRoot(pluginRoot) {
57
+ try {
58
+ const packageJsonPath = require.resolve("better-sqlite3/package.json");
59
+ return dirname(dirname(dirname(packageJsonPath)));
60
+ }
61
+ catch {
62
+ return pluginRoot;
63
+ }
64
+ }
65
+ function isRecoverableBetterSqlite3Error(error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ return /could not locate the bindings file|cannot find module ['"]better-sqlite3['"]|no native build was found/i.test(message);
68
+ }
69
+ function resolveNpmCommand() {
70
+ const npmCliPath = join(dirname(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
71
+ if (existsSync(npmCliPath)) {
72
+ return {
73
+ command: process.execPath,
74
+ argsPrefix: [npmCliPath],
75
+ };
76
+ }
77
+ return {
78
+ command: "npm",
79
+ argsPrefix: [],
80
+ };
81
+ }
82
+ function repairBetterSqlite3Binding(pluginRoot) {
83
+ const installRoot = resolveBetterSqlite3InstallRoot(pluginRoot);
84
+ const { command, argsPrefix } = resolveNpmCommand();
85
+ const args = betterSqlite3Installed(installRoot)
86
+ ? [...argsPrefix, "rebuild", "better-sqlite3", "--foreground-scripts"]
87
+ : [...argsPrefix, "install", "--omit=dev"];
88
+ try {
89
+ execFileSync(command, args, {
90
+ cwd: installRoot,
91
+ env: process.env,
92
+ stdio: "pipe",
93
+ });
94
+ writeRuntimeDepMarker(pluginRoot);
95
+ }
96
+ catch (error) {
97
+ const stderr = error &&
98
+ typeof error === "object" &&
99
+ "stderr" in error &&
100
+ Buffer.isBuffer(error.stderr)
101
+ ? error.stderr.toString("utf8").trim()
102
+ : "";
103
+ const stdout = error &&
104
+ typeof error === "object" &&
105
+ "stdout" in error &&
106
+ Buffer.isBuffer(error.stdout)
107
+ ? error.stdout.toString("utf8").trim()
108
+ : "";
109
+ const detail = stderr || stdout;
110
+ const suffix = detail ? ` (${detail})` : "";
111
+ throw new Error(`Failed to repair better-sqlite3 runtime dependency${suffix}`);
112
+ }
113
+ }
114
+ function resetDatabaseConstructorCache() {
115
+ try {
116
+ const resolved = require.resolve("better-sqlite3");
117
+ delete require.cache[resolved];
118
+ }
119
+ catch {
120
+ // best effort
121
+ }
122
+ databaseConstructor = null;
123
+ }
124
+ function loadDatabaseConstructor() {
125
+ if (databaseConstructor)
126
+ return databaseConstructor;
127
+ try {
128
+ databaseConstructor = require("better-sqlite3");
129
+ return databaseConstructor;
130
+ }
131
+ catch (error) {
132
+ if (!runtimeDepsRepairAttempted && isRecoverableBetterSqlite3Error(error)) {
133
+ runtimeDepsRepairAttempted = true;
134
+ const pluginRoot = findPluginRoot(currentModuleDir());
135
+ repairBetterSqlite3Binding(pluginRoot);
136
+ resetDatabaseConstructorCache();
137
+ databaseConstructor = require("better-sqlite3");
138
+ return databaseConstructor;
139
+ }
140
+ throw error;
141
+ }
142
+ }
9
143
  function stateDbPath() {
10
144
  return getOrgxPluginConfigPath(STATE_DB_FILENAME);
11
145
  }
@@ -138,7 +272,24 @@ export function getStateDb() {
138
272
  closeStateDb();
139
273
  }
140
274
  ensureStateDbDir();
141
- const db = new Database(nextPath);
275
+ let DatabaseCtor = loadDatabaseConstructor();
276
+ let db;
277
+ try {
278
+ db = new DatabaseCtor(nextPath);
279
+ }
280
+ catch (error) {
281
+ if (!runtimeDepsRepairAttempted && isRecoverableBetterSqlite3Error(error)) {
282
+ runtimeDepsRepairAttempted = true;
283
+ const pluginRoot = findPluginRoot(currentModuleDir());
284
+ repairBetterSqlite3Binding(pluginRoot);
285
+ resetDatabaseConstructorCache();
286
+ DatabaseCtor = loadDatabaseConstructor();
287
+ db = new DatabaseCtor(nextPath);
288
+ }
289
+ else {
290
+ throw error;
291
+ }
292
+ }
142
293
  initializeDatabase(db);
143
294
  dbInstance = db;
144
295
  dbInstancePath = nextPath;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-plugin",
3
3
  "name": "OrgX Integration",
4
- "version": "1.0.7",
4
+ "version": "0.7.25",
5
5
  "description": "Connects Clawdbot to OrgX for agent orchestration, quality gates, and model routing",
6
6
  "entry": "./dist/index.js",
7
7
  "author": "OrgX Team",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useorgx/openclaw-plugin",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "OrgX plugin for OpenClaw — agent orchestration, quality gates, model routing, and live dashboard",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",